From 42018e1174a65372c66b482f497647b940c049e7 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 13 Oct 2023 15:23:41 +0200 Subject: [PATCH] Two outputs (#3) * Two outputs To merge https://github.com/ttrushin/node-red-contrib-basicauth/ which is abandoned https://github.com/ttrushin/node-red-contrib-basicauth/issues/1 * Update node-red-contrib-mock-cli * Add msg.username * Add error messages * Update documentation * Fix realm * Readme outputs --- README.md | 31 ++++++++++++++++++++++++++++++- examples/flow.json | 32 ++++++++++++++++++++++++++------ images/flow.png | Bin 3895 -> 2522 bytes nodes/http-auth.html | 4 ++-- nodes/http-auth.js | 33 +++++++++++++++++++++++++++------ package-lock.json | 16 ++++++++-------- package.json | 4 ++-- 7 files changed, 95 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 2f14c04..763fd54 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ curl 'https://test:test@nodered.example.net/basic-auth-demo' There are three types of configuration: -1. *Simple*: each node has it’s own credentials. (one credential) +1. *Simple*: each node has its own credentials. (one credential) 2. *Multiple credentials*: credentials shared with multiple nodes. (multiple credentials) 3. *File with multiple credentials*: the user credentials are stored in a file. (multiple credentials) @@ -57,6 +57,35 @@ user1:test user2:$2y$10$5TSZDldoJ7MxDZdtK/SG2O3cwORqLDhHabYlKX9OsM.W/Z/oLwKW6 ``` +## Outputs + +The first node output is used when the authentication succeeded, and it contains the username: + +```json +"msg": { + "realm": "node-red", + "username": "alice", + "req": "...", + "res": "...", + "...": "..." +} +``` + +The second node output is used when the authentication failed, and it contains error information: + +```json +"msg": { + "realm": "node-red", + "username": "", + "authError": "Unknown user 'test'", + "req": "...", + "res": "...", + "...": "..." +} +``` + +Both outputs contain the `req` object, which can be inspected for detailed information about HTTP request headers, IP address, URL, etc. + ## Hints Here are examples to create hashed passwords: diff --git a/examples/flow.json b/examples/flow.json index 033ba30..ad0a2bb 100644 --- a/examples/flow.json +++ b/examples/flow.json @@ -5,15 +5,18 @@ "z": "d9a661f4.ef966", "name": "", "file": "", - "cred": "", + "multiple": "", "realm": "node-red", "username": "test", "password": "$2y$10$5TSZDldoJ7MxDZdtK/SG2O3cwORqLDhHabYlKX9OsM.W/Z/oLwKW6", - "x": 1040, + "x": 1030, "y": 100, "wires": [ [ "6ef8ccf5965075f1" + ], + [ + "a230b772edd4ea9c" ] ] }, @@ -26,7 +29,7 @@ "method": "get", "upload": false, "swaggerDoc": "", - "x": 810, + "x": 790, "y": 100, "wires": [ [ @@ -42,7 +45,7 @@ "statusCode": "", "headers": {}, "x": 1370, - "y": 100, + "y": 80, "wires": [] }, { @@ -56,12 +59,29 @@ "syntax": "plain", "template": "

\nHello world!\n

\n", "output": "str", - "x": 1220, - "y": 100, + "x": 1240, + "y": 80, "wires": [ [ "57b04097f0b0647d" ] ] + }, + { + "id": "a230b772edd4ea9c", + "type": "debug", + "z": "d9a661f4.ef966", + "name": "Log error", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 1240, + "y": 120, + "wires": [] } ] diff --git a/images/flow.png b/images/flow.png index efd94523e7acf9e45667a82b2127aa683866440a..88baf23cbb3ac1bbd89bd016fae84092317b520c 100644 GIT binary patch literal 2522 zcmV<02_^Q4P)2yu7J1Gc%f?liS+ayu6jgp>o7*rQnd-TO4}IvMRTgbP(kU*hq|;qNbW(UwZYD1p(vdvs<#orsei+`bYCBM^n;awwbor z^D4I_Z{*WI1)hB9GfMG{AbKW$Zr!*_*(>=s)`L#|xE5^2L!+L_ zPtO@w`Oqu*H|K&*KCF=!o|d~HALiscC;zoCE9OD^{cG5K%bIQR`MekzIl0E6eFCxxSGifFYhJ|xJ-Q)D@%{L|eTGfJs61xe$=4D!81vC=qGpi4A_<)Q z@%c(+RU^MHK5Zlar!4y$~ zpLx*4|0(%pAU~zRm~S$b%EO|KPCjLlhxs)WN=pHbHlrB(JAygGGFj!eL7qgLKS{LN zb|L?$8Fs^nHU*9R2#opAQ-Q7?{7|_o*s@AH`I((*E!vP7c3YlWrk$R$N{zfZ!=6Z= z5J)!fAm6DZ=|O&ESzNl|o1L{(y^~`lH5RBl)Qp=T%?_zR#LHSqLe5_ z)smi7UP-4fk`=6j{E|&8%=E!n^55O?(cUxszED|cvwBc$uX^xP>%tAJsLb5v0;H4< z^2cu3D#%jrEwaFnzj3|aE={|1-SE3q5!$Sn6}zsO*~wod^)i-xCf#=om+j=oZlwJq zFTH+I2UYpJ-THj5$*1o}oa94y`j6qHU?czBbsx#&-21LRzs|Ab$HxY!Ngdzxf&99S z{PQ39NB-pE{rmHCG|I0!>hrIO;^4@K{UAR#F!Go6{QKKVWx=6<%P)@%P5}=yN}+G$ z`|DCSveH}}y+(qLW7kIqJDse%K0K4R?fM8aN}*rm`|Q$2p43??g4<3fd2l@T}i&E&26c4pSA98YuEItes^xfZYxw}lY;V#k%`NeC(@a<>~ zGfE*m-C0F{;_-bokMG{n(^Giq1^a6w6;Wzc_gAkixUDLv!=+tCdxX0^so-!>ZQViR z{7~-V4l_z2A)hj_#(?}+78I-W21mAN~en`oiN+Bg5-=D3;_d!x5}K)z;lxU6A^lf0=EQu1F)iP|rCtW)w6L{>~mvFsWI-YS_@;ZP%Aj%D$CASt zM;N7$l5a|}nu^d0C7&o0aVv_laa4{JzSM#-(#dZCc|2CD353v{Jdnqa`TLRN8JBkb z(#;)08u@}o9^X%3&|hhO%b0u_8qi@S|K5;(9yZJkh#0S06#XMxGKTU!z0dafvoorO=Ev)s#;D7kK+) z0uy*>$L|#A>w?2*dBPa;B--S4@@%wWXIR#yUB7g?!;IH(d|9)TH=<3RrdefZ#H`U~ z$Y)rqQmD_c(OSF~D^T(e$&|O2n$4@y>}G+^o^Xg+8RW^twkuUP3Hf-q|`kpD{pSuug~D+#osr+IJ~ zK`YrrNh2R8*hObdB@E8&rkhd2hki(4wh{t{{QBhClYM&}*m0dEdt&Jpqmp*qkf zUAu?I`K$bUiM@XM-Xy;IP k@D?Y}$@3N`{{)f$A8)L6C3l$^{{R3007*qoM6N<$g77F1OaK4? literal 3895 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

There are three types of configuration:

    -
  1. Simple: each node has it’s own credentials. (one credential)
  2. +
  3. Simple: each node has its own credentials. (one credential)
  4. Multiple credentials: credentials shared with multiple nodes. (multiple credentials)
  5. File with multiple credentials: the user credentials are stored in a file. (multiple credentials)
diff --git a/nodes/http-auth.js b/nodes/http-auth.js index e820737..8c59c8e 100644 --- a/nodes/http-auth.js +++ b/nodes/http-auth.js @@ -25,20 +25,36 @@ function basicAuth(authStr, node, msg) { const values = Buffer.from(authStr, 'base64').toString().split(':'); const username = values[0]; const password = values[1]; + + if (username == '' || username == '') { + msg.authError = 'Invalid format for credentials!'; + unAuth(node, msg); + return; + } + const user = node.httpauthconf.getUser(username); - if (user !== null && passwordCompare(password, user.password)) { - node.send(msg); - } else { + if (user === null) { + msg.authError = `Unknown user '${username}'!`; unAuth(node, msg); + return; } + + if (!passwordCompare(password, user.password)) { + msg.authError = `Invalid credentials for user '${username}'!`; + unAuth(node, msg); + return; + } + + msg.username = user.username; + node.send([msg, null]); } -function unAuth(node, msg, stale) { +function unAuth(node, msg) { const res = msg.res._res || msg.res; // Resolves deprecates warning messages. res.set('WWW-Authenticate', 'Basic realm="' + node.httpauthconf.realm + '"'); - res.type('text/plain'); - res.status(401).send('401 Unauthorized'); + res.sendStatus(401); + node.send([null, msg]); } module.exports = function (RED) { @@ -76,10 +92,14 @@ module.exports = function (RED) { this.httpauthconf = {}; this.httpauthconf.src = src; this.httpauthconf.getUser = getUser; + this.httpauthconf.realm = config.realm; const node = this; this.on('input', function (msg) { + msg.realm = node.httpauthconf.realm; + msg.username = ''; + const header = msg.req.get('Authorization'); const authType = header ? header.match(/^\w+\b/)[0] : null; @@ -87,6 +107,7 @@ module.exports = function (RED) { const authStr = header.substring(authType.length).trim(); basicAuth(authStr, node, msg); } else { + msg.authError = 'Missing Basic Auth headers!'; unAuth(node, msg); } }); diff --git a/package-lock.json b/package-lock.json index 6c434bd..ba0b657 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,9 @@ "eslint-config-standard": "^17.1.0", "eslint-plugin-html": "^7.1.0", "eslint-plugin-import": "^2.28.1", - "eslint-plugin-n": "^16.1.0", + "eslint-plugin-n": "^16.2.0", "eslint-plugin-promise": "^6.1.1", - "node-red-contrib-mock-cli": "^1.4.0" + "node-red-contrib-mock-cli": "^1.4.1" }, "engines": { "node": ">=12" @@ -578,9 +578,9 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", "dev": true, "dependencies": { "get-intrinsic": "^1.2.1", @@ -2100,9 +2100,9 @@ } }, "node_modules/node-red-contrib-mock-cli": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-red-contrib-mock-cli/-/node-red-contrib-mock-cli-1.4.0.tgz", - "integrity": "sha512-z5HE5hTZiZ1lcariugJWhhF2YJpTSnIYWjB6PK/WO1fgtx8iumRw8LHrW2wLoD0QB0CA/laaAymSwZVLjRxBmQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/node-red-contrib-mock-cli/-/node-red-contrib-mock-cli-1.4.1.tgz", + "integrity": "sha512-O6eP5Iz1YRr6CC8ul/6hIS9Heo0RFNSHrmpWIOUn8CHnHX4NMStmdG8Iz8Sn4KlKIvWqynHhfiOl+GEzcTzmtg==", "dev": true, "engines": { "node": ">=8" diff --git a/package.json b/package.json index 9efcbb4..df4b9f8 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,9 @@ "eslint-config-standard": "^17.1.0", "eslint-plugin-html": "^7.1.0", "eslint-plugin-import": "^2.28.1", - "eslint-plugin-n": "^16.1.0", + "eslint-plugin-n": "^16.2.0", "eslint-plugin-promise": "^6.1.1", - "node-red-contrib-mock-cli": "^1.4.0" + "node-red-contrib-mock-cli": "^1.4.1" }, "scripts": { "start": "node ./index.js",