|
[ |
|
{ |
|
"id": "5f40353168982c37", |
|
"type": "comment", |
|
"z": "1ab76f62fe829c73", |
|
"name": "Header-based authorization (w/ expiration)", |
|
"info": "", |
|
"x": 180, |
|
"y": 40, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "8d77be6450ce0f2a", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "validate authorization", |
|
"func": " let TokenHeader = msg.req.headers['authorization'] || ''\n if (TokenHeader.startsWith('Bearer')) {\n let UserRegistry = global.get('UserRegistry') || Object.create(null)\n\n let Token = TokenHeader.replace(/^Bearer\\s+/,'').trim()\n if (Token !== '') {\n let [UserId,Expiration,Digest] = Token.split(':')\n UserId = UserId.toLowerCase()\n if (\n (UserId !== '') && (UserId in UserRegistry) && (UserRegistry[UserId] != null) &&\n /^\\d+$/.test(Expiration) && /^[0-9a-fA-F]+$/.test(Digest)\n ) {\n let TokenKey = global.get('TokenKey')\n const HMAC = crypto.createHmac('sha256',TokenKey)\n HMAC.update(UserId + ':' + Expiration)\n let expectedDigest = HMAC.digest('hex')\n\n if (\n (Digest === expectedDigest) &&\n (parseInt(Expiration,10) >= Date.now())\n ) {\n let UserRoles = UserRegistry[UserId].Roles || []\n if (\n (msg.requiredRole == null) ||\n (UserRoles.indexOf(msg.requiredRole) >= 0)\n ) {\n msg.authenticatedUser = UserId\n msg.authorizedRoles = UserRoles\n\n msg.headers = msg.headers || {}\n msg.headers['authorization'] = 'Bearer ' + Token\n return [msg,null] // authorized\n } else {\n msg.cookies = msg.cookies || {}\n msg.cookies.authorization = null\n\n msg.payload = 'Unauthorized'\n msg.statusCode = 401\n return [null,msg] // not authorized\n }\n }\n }\n }\n }\n\n msg.payload = 'Unauthorized'\n msg.statusCode = 401\n return [null,msg] // not authorized\n", |
|
"outputs": 2, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [ |
|
{ |
|
"var": "crypto", |
|
"module": "crypto" |
|
} |
|
], |
|
"x": 160, |
|
"y": 300, |
|
"wires": [ |
|
[ |
|
"79785206c30e98c3" |
|
], |
|
[ |
|
"478efb8ea02f84dd" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "b21258d4f3b19a31", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "validate credentials", |
|
"func": " let UserId = (msg.payload.UserId || '').toLowerCase()\n let Password = msg.payload.Password || ''\n\n let UserRegistry = global.get('UserRegistry') || Object.create(null)\n if ((UserId in UserRegistry) && (UserRegistry[UserId] != null)) {\n let UserSpecs = UserRegistry[UserId]\n if (UserSpecs.Password === Password) { // internal optimization\n return withAuthorizationOf(UserId,UserSpecs.Roles || [])\n }\n\n let PBKDF2Iterations = global.get('PBKDF2Iterations') || 100000\n crypto.pbkdf2(\n Password, Buffer.from(UserSpecs.Salt,'hex'), PBKDF2Iterations, 64, 'sha512',\n function (Error, computedHash) {\n if ((Error == null) && (computedHash.toString('hex') === UserSpecs.Hash)) {\n UserSpecs.Password = Password // speeds up future auth. requests\n return withAuthorizationOf(UserId,UserSpecs.Roles || [])\n } else {\n return withoutAuthorization()\n }\n }\n )\n } else {\n return withoutAuthorization()\n }\n\n function withAuthorizationOf (UserName, UserRoles) {\n if ((msg.requiredRole == null) || (UserRoles.indexOf(msg.requiredRole) >= 0)) {\n msg.authenticatedUser = UserId\n msg.authorizedRoles = UserRoles\n\n node.send([msg,null])\n node.done()\n } else {\n return withoutAuthorization()\n }\n }\n\n function withoutAuthorization () {\n msg.payload = 'Unauthorized'\n msg.statusCode = 401\n\n node.send([null,msg])\n node.done()\n }\n", |
|
"outputs": 2, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [ |
|
{ |
|
"var": "crypto", |
|
"module": "crypto" |
|
} |
|
], |
|
"x": 150, |
|
"y": 420, |
|
"wires": [ |
|
[ |
|
"6e31b911deebaca2" |
|
], |
|
[ |
|
"d30b6243d1a61ddc" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "3ae11bc056bb4ea5", |
|
"type": "inject", |
|
"z": "1ab76f62fe829c73", |
|
"name": "at Startup", |
|
"props": [ |
|
{ |
|
"p": "payload" |
|
}, |
|
{ |
|
"p": "topic", |
|
"vt": "str" |
|
} |
|
], |
|
"repeat": "", |
|
"crontab": "", |
|
"once": true, |
|
"onceDelay": 0.1, |
|
"topic": "", |
|
"payloadType": "date", |
|
"x": 110, |
|
"y": 160, |
|
"wires": [ |
|
[ |
|
"02f5a2c430d381b9" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "02f5a2c430d381b9", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "generate Token Key", |
|
"func": " let TokenKey = global.get('TokenKey')\n if (TokenKey == null) { // do not change TokenKey upon Node-RED deployment\n global.set('TokenKey',crypto.randomBytes(16).toString('hex'))\n }\n return msg\n", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [ |
|
{ |
|
"var": "crypto", |
|
"module": "crypto" |
|
} |
|
], |
|
"x": 320, |
|
"y": 160, |
|
"wires": [ |
|
[ |
|
"08c6f38667b1f597" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "08c6f38667b1f597", |
|
"type": "debug", |
|
"z": "1ab76f62fe829c73", |
|
"name": "Status", |
|
"active": true, |
|
"tosidebar": false, |
|
"console": false, |
|
"tostatus": true, |
|
"complete": "true", |
|
"targetType": "full", |
|
"statusVal": "'token key generated'", |
|
"statusType": "jsonata", |
|
"x": 530, |
|
"y": 160, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "3165fc5071ea3432", |
|
"type": "inject", |
|
"z": "1ab76f62fe829c73", |
|
"name": "at Startup", |
|
"props": [ |
|
{ |
|
"p": "payload" |
|
}, |
|
{ |
|
"p": "topic", |
|
"vt": "str" |
|
} |
|
], |
|
"repeat": "", |
|
"crontab": "", |
|
"once": true, |
|
"onceDelay": 0.1, |
|
"topic": "", |
|
"payloadType": "date", |
|
"x": 110, |
|
"y": 100, |
|
"wires": [ |
|
[ |
|
"4c8bc88e9138d602" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "4c8bc88e9138d602", |
|
"type": "change", |
|
"z": "1ab76f62fe829c73", |
|
"name": "set token lifetime", |
|
"rules": [ |
|
{ |
|
"t": "set", |
|
"p": "TokenLifetime", |
|
"pt": "global", |
|
"to": "2*60*1000", |
|
"tot": "jsonata" |
|
}, |
|
{ |
|
"t": "set", |
|
"p": "payload", |
|
"pt": "msg", |
|
"to": "TokenLifetime", |
|
"tot": "global" |
|
} |
|
], |
|
"action": "", |
|
"property": "", |
|
"from": "", |
|
"to": "", |
|
"reg": false, |
|
"x": 310, |
|
"y": 100, |
|
"wires": [ |
|
[ |
|
"59d1b07f62cd0149" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "59d1b07f62cd0149", |
|
"type": "debug", |
|
"z": "1ab76f62fe829c73", |
|
"name": "Status", |
|
"active": true, |
|
"tosidebar": false, |
|
"console": false, |
|
"tostatus": true, |
|
"complete": "true", |
|
"targetType": "full", |
|
"statusVal": "payload", |
|
"statusType": "msg", |
|
"x": 530, |
|
"y": 100, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "79785206c30e98c3", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "refresh token", |
|
"func": " let Expiration = Date.now() + global.get('TokenLifetime')\n let TokenContent = msg.authenticatedUser + ':' + Expiration\n\n let TokenKey = global.get('TokenKey')\n const HMAC = crypto.createHmac('sha256',TokenKey)\n HMAC.update(TokenContent)\n let Digest = HMAC.digest('hex')\n\n msg.headers = msg.headers || {}\n msg.headers['authorization'] = 'Bearer ' + TokenContent + ':' + Digest\n return msg\n", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [ |
|
{ |
|
"var": "crypto", |
|
"module": "crypto" |
|
} |
|
], |
|
"x": 330, |
|
"y": 240, |
|
"wires": [ |
|
[ |
|
"2006e13c1c6ae4a2" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "6e31b911deebaca2", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "create token", |
|
"func": " let Expiration = Date.now() + global.get('TokenLifetime')\n let TokenContent = msg.authenticatedUser + ':' + Expiration\n\n let TokenKey = global.get('TokenKey')\n const HMAC = crypto.createHmac('sha256',TokenKey)\n HMAC.update(TokenContent)\n let Digest = HMAC.digest('hex')\n\n msg.headers = msg.headers || {}\n msg.headers['authorization'] = 'Bearer ' + TokenContent + ':' + Digest\n return msg\n", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [ |
|
{ |
|
"var": "crypto", |
|
"module": "crypto" |
|
} |
|
], |
|
"x": 350, |
|
"y": 360, |
|
"wires": [ |
|
[ |
|
"71fe29a8a838e801" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "5a6436e0f351d31c", |
|
"type": "reusable-in", |
|
"z": "1ab76f62fe829c73", |
|
"name": "header auth", |
|
"info": "describe your reusable flow here", |
|
"scope": "global", |
|
"x": 90, |
|
"y": 240, |
|
"wires": [ |
|
[ |
|
"8d77be6450ce0f2a" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "53a1975a92fb3b0d", |
|
"type": "reusable-in", |
|
"z": "1ab76f62fe829c73", |
|
"name": "header login", |
|
"info": "describe your reusable flow here", |
|
"scope": "global", |
|
"x": 90, |
|
"y": 360, |
|
"wires": [ |
|
[ |
|
"b21258d4f3b19a31" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "2006e13c1c6ae4a2", |
|
"type": "reusable-out", |
|
"z": "1ab76f62fe829c73", |
|
"name": "authorized", |
|
"position": 1, |
|
"x": 510, |
|
"y": 240, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "478efb8ea02f84dd", |
|
"type": "reusable-out", |
|
"z": "1ab76f62fe829c73", |
|
"name": "unauthorized", |
|
"position": "2", |
|
"x": 510, |
|
"y": 280, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "71fe29a8a838e801", |
|
"type": "reusable-out", |
|
"z": "1ab76f62fe829c73", |
|
"name": "success", |
|
"position": 1, |
|
"x": 520, |
|
"y": 360, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "d30b6243d1a61ddc", |
|
"type": "reusable-out", |
|
"z": "1ab76f62fe829c73", |
|
"name": "failure", |
|
"position": "2", |
|
"x": 530, |
|
"y": 400, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "26648eb5bf6204e9", |
|
"type": "comment", |
|
"z": "1ab76f62fe829c73", |
|
"name": "try yourself", |
|
"info": "", |
|
"x": 80, |
|
"y": 500, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "b658a6de0857907b", |
|
"type": "http in", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"url": "header-auth", |
|
"method": "get", |
|
"upload": false, |
|
"swaggerDoc": "", |
|
"x": 110, |
|
"y": 560, |
|
"wires": [ |
|
[ |
|
"dc105a4977025043" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "ecd50a9259a2cfde", |
|
"type": "http response", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"statusCode": "", |
|
"headers": {}, |
|
"x": 530, |
|
"y": 620, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "af1aef5eff24af57", |
|
"type": "change", |
|
"z": "1ab76f62fe829c73", |
|
"name": "inform about success", |
|
"rules": [ |
|
{ |
|
"t": "set", |
|
"p": "payload", |
|
"pt": "msg", |
|
"to": "successfully authorized", |
|
"tot": "str" |
|
} |
|
], |
|
"action": "", |
|
"property": "", |
|
"from": "", |
|
"to": "", |
|
"reg": false, |
|
"x": 340, |
|
"y": 620, |
|
"wires": [ |
|
[ |
|
"ecd50a9259a2cfde" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "d44680247d011e9b", |
|
"type": "http in", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"url": "header-auth", |
|
"method": "post", |
|
"upload": false, |
|
"swaggerDoc": "", |
|
"x": 110, |
|
"y": 680, |
|
"wires": [ |
|
[ |
|
"2f655d899fbe9841" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "8c7ae8b543f3576a", |
|
"type": "change", |
|
"z": "1ab76f62fe829c73", |
|
"name": "inform about success", |
|
"rules": [ |
|
{ |
|
"t": "set", |
|
"p": "payload", |
|
"pt": "msg", |
|
"to": "successfully authorized", |
|
"tot": "str" |
|
} |
|
], |
|
"action": "", |
|
"property": "", |
|
"from": "", |
|
"to": "", |
|
"reg": false, |
|
"x": 340, |
|
"y": 740, |
|
"wires": [ |
|
[ |
|
"d405c4344cb50dcb" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "d405c4344cb50dcb", |
|
"type": "http response", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"statusCode": "", |
|
"headers": {}, |
|
"x": 530, |
|
"y": 740, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "dc105a4977025043", |
|
"type": "reusable", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"target": "header auth", |
|
"outputs": 2, |
|
"x": 310, |
|
"y": 560, |
|
"wires": [ |
|
[ |
|
"af1aef5eff24af57" |
|
], |
|
[ |
|
"ecd50a9259a2cfde" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "2f655d899fbe9841", |
|
"type": "reusable", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"target": "header login", |
|
"outputs": 2, |
|
"x": 310, |
|
"y": 680, |
|
"wires": [ |
|
[ |
|
"8c7ae8b543f3576a" |
|
], |
|
[ |
|
"d405c4344cb50dcb" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "a437ea9e9983f2b9", |
|
"type": "comment", |
|
"z": "1ab76f62fe829c73", |
|
"name": "if used without \"node-red-within-express\"", |
|
"info": "", |
|
"x": 180, |
|
"y": 820, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "ed50b5b2e94746c8", |
|
"type": "inject", |
|
"z": "1ab76f62fe829c73", |
|
"name": "at Startup", |
|
"props": [ |
|
{ |
|
"p": "payload" |
|
}, |
|
{ |
|
"p": "topic", |
|
"vt": "str" |
|
} |
|
], |
|
"repeat": "", |
|
"crontab": "", |
|
"once": false, |
|
"onceDelay": 0.1, |
|
"topic": "", |
|
"payloadType": "date", |
|
"x": 100, |
|
"y": 880, |
|
"wires": [ |
|
[ |
|
"4a4c700ae4ecd934" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "2d98c64a67e881e7", |
|
"type": "file in", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"filename": "./registeredUsers.json", |
|
"format": "utf8", |
|
"chunk": false, |
|
"sendError": false, |
|
"encoding": "utf8", |
|
"allProps": false, |
|
"x": 160, |
|
"y": 1000, |
|
"wires": [ |
|
[ |
|
"70c82a6e25251a70" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "470b387186d7852a", |
|
"type": "catch", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"scope": [ |
|
"2d98c64a67e881e7", |
|
"70c82a6e25251a70" |
|
], |
|
"uncaught": false, |
|
"x": 110, |
|
"y": 1060, |
|
"wires": [ |
|
[ |
|
"46809118b7d27252" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "a52bccab8bc183a8", |
|
"type": "debug", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"active": true, |
|
"tosidebar": true, |
|
"console": false, |
|
"tostatus": true, |
|
"complete": "true", |
|
"targetType": "full", |
|
"statusVal": "'could not load user registry'", |
|
"statusType": "jsonata", |
|
"x": 510, |
|
"y": 1060, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "3eb566d1907d9b3b", |
|
"type": "debug", |
|
"z": "1ab76f62fe829c73", |
|
"name": "Status", |
|
"active": true, |
|
"tosidebar": false, |
|
"console": false, |
|
"tostatus": true, |
|
"complete": "true", |
|
"targetType": "full", |
|
"statusVal": "'user registry available'", |
|
"statusType": "jsonata", |
|
"x": 530, |
|
"y": 880, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "46809118b7d27252", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "create in global context", |
|
"func": " let UserRegistry = Object.create(null)\n UserRegistry['node-red'] = {\n Roles: ['node-red'],\n Salt: '4486e8d35b8275020b1301226cc77963',\n Hash: 'ab2b740ea9148aa4f320af3f3ba60ee2e33bb8039c57eea2b29579ff3f3b16bec2401f19e3c6ed8ad36de432b80b6f973a12c41af5d50738e4bb902d0117df53'\n }\n global.set('UserRegistry',UserRegistry)\n\n return msg\n", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [], |
|
"x": 310, |
|
"y": 1060, |
|
"wires": [ |
|
[ |
|
"a52bccab8bc183a8", |
|
"e0b359a7b5f90189" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "a463788d4c5c13ab", |
|
"type": "file", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"filename": "./registeredUsers.json", |
|
"appendNewline": false, |
|
"createDir": false, |
|
"overwriteFile": "true", |
|
"encoding": "utf8", |
|
"x": 420, |
|
"y": 1180, |
|
"wires": [ |
|
[ |
|
"53948a7e0233c789" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "0892a7393f0e2863", |
|
"type": "catch", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"scope": [ |
|
"a463788d4c5c13ab" |
|
], |
|
"uncaught": false, |
|
"x": 110, |
|
"y": 1300, |
|
"wires": [ |
|
[ |
|
"e50a7cafe871861b", |
|
"d97e0ea28a9f3c81" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "e50a7cafe871861b", |
|
"type": "debug", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"active": true, |
|
"tosidebar": true, |
|
"console": false, |
|
"tostatus": true, |
|
"complete": "true", |
|
"targetType": "full", |
|
"statusVal": "'could not write user registry'", |
|
"statusType": "jsonata", |
|
"x": 250, |
|
"y": 1340, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "53948a7e0233c789", |
|
"type": "change", |
|
"z": "1ab76f62fe829c73", |
|
"name": "restore payload", |
|
"rules": [ |
|
{ |
|
"t": "set", |
|
"p": "payload", |
|
"pt": "msg", |
|
"to": "_payload", |
|
"tot": "msg" |
|
}, |
|
{ |
|
"t": "delete", |
|
"p": "_payload", |
|
"pt": "msg" |
|
} |
|
], |
|
"action": "", |
|
"property": "", |
|
"from": "", |
|
"to": "", |
|
"reg": false, |
|
"x": 180, |
|
"y": 1240, |
|
"wires": [ |
|
[ |
|
"1ce07b54225e17dd" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "d97e0ea28a9f3c81", |
|
"type": "change", |
|
"z": "1ab76f62fe829c73", |
|
"name": "report in payload", |
|
"rules": [ |
|
{ |
|
"t": "set", |
|
"p": "payload", |
|
"pt": "msg", |
|
"to": "'Internal Server Error'", |
|
"tot": "jsonata" |
|
}, |
|
{ |
|
"t": "set", |
|
"p": "statusCode", |
|
"pt": "msg", |
|
"to": "500", |
|
"tot": "str" |
|
}, |
|
{ |
|
"t": "delete", |
|
"p": "_payload", |
|
"pt": "msg" |
|
} |
|
], |
|
"action": "", |
|
"property": "", |
|
"from": "", |
|
"to": "", |
|
"reg": false, |
|
"x": 290, |
|
"y": 1300, |
|
"wires": [ |
|
[ |
|
"1ce07b54225e17dd" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "70c82a6e25251a70", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "write to global context", |
|
"func": " let UserSet = JSON.parse(msg.payload) // may fail!\n\n let UserRegistry = Object.create(null)\n for (let UserId in UserSet) {\n if (UserSet.hasOwnProperty(UserId)) {\n if ((UserId.indexOf('/') >= 0) || (UserId.indexOf(':') >= 0)) {\n throw 'Invalid character in UserId found'\n }\n \n UserRegistry[UserId.toLowerCase()] = UserSet[UserId]\n }\n }\n global.set('UserRegistry',UserRegistry)\n\n msg.payload = ''\n return msg\n", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [], |
|
"x": 400, |
|
"y": 1000, |
|
"wires": [ |
|
[ |
|
"e0b359a7b5f90189" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "4f110197c9f2a334", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "→ catch", |
|
"func": "// do not pass any msg from here!", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [], |
|
"x": 360, |
|
"y": 940, |
|
"wires": [ |
|
[ |
|
"46809118b7d27252" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "687ebf9f0226b7dc", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "→ catch", |
|
"func": "// do not pass any msg from here!", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [], |
|
"x": 300, |
|
"y": 1120, |
|
"wires": [ |
|
[ |
|
"d97e0ea28a9f3c81" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "848c6ddfb76643e4", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "read from global context", |
|
"func": " let UserRegistry = global.get('UserRegistry')\n let UserSet = {}\n for (let UserId in UserRegistry) {\n if (UserRegistry[UserId] == null) {\n UserSet[UserId] = null\n } else {\n let UserEntry = Object.assign({},UserRegistry[UserId])\n delete UserEntry.Password // never write passwords in plain text!\n UserSet[UserId] = UserEntry\n }\n }\n\n msg.payload = JSON.stringify(UserSet)\n return msg\n", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [], |
|
"x": 170, |
|
"y": 1180, |
|
"wires": [ |
|
[ |
|
"a463788d4c5c13ab" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "e0b359a7b5f90189", |
|
"type": "reusable-out", |
|
"z": "1ab76f62fe829c73", |
|
"name": "return", |
|
"position": 1, |
|
"x": 530, |
|
"y": 940, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "1ce07b54225e17dd", |
|
"type": "reusable-out", |
|
"z": "1ab76f62fe829c73", |
|
"name": "return", |
|
"position": 1, |
|
"x": 530, |
|
"y": 1240, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "254d667c7cc38a4e", |
|
"type": "reusable-in", |
|
"z": "1ab76f62fe829c73", |
|
"name": "load or create UserRegistry", |
|
"info": "describe your reusable flow here", |
|
"scope": "global", |
|
"x": 140, |
|
"y": 940, |
|
"wires": [ |
|
[ |
|
"4f110197c9f2a334", |
|
"2d98c64a67e881e7" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "e50f6e5eb55f7d87", |
|
"type": "reusable-in", |
|
"z": "1ab76f62fe829c73", |
|
"name": "write UserRegistry", |
|
"info": "describe your reusable flow here", |
|
"scope": "global", |
|
"x": 110, |
|
"y": 1120, |
|
"wires": [ |
|
[ |
|
"687ebf9f0226b7dc", |
|
"848c6ddfb76643e4" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "4a4c700ae4ecd934", |
|
"type": "reusable", |
|
"z": "1ab76f62fe829c73", |
|
"name": "", |
|
"target": "load or create userregistry", |
|
"outputs": 1, |
|
"x": 320, |
|
"y": 880, |
|
"wires": [ |
|
[ |
|
"3eb566d1907d9b3b" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "fe9d58b1f84fce63", |
|
"type": "inject", |
|
"z": "1ab76f62fe829c73", |
|
"name": "show UserRegistry", |
|
"props": [ |
|
{ |
|
"p": "payload" |
|
}, |
|
{ |
|
"p": "topic", |
|
"vt": "str" |
|
} |
|
], |
|
"repeat": "", |
|
"crontab": "", |
|
"once": false, |
|
"onceDelay": 0.1, |
|
"topic": "", |
|
"payloadType": "date", |
|
"x": 130, |
|
"y": 1480, |
|
"wires": [ |
|
[ |
|
"663a2575d83ff39f" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "80fab4a14a0c61a7", |
|
"type": "comment", |
|
"z": "1ab76f62fe829c73", |
|
"name": "for testing purposes", |
|
"info": "", |
|
"x": 110, |
|
"y": 1420, |
|
"wires": [] |
|
}, |
|
{ |
|
"id": "663a2575d83ff39f", |
|
"type": "function", |
|
"z": "1ab76f62fe829c73", |
|
"name": "create output", |
|
"func": "let UserRegistry = global.get('UserRegistry') || Object.create(null)\n let UserList = []\n for (let UserId in UserRegistry) {\n UserList.push(\n UserRegistry[UserId] == null ? '[' + UserId + ']' : UserId\n )\n }\nmsg.payload = (\n UserList.length === 0\n ? '(no user registered)'\n : 'registered users: \"' + UserList.join('\",\"') + '\"'\n)\nreturn msg", |
|
"outputs": 1, |
|
"noerr": 0, |
|
"initialize": "", |
|
"finalize": "", |
|
"libs": [], |
|
"x": 340, |
|
"y": 1480, |
|
"wires": [ |
|
[ |
|
"62b2658722dbd9e7" |
|
] |
|
] |
|
}, |
|
{ |
|
"id": "62b2658722dbd9e7", |
|
"type": "debug", |
|
"z": "1ab76f62fe829c73", |
|
"name": "show", |
|
"active": true, |
|
"tosidebar": true, |
|
"console": false, |
|
"tostatus": false, |
|
"complete": "payload", |
|
"targetType": "msg", |
|
"statusVal": "", |
|
"statusType": "auto", |
|
"x": 510, |
|
"y": 1480, |
|
"wires": [] |
|
} |
|
] |