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 efd9452..88baf23 100644 Binary files a/images/flow.png and b/images/flow.png differ diff --git a/nodes/http-auth.html b/nodes/http-auth.html index cfa545b..8becc6a 100644 --- a/nodes/http-auth.html +++ b/nodes/http-auth.html @@ -3,7 +3,7 @@ RED.nodes.registerType('http-basic-auth', { category: 'function', inputs: 1, - outputs: 1, + outputs: 2, defaults: { name: { value: '' }, file: { value: '', type: 'http-basic-auth-file', required: false }, @@ -77,7 +77,7 @@

Config

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",