Uibuilder Security.js

Hey TotallyInformation,

I've tried what you suggested and It doesn't crash anymore. Since I'm not using any custom security.js at this point, that shouldn't be a problem at all. I'm just using your default template hoping to successfully login with user id 'test' and any password. From my understanding of the user validation in security.js the below input should be successful.

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

| 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: 'isitworking?',

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

3 May 14:29:25 - [error] [uibuilder:uiblib:logon] _auth is not valid, logon cancelled. Please check 'userValidate()' in '/data/uibuilder/.config/security.js'.

It MUST return an object with at least id, userValidated, info props. info must be an object.

[uibuilder:uiblib:logon] _auth= true```

I've just spotted a problem with the default security.js template - sorry about that.

I now notice that it defaults to a simple return of true/false and that is no longer valid :frowning:

Updated in the express branch. Here is the updated security.js code, please replace yours with this:

/* globals module */
/**
 * Copyright (c) 2020-2021 Julian Knight (Totally Information)
 * https://it.knightnet.org.uk
 *
 * 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.
 **/
/**
 * Template security functions for uibuilder.
 * Only used if the node.useSecurity flag is set.
 * Please replace with your own code.
 * 
 * You MUST export the following functions:
 *   - userValidate - based on an id, lookup the user data to see if the user is valid.
 *                    MUST return a boolean or object of type userValidation.
 *                    Called from the server's logon process. (uiblib.js::logon)
 *      
 * 
 * Each function MUST at least return true/false. See each function for more details.
 *
 * NOTES & WARNINGS:
 *   1) IF there is an error in this JavaScript, it is very likely that Node-RED will terminate.
 *   2) You can use different security.js files for different instances of uibuilder.
 *      Simply, place a securiy.js file in the instances root folder (e.g ~/.node-red/uibuilder/<url>/security.js)
 *      Note, however, that this means that the security.js file can be edited using the admin Editor.
 *      You have to restart Node-RED to pick up the new file.
 */
'use strict'

//#region ----- Variable and parameter definitions - can be removed if not wanted ----- //
/**
 * Standard msg._auth object exchanged in msg's between front-end and server
 * @typedef {import('../../index').MsgAuth} MsgAuth
 */ 
/**
 * Validated user object returned by the userValidate function
 * typedef {import('./security').userValidation} userValidation 
 */

try { // Partially fixes #126
    const TYPEDEFS = require('../../typedefs.js')
} catch (e) {}
/**
 * typedef {TYPEDEFS.MsgAuth} MsgAuth
 * @typedef {TYPEDEFS.userValidation} userValidation
 * @typedef {TYPEDEFS.userMetadata} userMetadata
 */

//#endregion ----- ------------------------------------------------------------- ----- //

module.exports = {
    /** Validate user against your own user data.
     * The minimum input data is _auth.id which must represent a unique ID.
     * Called from the logon function (uiblib.js::logon) which is triggered by a uibuilder control msg from the client of type 'logon'.
     * May also be called to revalidate users at any time.
     * @param {MsgAuth} _auth Required. 
     * @return {userValidation} Object of type userValidation
     */
    userValidate: function(_auth) {
        console.log(`[uibuilder:.common/security.js] userValidate Security from '${__filename}' used. Replace this template with your own code. _auth:`, _auth)

        // Always start by invalidating the user credentials
        _auth.userValidated = false
        
        /** Manual "test" ID validates - this will be replaced with a suitable lookup in your code - maybe from a database or a file.
         * You will also want to pass through some kind of password to validate the user.
         */
        if ( _auth.id === 'test' ) {

            console.log(`[uibuilder:.common/security.js] User id '${_auth.id}' has been validated`)

            _auth.userValidated = true
            _auth.info = {
                name: 'Jimbob',
                message: 'Hi Jimbob, don\'t forget to change your password :)'
            }

        } else {

            // In all other cases, fail the validation - optionally, you can include more info here as well.
            _auth.userValidated = false
            _auth.info = {
                message: 'Ouch! Sorry, that login is not valid'
            }
        }
        
        return _auth
        
    } // ---- End of userValidate ---- //

}

//EOF

Alright, final messing about version. Simplifies understanding of the _auth object definition.

/* globals module */
/**
 * Copyright (c) 2020-2021 Julian Knight (Totally Information)
 * https://it.knightnet.org.uk
 *
 * 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.
 **/
/**
 * Template security functions for uibuilder.
 * Only used if the node.useSecurity flag is set.
 * Please replace with your own code.
 * 
 * You MUST export the following functions:
 *   - userValidate - based on an id, lookup the user data to see if the user is valid.
 *                    MUST return a boolean or object of type userValidation.
 *                    Called from the server's logon process. (uiblib.js::logon)
 *      
 * 
 * Each function MUST at least return true/false. See each function for more details.
 *
 * NOTES & WARNINGS:
 *   1) IF there is an error in this JavaScript, it is very likely that Node-RED will terminate.
 *   2) You can use different security.js files for different instances of uibuilder.
 *      Simply, place a securiy.js file in the instances root folder (e.g ~/.node-red/uibuilder/<url>/security.js)
 *      Note, however, that this means that the security.js file can be edited using the admin Editor.
 *      You have to restart Node-RED to pick up the new file.
 */
'use strict'

/** Define the _auth object
 * @typedef {Object} _auth The standard auth object used by uibuilder security. See docs for details.
 * Note that any other data may be passed from your front-end code in the uibAuth object.
 * @property {String} id Required. A unique user identifier.
 * @property {String} [password] Required for login only.
 * @property {String} [jwt] Required if logged in. Needed for ongoing session validation and management.
 * @property {String} [sessionExpiry] Required if logged in. ISO8601 formatted date/time string. Needed for ongoing session validation and management.
 * @property {boolean} [userValidated] Required after user validation. Whether the input ID (and optional additional data from the _auth object) validated correctly or not.
 * @property {Object=} [info] Optional metadata about the user.
 */

module.exports = {
    /** Validate user against your own user data.
     * The minimum input data is _auth.id which must represent a unique ID.
     * Called from the logon function (uiblib.js::logon) which is triggered by a uibuilder control msg from the client of type 'logon'.
     * May also be called to revalidate users at any time.
     * @param {_auth} _auth Required. 
     * @return {_auth} Object of type MsgAuth
     */
    userValidate: function(_auth) {
        console.log(`[uibuilder:.common/security.js] userValidate Security from '${__filename}' used. Replace this template with your own code. _auth:`, _auth)

        // Always start by invalidating the user credentials
        _auth.userValidated = false
        
        /** Manual "test" ID validates - this will be replaced with a suitable lookup in your code - maybe from a database or a file.
         * You will also want to pass through some kind of password to validate the user.
         */
        if ( _auth.id === 'test' ) {

            console.log(`[uibuilder:.common/security.js] User id '${_auth.id}' has been validated`)

            _auth.userValidated = true
            _auth.info = {
                name: 'Jimbob',
                message: 'Hi Jimbob, don\'t forget to change your password :)'
            }

        } else {

            // In all other cases, fail the validation - optionally, you can include more info here as well.
            _auth.userValidated = false
            _auth.info = {
                message: 'Ouch! Sorry, that login is not valid'
            }
        }
        
        return _auth

    } // ---- End of userValidate ---- //

}

//EOF

Hi TotallyInformation,

I've just tried the express branch and it seems to work fine now. Thanks for that, even though I should have realised that a true/false validation is not enough in the security.js. Sometimes you just don't think about it :smiley:

best regards,

Amit Singh

No, my fault. Originally, you could just return true/false but somewhere along the line it seems that I changed my mind. I did do a lot of messing around with the format trying to get something that would work for all general and specific cases.

Thanks for the input, this should help everyone. Of course, I do note that the security coding isn't complete but each reported problem gets us a step closer :grinning:

Just curious, what did you do to _auth? In the beginning it was changed to a boolean before you tweaked a bit.

Because the logon process replaces the _auth object during a number of stages of processing including your custom userValidate fn, I changed the default userValidate to update the input _auth rather than simply output a boolean.

I also updated the chkAuth fn in uiblib.js to make it more robust about checking for a valid object and schema. And added some improved logging for when things go wrong.

Basically, _auth (which is attached to a msg of course) should always follow this schema:

/** Define the _auth object
 * @typedef {Object} _auth The standard auth object used by uibuilder security. See docs for details.
 * Note that any other data may be passed from your front-end code in the uibAuth object.
 * @property {String} id Required. A unique user identifier.
 * @property {String} [password] Required for input to login only.
 * @property {String} [jwt] Required if logged in. Needed for ongoing session validation and management.
 * @property {String} [sessionExpiry] Required if logged in. ISO8601 formatted date/time string. Needed for ongoing session validation and management.
 * @property {boolean} [userValidated] Required after user validation. Whether the input ID (and optional additional data from the _auth object) validated correctly or not.
 * @property {Object=} [info] Optional metadata about the user.
 */