Ah, as I thought. The wrong version. My fault, I should have changed the name.
You can try replacing with this which is the latest template:
/* 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'
/** Typedef: 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.
*/
/** Validate user against your own user data.
* The minimum input data is _auth.id which must represent a unique ID.
* Called when uib receives an 'auth' control msg and on receipt of any standard msg - via this.authCheck2
* May also be called to revalidate users at any time.
* SHOULD *NOT* CONTAIN A PASSWORD CHECK
* SHOULD CONTAIN A SESSION CHECK
* SHOULD BE KEPT SHORT - as it is called for every msg
* SHOULD UPDATE LAST SEEN timestamp
* REQUIRES a logon process that keeps track of client auths
* @param {_auth} _auth Required.
* @returns {_auth} Updated _auth object
*/
function userValidate(_auth) {
console.log(`[uibuilder:.common/security.js] userValidate Security from '${__filename}' used. Replace this template with your own code. _auth:`, _auth)
/** Always start by assuming the user auth is invalid
* Because a client could change it, previous setting must not be trusted here.
*/
_auth.userValidated = false
/** If the JWT is invalid, then the user auth is revoked */
if ( _auth.info.validJwt !== true ) this.removeUserAuth(_auth)
/** This is optional, a default pseudo-user that always validates
*/
if ( _auth.id === 'anonymous' ) {
_auth.userValidated = true
// Add any extra metadata you want
_auth.info.name = 'Anonymous'
_auth.info.message = 'Lets you control data access in your flows'
} 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'
}
//? Needed?
if ( _auth.userValidated === false ) this.removeUserAuth(_auth)
return _auth
} // ---- End of userValidate ---- //
/** Called from security.logon */
function captureUserAuth(_auth) {}
function removeUserAuth(_auth) {
if ( uibSessions[_auth.id] ) {
delete uibSessions[_auth.id]
}
console.log(`[uibuilder:.common/security.js:removeUserAuth] Clent session authorisation removed for ${_auth.id}`)
return _auth
}
/** Validate user against your own user data.
* The minimum input data is _auth.id which must represent a unique ID.
* Called when uib receives an 'auth' control msg and on receipt of any standard msg - via this.authCheck2
* May also be called to revalidate users at any time.
* SHOULD *NOT* CONTAIN A PASSWORD CHECK
* SHOULD CONTAIN A SESSION CHECK
* SHOULD BE KEPT SHORT - as it is called for every msg
* SHOULD UPDATE LAST SEEN timestamp
* REQUIRES a logon process that keeps track of client auths
* @param {_auth} _auth Required.
* @param {number} sessionLength The number of seconds a session can last for before needing a new login
* @returns {_auth} Updated _auth object
*/
function checkUserAuth(_auth, sessionLength) {
//console.log(`[uibuilder:.common/security.js:checkUserAuth] Security from '${__filename}' used. Replace this template with your own code. _auth:`, _auth)
/** Always start by assuming the user auth is invalid
* Because a client could change it, previous setting must not be trusted here.
*/
_auth.userValidated = false
/** If the JWT is invalid, then the user auth is revoked */
if ( _auth.info.validJwt !== true ) this.removeUserAuth(_auth)
//! THIS SHOULD ONLY check if session, update lastSeen - move to captureUserAuth
// If client session doesn't exist, create it
if ( ! uibSessions[_auth.id] ) {
uibSessions[_auth.id] = {id: _auth.id}
// Add the session expiry timestamp
let dt = new Date()
dt.setSeconds(dt.getSeconds() + sessionLength)
uibSessions[_auth.id].sessionExpiry = dt.toISOString()
console.log(`[uibuilder:.common/security.js:checkUserAuth] Clent authorisation added ${_auth.id}`)
}
// If no valid session for this id, invalidate and return
if ( ! uibSessions[_auth.id] ) {
_auth.userValidated = false
_auth.info.message = 'No session found, please log in'
console.log('[uibuilder:.common/security.js:checkUserAuth] No session found')
} else {
// Otherwise update the timestamp ...
const session = uibSessions[_auth.id]
// Save last seen timestamp
session.lastSeen = (new Date()).toISOString()
// ... and check if the session has expired
if ( session.lastSeen >= session.sessionExpiry ) {
_auth.userValidated = false
_auth.info.message = 'Session has expired, please log in again'
console.log('[uibuilder:.common/security.js:checkUserAuth] Session expired, user not validated.', session)
} else {
_auth.userValidated = true
_auth.info.message = 'Session updated'
}
console.log('[uibuilder:.common/security.js:checkUserAuth] Updated session.', session)
}
//? Needed?
if ( _auth.userValidated === false ) _auth = this.removeUserAuth(_auth)
return _auth
} // ---- End of checkUserAuth ---- //
// Silly client session store - replace with a file or database!
const uibSessions = {}
//! FOR TESTING
// Add the session expiry timestamp
let dt = new Date()
dt.setSeconds(dt.getSeconds() + 432000)
uibSessions['anonymous'] = {
id: 'anonymous',
sessionExpiry: dt.toISOString(),
}
module.exports = {
/** Allow users to sign up for themselves.
* Build in any workflow you like.
* If external authorisation is required, you will need to create a separate app to write to your user store.
* The simple template example will allow anyone to self-signup
*/
userSignup: function () {},
userValidate: userValidate,
/** Capture user logins. Needed to control msgs from Node-RED to the Front-End
* Unless some details are captured, it would not be possible to limit outgoing messages
* to only authorised users.
*/
captureUserAuth: captureUserAuth,
removeUserAuth: removeUserAuth,
checkUserAuth: checkUserAuth,
jwtValidateCustom: function() {},
jwtCreateCustom: function() {},
}
//EOF
I'm adding an extra error log message into the appropriate function. Will push shortly.