From 42e2cebd2cab494ff2dbfdc57163a08965ff86b3 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Thu, 29 Feb 2024 13:33:13 +0000 Subject: [PATCH 1/6] Start adding Token support --- lib/auth/httpAuthMiddleware.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/auth/httpAuthMiddleware.js b/lib/auth/httpAuthMiddleware.js index 378e5d0..5aaf19f 100644 --- a/lib/auth/httpAuthMiddleware.js +++ b/lib/auth/httpAuthMiddleware.js @@ -15,6 +15,14 @@ module.exports = { try { if (req.session.ffSession) { next() + } else if (req.get('Authorization')?.startsWith('Bearer')) { + // need to make a HTTP request against the forge platform to check + // the token. We should cache the token to reduce round trip on every + // request. + // We should include the Project ID and the path along with the token + // to be checked + console.log('Token auth') + next() } else { req.session.redirectTo = req.originalUrl passport.authenticate('FlowFuse', { session: false })(req, res, next) From 1b0cf9a375d86d45296b8aa6b001ffd7489c8389 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Fri, 1 Mar 2024 15:25:38 +0000 Subject: [PATCH 2/6] HTTP Node Auth token support --- lib/auth/httpAuthMiddleware.js | 72 +++++++++++++++++++++++++--------- lib/runtimeSettings.js | 3 +- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/lib/auth/httpAuthMiddleware.js b/lib/auth/httpAuthMiddleware.js index 5aaf19f..154d415 100644 --- a/lib/auth/httpAuthMiddleware.js +++ b/lib/auth/httpAuthMiddleware.js @@ -1,4 +1,5 @@ const crypto = require('crypto') +const got = require('got') const session = require('express-session') const MemoryStore = require('memorystore')(session) const { Passport } = require('passport') @@ -7,31 +8,53 @@ const { Strategy } = require('./strategy') let options let passport let httpNodeApp +let client module.exports = { init (_options) { options = _options - return (req, res, next) => { - try { - if (req.session.ffSession) { - next() - } else if (req.get('Authorization')?.startsWith('Bearer')) { - // need to make a HTTP request against the forge platform to check - // the token. We should cache the token to reduce round trip on every - // request. - // We should include the Project ID and the path along with the token - // to be checked - console.log('Token auth') - next() - } else { - req.session.redirectTo = req.originalUrl - passport.authenticate('FlowFuse', { session: false })(req, res, next) + return [ + async (req, res, next) => { + try { + if (req.session.ffSession) { + next() + } else if (req.get('Authorization')?.startsWith('Bearer')) { + // need to make a HTTP request against the forge platform to check + // the token. Should we cache the token to reduce round trip on every + // request? + // We should include the Project ID and the path along with the token + // to be checked + const token = req.get('Authorization').split(' ')[1] + const query = { + path: req.path + } + try { + const result = await client.get(options.projectId, { + headers: { + authorization: `Bearer ${token}` + }, + searchParams: query + }) + next() + } catch (err) { + console.log(err) + const error = new Error('Failed to check token') + error.status = 401 + next(error) + } + } else { + req.session.redirectTo = req.originalUrl + passport.authenticate('FlowFuse', { session: false })(req, res, next) + } + } catch (err) { + console.log(err.stack) + throw err } - } catch (err) { - console.log(err.stack) - throw err + }, + (err, req, res, next) => { + res.status(err.status).send() } - } + ] }, setupAuthRoutes (app) { @@ -97,5 +120,16 @@ module.exports = { res.redirect('/') } }) + + // need to decide on the path here + client = got.extend({ + prefixUrl: `${options.forgeURL}/account/check/http`, + headers: { + 'user-agent': 'FlowFuse HTTP Node Auth', + }, + timeout: { + request: 500 + } + }) } } diff --git a/lib/runtimeSettings.js b/lib/runtimeSettings.js index 1f56117..fd34dd9 100644 --- a/lib/runtimeSettings.js +++ b/lib/runtimeSettings.js @@ -44,7 +44,8 @@ function getSettingsFile (settings) { baseURL: '${settings.baseURL}', forgeURL: '${settings.forgeURL}', clientID: '${settings.clientID}', - clientSecret: '${settings.clientSecret}' + clientSecret: '${settings.clientSecret}', + projectId: '${settings.projectID}' })` projectSettings.httpNodeMiddleware = 'httpNodeMiddleware: flowforgeAuthMiddleware,' } From 62bb71257947512609fbb87d0b421e34d3bd1411 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 5 Mar 2024 13:33:21 +0000 Subject: [PATCH 3/6] Add token cache --- lib/auth/httpAuthMiddleware.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/auth/httpAuthMiddleware.js b/lib/auth/httpAuthMiddleware.js index 154d415..e9f3bd0 100644 --- a/lib/auth/httpAuthMiddleware.js +++ b/lib/auth/httpAuthMiddleware.js @@ -9,6 +9,7 @@ let options let passport let httpNodeApp let client +const httpTokenCache = {} module.exports = { init (_options) { @@ -25,6 +26,15 @@ module.exports = { // We should include the Project ID and the path along with the token // to be checked const token = req.get('Authorization').split(' ')[1] + const cacheHit = httpTokenCache[token] + if (cacheHit) { + const age = (Date.now() - cacheHit.age) / 1000 + if (age < 300) { + next() + return + } + delete httpTokenCache[token] + } const query = { path: req.path } @@ -35,9 +45,10 @@ module.exports = { }, searchParams: query }) + httpTokenCache[token] = { age: Date.now() } next() } catch (err) { - console.log(err) + // console.log(err) const error = new Error('Failed to check token') error.status = 401 next(error) From 647255748de48aa0189af76f360e32b28b788c78 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 5 Mar 2024 14:01:04 +0000 Subject: [PATCH 4/6] Fix tests --- test/unit/lib/runtimeSettings_spec.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/unit/lib/runtimeSettings_spec.js b/test/unit/lib/runtimeSettings_spec.js index f82dd77..c84cc44 100644 --- a/test/unit/lib/runtimeSettings_spec.js +++ b/test/unit/lib/runtimeSettings_spec.js @@ -260,12 +260,18 @@ describe('Runtime Settings', function () { const settings = await loadSettings(result) settings.should.not.have.property('httpNodeAuth') settings.should.have.property('httpNodeMiddleware') - ;(typeof settings.httpNodeMiddleware).should.equal('function') + settings.httpNodeMiddleware.should.be.an.Array() + ;(typeof settings.httpNodeMiddleware[0]).should.equal('function') + ;(typeof settings.httpNodeMiddleware[1]).should.equal('function') settings.should.have.property('ui') settings.ui.should.not.have.property('path') settings.ui.should.have.property('middleware') - ;(typeof settings.ui.middleware).should.equal('function') + console.log(settings.ui.middleware) + settings.ui.middleware.should.be.an.Array() + ;(typeof settings.ui.middleware[0]).should.equal('function') + ;(typeof settings.ui.middleware[1]).should.equal('function') } catch (err) { + console.log(err) // Temporary fix as this module will not be found when running in CI // until we publish the release of the new nr-auth module. err.toString().should.match(/Cannot find module '@flowfuse\/nr-auth\/middleware'/) @@ -283,7 +289,9 @@ describe('Runtime Settings', function () { settings.should.have.property('ui') settings.ui.should.have.property('path', '/foo') settings.ui.should.have.property('middleware') - ;(typeof settings.ui.middleware).should.equal('function') + settings.ui.middleware.should.be.an.Array() + ;(typeof settings.ui.middleware[0]).should.equal('function') + ;(typeof settings.ui.middleware[1]).should.equal('function') } catch (err) { // Temporary fix as this module will not be found when running in CI // until we publish the release of the new nr-auth module. From 34f0e59a5ca059ae7259e2ff4321a198ffcb8d33 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Mon, 11 Mar 2024 14:06:11 +0000 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> --- lib/auth/httpAuthMiddleware.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/auth/httpAuthMiddleware.js b/lib/auth/httpAuthMiddleware.js index e9f3bd0..f2d0642 100644 --- a/lib/auth/httpAuthMiddleware.js +++ b/lib/auth/httpAuthMiddleware.js @@ -26,7 +26,7 @@ module.exports = { // We should include the Project ID and the path along with the token // to be checked const token = req.get('Authorization').split(' ')[1] - const cacheHit = httpTokenCache[token] + const cacheHit = httpTokenCache[token] if (cacheHit) { const age = (Date.now() - cacheHit.age) / 1000 if (age < 300) { @@ -39,7 +39,7 @@ module.exports = { path: req.path } try { - const result = await client.get(options.projectId, { + await client.get(options.projectId, { headers: { authorization: `Bearer ${token}` }, @@ -136,7 +136,7 @@ module.exports = { client = got.extend({ prefixUrl: `${options.forgeURL}/account/check/http`, headers: { - 'user-agent': 'FlowFuse HTTP Node Auth', + 'user-agent': 'FlowFuse HTTP Node Auth' }, timeout: { request: 500 From eaa55706d62680c2716ed5e773fa045c996fbe07 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Mon, 11 Mar 2024 14:07:07 +0000 Subject: [PATCH 6/6] Fix comment --- lib/auth/httpAuthMiddleware.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/auth/httpAuthMiddleware.js b/lib/auth/httpAuthMiddleware.js index f2d0642..8bd5774 100644 --- a/lib/auth/httpAuthMiddleware.js +++ b/lib/auth/httpAuthMiddleware.js @@ -20,11 +20,8 @@ module.exports = { if (req.session.ffSession) { next() } else if (req.get('Authorization')?.startsWith('Bearer')) { - // need to make a HTTP request against the forge platform to check - // the token. Should we cache the token to reduce round trip on every - // request? // We should include the Project ID and the path along with the token - // to be checked + // to be checked to allow scoping tokens const token = req.get('Authorization').split(' ')[1] const cacheHit = httpTokenCache[token] if (cacheHit) {