Uibuilder Security.js

Hey Guys,

I'm struggling with setting up uibuilder's in-build security mechanism. I'm using a webpack and nginx setup based on the wiki. Everything seems to work fine. When I activate the uibuilder security (in dev mode), the first thing I notice is that the security.js template is not copied even though it should, right (when copy flag is on)? Alright, so I copied the template myself into the root directory of my uibuilder instance. And tried to logon with the supplied test user without passwort, but the socket connection closes, security.js is not even evaluated at all. Does anyone else have this problem? Did I get anything wrong? Any help is appreciated.

best regards,

Amit Singh

Please firstly note that the documentation does state that the security parts are not fully live as yet and so it is more likely that issues will arise.

Not sure why the template file isn't copying over though, I think that it should do. However, I did make some changes to the template handling in the last release so maybe a regression has crept in. I will try to test.

For the other issue, can you check both the Node-RED logs and the browser console to see if there are any errors?

Note that the security functions in the node do have a number of detailed console.log statements still in place so you may be able to see what is happening in regard to the authentication process in the Node-RED log.

The key thing is the presence of the msg._auth object. This is expected to be present in all messages from login onwards. That has a defined schema that is documented in the technical docs. The most important element being msg._auth.id. The template security.js file just checks to see if the id="test" and ignores everything else. You should see the process happening via the log statements.

Hi TotallyInformation,

first of all: Thanks for all the effort you put into the projects and the quick responses on all platforms.

Regarding the logs:

Chrome: ​

Log:

[uibuilder:socket.on.control] Use Security _auth: { id: 'test', info: {} } . Node ID: 8c8923f2.99973
[uibuilder:uiblib.js:checkToken] response: { id: 'test',
jwt: undefined,
info:
{ validJwt: false,
error:
{ JsonWebTokenError: jwt must be provided
at Object.module.exports [as verify] (/data/node_modules/jsonwebtoken/verify.js:53:17)
at Object.checkToken (/data/node_modules/node-red-contrib-uibuilder/nodes/uiblib.js:741:49)
at Object.authCheck (/data/node_modules/node-red-contrib-uibuilder/nodes/uiblib.js:653:22)
at Socket. (/data/node_modules/node-red-contrib-uibuilder/nodes/uibuilder.js:621:44)
at Socket.emit (events.js:203:15)
at /data/node_modules/socket.io/lib/socket.js:528:12
at process._tickCallback (internal/process/next_tick.js:61:11) name: 'JsonWebTokenError', message: 'jwt must be provided' } } }

It seems like uiblib.js doesn't provide a jwt.

Thanks for the help.

best regards,

Amit Singh

Hi Amit,

Could I trouble you to do a simplified test so that we can tell whether this is NGINX getting in the way or some other issue?

If you could try without NGINX and using the standard template (so no hidden complexities from webpack either)?

The sequence should be something like:

  1. User loads a uibuilder enabled page. Initial control messages will be exchanged but no standard messages can be exchanged. So your page needs to check whether the user is logged in. If not, you need to show a login form.
  2. User submits a login.
  3. The uibuilder node checks the login data (userid, password and anything else you want to use). It validates the user in the standard functions in security.js.
  4. If your function in security.js returns that the login details are valid, uibuilder should generate the JWT and attach it to the return msg to the front-end.
  5. The uibuilderfe should then automatically include the JWT in every msg back to Node-RED, the uibuilder node should do the same back to the front-end.

Docs:

Hi TotallyInformation,

I've already used a fresh install without nginx. Just a fresh node-red container on docker. I turned on the security, provided an jwt secret.I then copied your security-template from Github and tried the simple login template with input "test" as user id, So no fancy setup at this point, still the above output.

Does your template work for you?

best regards,

Amit Singh

Hi Amit, I'm trying to find some time to test. Didn't get much time at the weekend. I'll see if I can test this evening.

Boring meeting so I set up a quick test :slight_smile:

This is working:

image

[{"id":"e60f71ab.a49d8","type":"inject","z":"18cb249f.38bafb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":4560,"wires":[["7b172412.fe10ac"]]},{"id":"7b172412.fe10ac","type":"uibuilder","z":"18cb249f.38bafb","name":"","topic":"","url":"sectest","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"useSecurity":true,"sessionLength":432000,"tokenAutoExtend":true,"x":330,"y":4560,"wires":[["e63ab0f6.5f1d"],["c5a226b1.5a57c8"]]},{"id":"e63ab0f6.5f1d","type":"debug","z":"18cb249f.38bafb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":475,"y":4540,"wires":[],"l":false},{"id":"c5a226b1.5a57c8","type":"debug","z":"18cb249f.38bafb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":475,"y":4580,"wires":[],"l":false}]

HTML

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Node-RED UI Builder - VueJS + bootstrap-vue default template</title>
    <meta name="description" content="Node-RED UI Builder - VueJS + bootstrap-vue default template">

    <link rel="icon" href="./images/node-blue.ico">

    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
    <!-- Your own CSS -->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

</head><body>

    <div id="app" v-cloak>
        <b-container id="app_container">

            <b-img src="./images/node-blue-192x192.png" rounded left v-bind="imgProps" alt="Blue Node-RED" class="mt-1 mr-2"></b-img>
            <h1>UIbuilder + Vue.js + bootstrap-vue for Node-RED</h1>
            <p>
                This is a uibuilder example using <a href="https://vuejs.org/">Vue.js</a> as a front-end library.
                See the
                <a
                    href="https://github.com/TotallyInformation/node-red-contrib-uibuilder">node-red-contrib-uibuilder</a>
                README for details on how to use UIbuilder.
            </p>

            <h2>Simple Logon/Logoff form</h2>
            <b-form class="border p-3 m-2">
                <p v-if="! isLoggedOn">
                    <b-form-input v-model="inputId" type="text" placeholder="Enter user id to logon"></b-form-input><br>
                    <b-form-input v-model="inputPw" type="password" placeholder="Enter user password to logon"></b-form-input><br>
                    <b-button id="btn_logon" pill variant="primary" v-on:click="doLogon">Log On</b-button>
                </p>
                <p v-if="isLoggedOn">
                    <b-button id="btn_logoff" pill variant="primary" v-on:click="doLogoff">Log Off</b-button>
                </p>
            </b-form>
            
            <b-card>
                <h3 slot="header">Simple input using Vue</h3>
                
                Simplest possible way of getting event information back to Node-RED using a uibuilder helper function.
                The method works with any DOM event. Demonstrated here with a simple button click.
                
                <p class="mt-1 pb-3">
                    <b-button id="myButton1" @click="doEvent" data-something="hello">Send Something To Node-RED</b-button>
                </p>

                <b-form class="border p-3">
                    <p>
                        You can also very simply create a form like this using Vue & bootstrap-vue.
                        The form sends data back to Node-RED.
                        Look at the <code>increment</code> method in <code>index.js</code> to see how easy this is.
                    </p>

                    <p>
                        <b-form-input v-model="inputText" type="text" placeholder="Enter some text to send to Node-RED"></b-form-input><br>
                        <b-form-checkbox v-model="inputChkBox">
                            To tick or not to tick? That is the question
                        </b-form-checkbox><br>
                        <b-button id="btn_increment" pill variant="primary" v-on:click="increment">Increment</b-button>
                            &nbsp;&nbsp;Click Counter: <b>{{counterBtn}}</b>.
                        <p>Click on the button to increment the counter. It sends the data dynamically back to Node-RED as well.</p>
                    </p>
                </b-form>
            </b-card>
            
            <h2>Dynamic Data</h2>
            <div>
                <p>Uses Vue to dynamically update in response to messages from Node-RED.</p>
                <p>
                    Check out the <code>mounted</code> function in <code>index.js</code> to See
                    how easy it is to update Vue data from Node-RED.
                </p>

                <b-card class="mt-3" header="Status" border-variant="info" header-bg-variant="info" header-text-variant="white" align="center" >
                    <p class="float-left">Socket.io Connection Status: <b>{{socketConnectedState}}</b></p>
                    <p class="float-right">Time offset between browser and server: <b>{{serverTimeOffset}}</b> hours</p>
                </b-card>

                <b-card class="mt-3" header="Normal Messages" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="left" >
                    <p>
                        Messages: Received=<b>{{msgsReceived}}</b>, Sent=<b>{{msgsSent}}</b>
                    </p>
                    <pre v-html="hLastRcvd" class="syntax-highlight"></pre>
                    <pre v-html="hLastSent" class="syntax-highlight"></pre>
                    <p slot="footer" class="mb-0">
                        The received message is from the input to the uibuilder node.
                        The send message will appear out of port #1 of the node.
                    </p>
                </b-card>

                <b-card class="mt-3" header="Control Messages" border-variant="secondary" header-bg-variant="secondary" header-text-variant="white" align="left" >
                    <p>
                        Control Messages: Received=<b>{{msgsControl}}</b>, Sent=<b>{{msgsCtrlSent}}</b>
                    </p>
                    <pre v-html="hLastCtrlRcvd" class="syntax-highlight"></pre>
                    <pre v-html="hLastCtrlSent" class="syntax-highlight"></pre>
                    <p slot="footer" class="mb-0">
                        Control messages always appear out of port #2 of the uibuilder node
                        whether they are from the server or the client. The <code>from</code> property
                        of the message tells you where it came from.
                    </p>
                </b-card>
            </div>

        </b-container>
    </div>

    <!-- These MUST be in the right order. Note no leading / -->

    <!-- REQUIRED: Socket.IO is loaded only once for all instances. Without this, you don't get a websocket connection -->
    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>

    <!-- Vendor Libraries - Load in the right order, use minified, production versions for speed -->
    <script src="../uibuilder/vendor/vue/dist/vue.js"></script> <!-- dev version with component compiler -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.min.js"></script>   prod version with component compiler -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.runtime.min.js"></script>   prod version without component compiler -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script> <!-- Dev version -->
    <!-- <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.min.js"></script>   Prod version -->

    <!-- REQUIRED: Sets up Socket listeners and the msg object -->
    <script src="./uibuilderfe.js"></script> <!-- dev version -->
    <!-- <script src="./uibuilderfe.min.js"></script>     prod version -->

    <!-- OPTIONAL: You probably want this. Put your custom code here -->
    <script src="./index.js"></script>

</body></html>

JS

/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder */
// @ts-nocheck
/*
  Copyright (c) 2021 Julian Knight (Totally Information)

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
'use strict'

/** @see https://totallyinformation.github.io/node-red-contrib-uibuilder/#/front-end-library */

// eslint-disable-next-line no-unused-vars
const app = new Vue({
    el: '#app',

    data() { return {

        startMsg    : 'Vue has started, waiting for messages',
        feVersion   : '',
        counterBtn  : 0,
        inputText   : null,
        inputChkBox : false,
        socketConnectedState : false,
        serverTimeOffset     : '[unknown]',
        imgProps             : { width: 75, height: 75 },

        msgRecvd    : '[Nothing]',
        msgsReceived: 0,
        msgCtrl     : '[Nothing]',
        msgsControl : 0,

        msgSent     : '[Nothing]',
        msgsSent    : 0,
        msgCtrlSent : '[Nothing]',
        msgsCtrlSent: 0,

        isLoggedOn  : false,
        userId      : null,
        userPw      : null,
        inputId     : '',
        inputPw     : '',

    }}, // --- End of data --- //

    computed: {

        hLastRcvd: function() {
            var msgRecvd = this.msgRecvd
            if (typeof msgRecvd === 'string') return 'Last Message Received = ' + msgRecvd
            else return 'Last Message Received = ' + this.syntaxHighlight(msgRecvd)
        },
        hLastSent: function() {
            var msgSent = this.msgSent
            if (typeof msgSent === 'string') return 'Last Message Sent = ' + msgSent
            else return 'Last Message Sent = ' + this.syntaxHighlight(msgSent)
        },
        hLastCtrlRcvd: function() {
            var msgCtrl = this.msgCtrl
            if (typeof msgCtrl === 'string') return 'Last Control Message Received = ' + msgCtrl
            else return 'Last Control Message Received = ' + this.syntaxHighlight(msgCtrl)
        },
        hLastCtrlSent: function() {
            var msgCtrlSent = this.msgCtrlSent
            if (typeof msgCtrlSent === 'string') return 'Last Control Message Sent = ' + msgCtrlSent
            //else return 'Last Message Sent = ' + this.callMethod('syntaxHighlight', [msgCtrlSent])
            else return 'Last Control Message Sent = ' + this.syntaxHighlight(msgCtrlSent)
        },

    }, // --- End of computed --- //

    methods: {

        doLogon: function() {
            uibuilder.logon( {
                'id': this.inputId,
                'password': this.inputPw,
            } )
        }, // --- End of doLogon --- //

        doLogoff: function() {
            uibuilder.logoff()
        }, // --- End of doLogon --- //

        // Called from the increment button - sends a msg to Node-RED
        increment: function(event) {
            console.log('Button Pressed. Event Data: ', event)

            // Increment the count by one
            this.counterBtn = this.counterBtn + 1
            var topic = this.msgRecvd.topic || 'uibuilder/vue'
            uibuilder.send( {
                'topic': topic,
                'payload': {
                    'type': 'counterBtn',
                    'btnCount': this.counterBtn,
                    'message': this.inputText,
                    'inputChkBox': this.inputChkBox
                }
            } )

        }, // --- End of increment --- //

        // REALLY Simple method to return DOM events back to Node-RED. See the 2nd b-button on the default html
        doEvent: uibuilder.eventSend,

        // return formatted HTML version of JSON object
        syntaxHighlight: function(json) {
            json = JSON.stringify(json, undefined, 4)
            json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
            json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
                var cls = 'number'
                if (/^"/.test(match)) {
                    if (/:$/.test(match)) {
                        cls = 'key'
                    } else {
                        cls = 'string'
                    }
                } else if (/true|false/.test(match)) {
                    cls = 'boolean'
                } else if (/null/.test(match)) {
                    cls = 'null'
                }
                return '<span class="' + cls + '">' + match + '</span>'
            })
            return json
        }, // --- End of syntaxHighlight --- //

    }, // --- End of methods --- //

    // Available hooks: beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed, activated,deactivated, errorCaptured

    /** Called after the Vue app has been created. A good place to put startup code */
    created: function() {

        // Example of retrieving data from uibuilder
        this.feVersion = uibuilder.get('version')

        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder
         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
         * e.g. uibuilder.start('/uib', '/uibuilder/vendor/socket.io') // change to use your paths/names
         * @param {Object=|string=} namespace Optional. Object containing ref to vueApp, Object containing settings, or String IO Namespace override. changes self.ioNamespace from the default.
         * @param {string=} ioPath Optional. changes self.ioPath from the default
         * @param {Object=} vueApp Optional. Reference to the VueJS instance. Used for Vue extensions.
         */
        uibuilder.start(this) // Single param passing vue app to allow Vue extensions to be used.

        //console.log(this)

    }, // --- End of created hook --- //

    /** Called once all Vue component instances have been loaded and the virtual DOM built */
    mounted: function(){

        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        var app = this  // Reference to `this` in case we need it for more complex functions

        // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO
        uibuilder.onChange('msg', function(msg){
            //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            app.msgRecvd = msg
            app.msgsReceived = uibuilder.get('msgsReceived')
        })

        //#region ---- Debug info, can be removed for live use ---- //

        /** You can use the following to help trace how messages flow back and forth.
         * You can then amend this processing to suite your requirements.
         */

        // If we receive a control message from Node-RED, we can get the new data here - we pass it to a Vue variable
        uibuilder.onChange('ctrlMsg', function(msg){
            //console.info('[indexjs:uibuilder.onChange:ctrlMsg] CONTROL msg received from Node-RED server:', msg)
            app.msgCtrl = msg
            app.msgsControl = uibuilder.get('msgsCtrl')
        })

        /** You probably only need these to help you understand the order of processing
         * If a message is sent back to Node-RED, we can grab a copy here if we want to
         */
        uibuilder.onChange('sentMsg', function(msg){
            //console.info('[indexjs:uibuilder.onChange:sentMsg] msg sent to Node-RED server:', msg)
            app.msgSent = msg
            app.msgsSent = uibuilder.get('msgsSent')
        })

        /** If we send a control message to Node-RED, we can get a copy of it here */
        uibuilder.onChange('sentCtrlMsg', function(msg){
            //console.info('[indexjs:uibuilder.onChange:sentCtrlMsg] Control message sent to Node-RED server:', msg)
            app.msgCtrlSent = msg
            app.msgsCtrlSent = uibuilder.get('msgsSentCtrl')
        })

        /** If Socket.IO connects/disconnects, we get true/false here */
        uibuilder.onChange('ioConnected', function(connected){
            //console.info('[indexjs:uibuilder.onChange:ioConnected] Socket.IO Connection Status Changed to:', connected)
            app.socketConnectedState = connected
        })
        /** If Server Time Offset changes */
        uibuilder.onChange('serverTimeOffset', function(serverTimeOffset){
            //console.info('[indexjs:uibuilder.onChange:serverTimeOffset] Offset of time between the browser and the server has changed to:', serverTimeOffset)
            app.serverTimeOffset = serverTimeOffset
        })

        /** If user is logged on/off */
        uibuilder.onChange('isAuthorised', function(isAuthorised){
            //console.info('[indexjs:uibuilder.onChange:isAuthorised] isAuthorised changed. User logged on?:', isAuthorised)
            //console.log('authData: ', uibuilder.get('authData'))
            //console.log('authTokenExpiry: ', uibuilder.get('authTokenExpiry'))
            app.isLoggedOn = isAuthorised
        })

        //#endregion ---- Debug info, can be removed for live use ---- //

    }, // --- End of mounted hook --- //

}) // --- End of app1 --- //

// EOF

It is based on the default template with a minimal logon/logoff form.

With the default template security.js file. You need to use id=test, password=anything

Hi guys,

I'm glad i found this topic, because I've been trying to implement security into uibuilder myself.

I tried out the short example from @TotallyInformation, but I am also getting the same error. But there is more. When i click the button on Julian's example to submit the form i get this error:

"[uibuilder:uiblib:logon] Security is ON but security.js could not be required. Cannot process logons. Is security.js a valid Node.js module?"

Don't know why but although the require path seems right, the require() function returns null.

And then when i click the button again it says:

20 Apr 15:29:17 - [red] Uncaught Exception:
20 Apr 15:29:17 - TypeError: Cannot read property 'userValidate' of null
at Object.logon (/home/opc/.node-red/node_modules/node-red-contrib-uibuilder/nodes/uiblib.js:891:27)
at Socket. (/home/opc/.node-red/node_modules/node-red-contrib-uibuilder/nodes/uibuilder.js:611:28)
at Socket.emit (events.js:314:20)
at /home/opc/.node-red/node_modules/socket.io/lib/socket.js:528:12
at processTicksAndRejections (internal/process/task_queues.js:79:11)

I'm hoping we will resolve this issue somehow, otherwise I need to implement security some other way, as I am doing a node-red school project and the deadline is coming :grimacing:.

Adam

So if the simple example isn't working for you, there must be something in your local environment that is preventing it from working.

Can you post the opening couple of dozen lines from your Node-RED log so that we can see what configuration you are using? Can you also confirm whether you are using a reverse proxy (web server) of any kind?

This means that either security.js is not there (in ~/.node-red/uibuilder/.common/security.js if you are using a standard Node-RED install without projects turned on.

Or that you have changed it such that it is no longer a valid Node.js module which means that Node.JS's require function cannot import it.

The rest of that error is because the functions don't exist since they would have been defined in the security.js file.

The Example is not working for me as well. I guess it has something to do with the node-red on docker. I'll try to confirm that by setting up a node-red environment without docker. Does projects feature turned on make a difference for requiring the security.js file?

While it shouldn't, the projects feature does move things around a lot so it is possible that there could be and issue there. It is certainly one of the variables.

Have you tried your template with node-red on a docker container? Or another node-red setup. Because on my fresh node-red docker I'm not "projects", but still the same problem.

I don't use docker so I can't help there directly I'm afraid. It increasingly sounds like some kind of docker issue though.

Lets regroup for a second and agree what we know:

  • The front end doesn't get a JWT back when attempting what should be a successful login?
  • The Node-RED log shows that you can't require the security.js file?
  • Both examples of the error are happening on Docker with an otherwise clean install, the simple (otherwise working) example and without projects turned on?

If this is the case, does it imply that the security.js file cannot be reached? Is that a security access problem?

Try putting the following code into your settings.js file - it can go OUTSIDE and before the module.exports:

const x = require('C:/src/nr2/data/uibuilder/.config/security.js')
console.log( {x} )

Obviously, change the path to the absolute path of your uibuilder root folder.
You should get the following output near the start of the Node-RED log:

{ x: { userValidate: [Function: userValidate] } }

This will prove whether or not, Node-RED can successfully reach the file and if not, should tell us why not.

Oh, and please share the start of your Node-RED log down from the "Welcome to Node-RED" message down to the "Started flows" message.

Okay, so in my instance I am running on an Oracle VM with projects turned on, though I am not using Docker.

What I found is that when requiring the security.js file inside uiblib.js it returned null. The file could be reached because otherwise it would throw some exception saying that this in not a module. So i looked at the security.js file and it turns out that it was not able to run this line:

const TYPEDEFS = require('../../typedefs.js')

Because there was no such file. So i copied the typedefs.js over to my project's folder and now this part is solved. I'm now able to return that object with message and userValidated = true.

Don't know about the JWT yet. I will now continue in implementing the security using Julian's documentation.

I''ll let you know if i come across any other issues.

Adam

Also,this is the requested Node-RED log

22 Apr 09:38:54 - [info] Node-RED version: v1.2.9
22 Apr 09:38:54 - [info] Node.js version: v12.21.0
22 Apr 09:38:54 - [info] Linux 5.4.17-2036.103.3.1.el7uek.x86_64 x64 LE
22 Apr 09:38:55 - [info] Loading palette nodes
22 Apr 09:38:56 - [info] +-----------------------------------------------------
22 Apr 09:38:56 - [info] | uibuilder initialised:
22 Apr 09:38:56 - [info] | root folder: /home/opc/.node-red/projects/Visualization_platform/uibuilder
22 Apr 09:38:56 - [info] | version . .: 3.2.1
22 Apr 09:38:56 - [info] | packages . : jquery,socket.io,vue,bootstrap-vue,bootstrap,vue-router,http-vue-loader,chart.js,vue-svg-gauge,vue-browser-geolocation
22 Apr 09:38:56 - [info] +-----------------------------------------------------
22 Apr 09:38:57 - [info] Worldmap version 2.9.0
22 Apr 09:38:57 - [info] Dashboard version 2.28.1 started at /ui
22 Apr 09:38:57 - [info] Settings file : /home/opc/.node-red/settings.js
22 Apr 09:38:57 - [info] Context store : 'default' [module=memory]
22 Apr 09:38:57 - [info] User directory : /home/opc/.node-red
22 Apr 09:38:57 - [info] Server now running at https://127.0.0.1:1880/
22 Apr 09:38:57 - [info] Active project : Visualization_platform

After messing around with the function createToken() inside uiblib.js I was able to fix the issue with generating the JWT in my app.

image

So the jsonwebtoken.sign() function was able to generate the JWT but there was one exception thrown, resulting into an error.

I was able to make it work, changing the line where you assign the _auth.info.validJwt value like this:

image

It now works and I'm getting a valid _auth object with generated JWT.

Adam

1 Like

Thanks for working through it with patience.

Drat, that's my fault. I was trying to get VScode to play nicely with my code. I need to sort that out for the next release.

You can, of course, remove all of the type definition stuff. I've added a try/catch and a region comment to say that it can all be removed if not wanted.

Aghh! What a daft error! I even have the correct line in the catch section:

if (!_auth.info) _auth.info = {}

Though I've no idea why I wasn't getting an error and you were. Hmm, thinking about it, maybe I am actually sending that property from the front end. The dangers of being too close to the code.

I will try to get a new version out this weekend. I've some other changes to make as well.


Fixes are included in the "express" branch which you can install direct from GitHub if you want to.

Yeah, so the authentication is working, but I have now stumbled upon a very annoying problem. So the uibuilder with security on is supposed to use control messages only. But even when trying on the simple example that you provided, I am not able to receive any messages in the front end. Only the automatically generated ones like "authorised" or "client connected", but not the "manual send" control message.

image

I'm just trying to see the control message pop up in the front end, but with no results and no errors. Maybe I am missing something. @TotallyInformation would you please take a look at this?

I remember that when receiving a ctrlMsg and wanting to send it back to uibuilder I have to remove the uibuilderCtrl attribute, but then it becomes a regular data message, which when sent to uibuilder, it breaks the authorisation and logs me out. So how am I supposed to send control messages only?

Adam

#Update : just noticed that when I send a control message it lacks the property userValidated: true

Do you have example code you can share so that I can reproduce?

Hi,

sorry for being late to the party and not providing the necessary informations, but it seems like you made some crucial steps for making auth work. I've tried the express branch with the base setup you provided @TotallyInformation and getting the following error, when trying to login.

3 May 09:25:56 - [warn] [uibuilder:uiblib:logon] **WARNING**


+---------------------------------------------------------------+

| uibuilder security warning: |

| A logon is being processed without TLS security turned on. |

| This works, with warnings, in a development environment. |

| It will NOT work for non-development environments. |

| See the uibuilder security docs for details. |

+---------------------------------------------------------------+


[uibuilder:security.js] userValidate Security from /data/uibuilder/.config/security.js used. Replace this template with your own code. _auth: { id: 'test',

password: 'test12345',

info:

{ warning:

'\n \n +---------------------------------------------------------------+\n | uibuilder security warning: |\n | A logon is being processed without TLS security turned on. |\n | This works, with warnings, in a development environment. |\n | It will NOT work for non-development environments. |\n | See the uibuilder security docs for details. |\n +---------------------------------------------------------------+\n ' },

userValidated: false }

[uibuilder:security.js] User id test has been validated

[uibuilder:uiblib.js:logon] Updated _auth: true

true

3 May 09:25:56 - [red] Uncaught Exception:

3 May 09:25:56 - TypeError: Cannot create property 'info' on boolean 'true'

at Object.logon (/data/node_modules/node-red-contrib-uibuilder/nodes/uiblib.js:954:28)

at Socket.<anonymous> (/data/node_modules/node-red-contrib-uibuilder/nodes/uibuilder.js:663:28)

at Socket.emit (events.js:198:13)

at /data/node_modules/socket.io/lib/socket.js:528:12

at process._tickCallback (internal/process/next_tick.js:61:11)

npm ERR! code ELIFECYCLE

npm ERR! errno 1

npm ERR! node-red-docker@1.2.7 start: `node $NODE_OPTIONS node_modules/node-red/red.js $FLOWS "--userDir" "/data"`

npm ERR! Exit status 1

npm ERR!

npm ERR! Failed at the node-red-docker@1.2.7 start script.

npm ERR! This is probably not a problem with npm. There is likely additional logging output above.


npm ERR! A complete log of this run can be found in:

npm ERR! /data/.npm/_logs/2021-05-03T09_25_56_556Z-debug.log

It seems like auth is working fine, but raising another exception. Is _auth set to a boolean somewhere in the process, which raises this error?

best regards,

Amit Singh

Hi Amit, this doesn't happen in my tests. And I can't see how it could happen with the code as it stands today. That's because the console.log statement that outputs [uibuilder:uiblib.js:logon] Updated _auth: is immediately after the chkAuth function call that makes certain that the _auth object is correctly formatted. Since that has reported success, I don't see how _auth suddenly becomes true in the very next line?

To be certain though, I've made a couple of tweaks to the chkAuth and some other functions - could you please reinstall from the express branch so that you get the latest code & restart node-red.


With the default template security.js file, using the following function from your front-end javascript will result in a successful logon:

            uibuilder.logon( {
                'id': 'test',
                'password': 'anything',
            } )

The result that the front-end receives back from Node-RED results in an "isAuthorised" event so you can action that using:

uibuilder.onChange('isAuthorised', function(isAuthorised){
    console.info('[indexjs:uibuilder.onChange:isAuthorised] isAuthorised changed. User logged on?:', isAuthorised)
    console.log('authData: ', uibuilder.get('authData'))
    vueApp.isLoggedOn = isAuthorised
})

The isAuthorised event will also be triggered by Node-RED de-authorising the login and by a login timespan expiring. It is true if the user is authorised, false otherwise. I use the above function so that Vue uses it to either display a login OR a logout form.

If that doesn't work, I would recommend double-checking what you have done in the security.js file.