From 68c97986842ae01c22557d57753e149e5f84d024 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Sat, 8 Feb 2025 09:30:14 +0000 Subject: [PATCH 01/13] Update openid-client to v6 v6 is a complete rewrite, with API changes: https://github.com/panva/openid-client/releases/tag/v6.0.0 Closes #1230 --- lib/resources/oidc.js | 46 +++++++++++-------- lib/util/oidc.js | 37 ++++++--------- package-lock.json | 88 +++++++++++++++--------------------- package.json | 2 +- test/integration/api/oidc.js | 38 ++++++++++++++++ 5 files changed, 119 insertions(+), 92 deletions(-) create mode 100644 test/integration/api/oidc.js diff --git a/lib/resources/oidc.js b/lib/resources/oidc.js index f216a1056..be8d174c7 100644 --- a/lib/resources/oidc.js +++ b/lib/resources/oidc.js @@ -13,7 +13,14 @@ // OpenID Connect auth handling using Authorization Code Flow with PKCE. // TODO document _why_ auth-code-flow, and not e.g. implicit flow? -const { generators } = require('openid-client'); +const { // eslint-disable-line object-curly-newline + authorizationCodeGrant, + buildAuthorizationUrl, + calculatePKCECodeChallenge, + fetchUserInfo, + randomPKCECodeVerifier, + randomState, +} = require('openid-client'); // eslint-disable-line object-curly-newline const config = require('config'); const { parse, render } = require('mustache'); @@ -22,9 +29,8 @@ const { redirect } = require('../util/http'); const { createUserSession } = require('../http/sessions'); const { // eslint-disable-line object-curly-newline CODE_CHALLENGE_METHOD, - RESPONSE_TYPE, SCOPES, - getClient, + getOidcConfig, getRedirectUri, isEnabled, } = require('../util/oidc'); // eslint-disable-line camelcase,object-curly-newline @@ -95,27 +101,30 @@ const loaderTemplate = ` `; parse(loaderTemplate); // caches template for future perf. -const stateFor = next => [ generators.state(), Buffer.from(next).toString('base64url') ].join(':'); -const nextFrom = state => Buffer.from(state.split(':')[1], 'base64url').toString(); +const stateFor = next => [ randomState(), Buffer.from(next).toString('base64url') ].join(':'); +const nextFrom = state => { + if (state) return Buffer.from(state.split(':')[1], 'base64url').toString(); +}; module.exports = (service, endpoint) => { if (!isEnabled()) return; service.get('/oidc/login', endpoint.html(async ({ Sentry }, _, req, res) => { try { - const client = await getClient(); - const code_verifier = generators.codeVerifier(); // eslint-disable-line camelcase + const oidcConfig = await getOidcConfig(); + const code_verifier = randomPKCECodeVerifier(); // eslint-disable-line camelcase - const code_challenge = generators.codeChallenge(code_verifier); // eslint-disable-line camelcase + const code_challenge = await calculatePKCECodeChallenge(code_verifier); // eslint-disable-line camelcase const next = req.query.next ?? ''; const state = stateFor(next); - const authUrl = client.authorizationUrl({ + const authUrl = buildAuthorizationUrl(oidcConfig, { scope: SCOPES.join(' '), - resource: `${envDomain}/v1`, + resource: `${envDomain}/v1`, // TODO is this still available? code_challenge, code_challenge_method: CODE_CHALLENGE_METHOD, + redirect_uri: getRedirectUri(), state, }); @@ -135,20 +144,21 @@ module.exports = (service, endpoint) => { service.get('/oidc/callback', endpoint.html(async (container, _, req, res) => { try { - const code_verifier = req.cookies[CODE_VERIFIER_COOKIE]; // eslint-disable-line camelcase - const state = req.cookies[STATE_COOKIE]; // eslint-disable-line no-multi-spaces + const pkceCodeVerifier = req.cookies[CODE_VERIFIER_COOKIE]; + const expectedState = req.cookies[STATE_COOKIE]; // eslint-disable-line no-multi-spaces res.clearCookie(CODE_VERIFIER_COOKIE, callbackCookieProps); res.clearCookie(STATE_COOKIE, callbackCookieProps); // eslint-disable-line no-multi-spaces - const client = await getClient(); + const oidcConfig = await getOidcConfig(); - const params = client.callbackParams(req); + // N.B. use req.originalUrl in preference to req.url, as the latter is corrupted somewhere upstream. + const requestUrl = new URL(req.originalUrl, getRedirectUri()); - const tokenSet = await client.callback(getRedirectUri(), params, { response_type: RESPONSE_TYPE, code_verifier, state }); + const tokenSet = await authorizationCodeGrant(oidcConfig, requestUrl, { pkceCodeVerifier, expectedState }); const { access_token } = tokenSet; - - const userinfo = await client.userinfo(access_token); + const expectedSubject = tokenSet.claims().sub; + const userinfo = await fetchUserInfo(oidcConfig, access_token, expectedSubject); const { email, email_verified } = userinfo; if (!email) { @@ -162,7 +172,7 @@ module.exports = (service, endpoint) => { await initSession(container, req, res, user); - const nextPath = safeNextPathFrom(nextFrom(state)); + const nextPath = safeNextPathFrom(nextFrom(expectedState)); // This redirect would be ideal, but breaks `SameSite: Secure` cookies. // return redirect(303, nextPath); diff --git a/lib/util/oidc.js b/lib/util/oidc.js index ce2de861a..f6af9c699 100644 --- a/lib/util/oidc.js +++ b/lib/util/oidc.js @@ -27,13 +27,13 @@ module.exports = { CODE_CHALLENGE_METHOD, RESPONSE_TYPE, SCOPES, - getClient, + getOidcConfig, getRedirectUri, isEnabled, }; const config = require('config'); -const { Issuer } = require('openid-client'); +const { allowInsecureRequests, discovery } = require('openid-client'); const oidcConfig = (config.has('default.oidc') && config.get('default.oidc')) || {}; @@ -48,19 +48,21 @@ function getRedirectUri() { return `${config.get('default.env.domain')}/v1/oidc/callback`; } -let clientLoader; // single instance, initialised lazily -function getClient() { - if (!clientLoader) clientLoader = initClient(); - return clientLoader; +let configLoader; // single instance, initialised lazily +function getOidcConfig() { + if (!configLoader) configLoader = initConfig(); + return configLoader; } -async function initClient() { +async function initConfig() { if (!isEnabled()) throw new Error('OIDC is not enabled.'); try { assertHasAll('config keys', Object.keys(oidcConfig), ['issuerUrl', 'clientId', 'clientSecret']); - const { issuerUrl } = oidcConfig; - const issuer = await Issuer.discover(issuerUrl); + const { issuerUrl, clientId, clientSecret } = oidcConfig; + const _issuerUrl = new URL(issuerUrl); + const execute = _issuerUrl.hostname === 'localhost' ? [ allowInsecureRequests ] : undefined; + const discoveredConfig = await discovery(_issuerUrl, clientId, clientSecret, undefined, { execute }); // eslint-disable-next-line object-curly-newline const { @@ -70,7 +72,7 @@ async function initClient() { response_types_supported, scopes_supported, token_endpoint_auth_methods_supported, - } = issuer.metadata; // eslint-disable-line object-curly-newline + } = discoveredConfig.serverMetadata(); // eslint-disable-line object-curly-newline // This code uses email to verify a user's identity. An unverified email // address is not suitable for verification. @@ -106,19 +108,10 @@ async function initClient() { assertHas('token signing alg', id_token_signing_alg_values_supported, TOKEN_SIGNING_ALG); assertHas('token endpoint auth method', token_endpoint_auth_methods_supported, TOKEN_ENDPOINT_AUTH_METHOD); - const client = new issuer.Client({ - client_id: oidcConfig.clientId, - client_secret: oidcConfig.clientSecret, - redirect_uris: [getRedirectUri()], - response_types: [RESPONSE_TYPE], - id_token_signed_response_alg: TOKEN_SIGNING_ALG, - token_endpoint_auth_method: TOKEN_ENDPOINT_AUTH_METHOD, - }); - - return client; - } catch (err) { + return discoveredConfig; + } catch (cause) { // N.B. don't include the config here - it might include the client secret, perhaps in the wrong place. - throw new Error(`Failed to configure OpenID Connect client: ${err}`); + throw new Error(`Failed to configure OpenID Connect client`, { cause }); } } diff --git a/package-lock.json b/package-lock.json index bd02f8ac8..4967be21a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "mustache": "~2.3", "nodemailer": "~6", "odata-v4-parser": "~0.1", - "openid-client": "^5.4.3", + "openid-client": "^6.1.7", "path-to-regexp": "^6.2.2", "pg": "~8", "pg-query-stream": "~4", @@ -6222,9 +6222,10 @@ } }, "node_modules/jose": { - "version": "4.15.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", - "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -7742,6 +7743,15 @@ "node": ">=6" } }, + "node_modules/oauth4webapi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", + "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7828,14 +7838,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -8003,15 +8005,6 @@ } } }, - "node_modules/oidc-provider/node_modules/jose": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.2.tgz", - "integrity": "sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/oidc-provider/node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -8061,6 +8054,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "dev": true, "engines": { "node": "^10.13.0 || >=12.0.0" } @@ -8099,14 +8093,13 @@ "dev": true }, "node_modules/openid-client": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz", - "integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", + "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", + "license": "MIT", "dependencies": { - "jose": "^4.14.4", - "lru-cache": "^6.0.0", - "object-hash": "^2.2.0", - "oidc-token-hash": "^5.0.3" + "jose": "^5.9.6", + "oauth4webapi": "^3.1.4" }, "funding": { "url": "https://github.com/sponsors/panva" @@ -16237,9 +16230,9 @@ } }, "jose": { - "version": "4.15.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", - "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==" + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==" }, "js-git": { "version": "0.7.8", @@ -17399,6 +17392,11 @@ } } }, + "oauth4webapi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", + "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -17465,11 +17463,6 @@ } } }, - "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" - }, "object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -17590,12 +17583,6 @@ "ms": "2.1.2" } }, - "jose": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.2.tgz", - "integrity": "sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==", - "dev": true - }, "jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -17625,7 +17612,8 @@ "oidc-token-hash": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", - "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==" + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "dev": true }, "on-finished": { "version": "2.3.0", @@ -17655,14 +17643,12 @@ "dev": true }, "openid-client": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz", - "integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==", - "requires": { - "jose": "^4.14.4", - "lru-cache": "^6.0.0", - "object-hash": "^2.2.0", - "oidc-token-hash": "^5.0.3" + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", + "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", + "requires": { + "jose": "^5.9.6", + "oauth4webapi": "^3.1.4" } }, "optionator": { diff --git a/package.json b/package.json index 5bb0feb5e..8ebe8d1c6 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "mustache": "~2.3", "nodemailer": "~6", "odata-v4-parser": "~0.1", - "openid-client": "^5.4.3", + "openid-client": "^6.1.7", "path-to-regexp": "^6.2.2", "pg": "~8", "pg-query-stream": "~4", diff --git a/test/integration/api/oidc.js b/test/integration/api/oidc.js new file mode 100644 index 000000000..76f88120b --- /dev/null +++ b/test/integration/api/oidc.js @@ -0,0 +1,38 @@ +const { testService } = require('../setup'); + +describe('api: /oidc/...', () => { + if (process.env.TEST_AUTH === 'oidc') { + describe('GET /oidc/login', () => { + it('should redirect to IdP if no parameters are provided', testService(service => + service.get('/v1/oidc/login') + .expect(307) + .then(({ text, headers }) => { + const expectedUrlPrefix = 'http://localhost:9898/auth?client_id=odk-central-backend-dev&scope=openid%20email&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8989%2Fv1%2Foidc%2Fcallback&resource=http%3A%2F%2Flocalhost%3A8989%2Fv1&code_challenge='; + text.should.startWith('Temporary Redirect. Redirecting to ' + expectedUrlPrefix); + headers.location.should.startWith(expectedUrlPrefix); + }))); + }); + + describe('GET /oidc/callback', () => { + it('should redirect to error page if no parameters are provided', testService(service => + service.get('/v1/oidc/callback') + .expect(303) + .then(({ text, headers }) => { + text.should.eql('See Other. Redirecting to http://localhost:8989/#/login?oidcError=internal-server-error'); + headers.location.should.eql('http://localhost:8989/#/login?oidcError=internal-server-error'); + }))); + }); + } else { // OIDC not enabled + describe('GET /oidc/login', () => { + it('should not exist', testService(service => + service.get('/v1/oidc/login') + .expect(404))); + }); + + describe('GET /oidc/callback', () => { + it('should not exist', testService(service => + service.get('/v1/oidc/callback') + .expect(404))); + }); + } +}); From a0314597246c9d202432f0241663eb2573618c41 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Tue, 11 Feb 2025 07:30:27 +0000 Subject: [PATCH 02/13] available -> required --- lib/resources/oidc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resources/oidc.js b/lib/resources/oidc.js index be8d174c7..85a8ba853 100644 --- a/lib/resources/oidc.js +++ b/lib/resources/oidc.js @@ -121,7 +121,7 @@ module.exports = (service, endpoint) => { const authUrl = buildAuthorizationUrl(oidcConfig, { scope: SCOPES.join(' '), - resource: `${envDomain}/v1`, // TODO is this still available? + resource: `${envDomain}/v1`, // TODO is this still required? code_challenge, code_challenge_method: CODE_CHALLENGE_METHOD, redirect_uri: getRedirectUri(), From 3096157b3e49dc0060a958f5513d8ef20d182272 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Tue, 11 Feb 2025 13:56:22 +0000 Subject: [PATCH 03/13] wip --- test/integration/api/oidc.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/integration/api/oidc.js b/test/integration/api/oidc.js index 76f88120b..0c7358232 100644 --- a/test/integration/api/oidc.js +++ b/test/integration/api/oidc.js @@ -7,9 +7,33 @@ describe('api: /oidc/...', () => { service.get('/v1/oidc/login') .expect(307) .then(({ text, headers }) => { - const expectedUrlPrefix = 'http://localhost:9898/auth?client_id=odk-central-backend-dev&scope=openid%20email&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8989%2Fv1%2Foidc%2Fcallback&resource=http%3A%2F%2Flocalhost%3A8989%2Fv1&code_challenge='; + const expectedUrlPrefix = 'http://localhost:9898/auth?'; text.should.startWith('Temporary Redirect. Redirecting to ' + expectedUrlPrefix); headers.location.should.startWith(expectedUrlPrefix); + + const url = new URL(headers.location); + url.searchParams.sort(); + + [ ...url.searchParams.keys() ].should.eql([ + 'client_id', + 'code_challenge', + 'code_challenge_method', + 'redirect_uri', + 'resource', + 'response_type', + 'scope', + 'state', + ]); + + url.searchParams.get('client_id').should.eql('odk-central-backend-dev'); + url.searchParams.get('code_challenge_method').should.eql('S256'); + url.searchParams.get('redirect_uri').should.eql('http://localhost:8989/v1/oidc/callback'); + url.searchParams.get('resource').should.eql('http://localhost:8989/v1'); + url.searchParams.get('response_type').should.eql('code'); + url.searchParams.get('scope').should.eql('openid email'); + + url.searchParams.get('code_challenge').should.match(/^[a-zA-Z0-9-_]{43}$/); + url.searchParams.get('state' ).should.match(/^[a-zA-Z0-9-_]{43}:$/); // eslint-disable-line space-in-parens,no-multi-spaces }))); }); From 955d26d757c5c00780013bdac0602432387fb011 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Tue, 11 Feb 2025 13:59:09 +0000 Subject: [PATCH 04/13] resolve TODO --- lib/resources/oidc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resources/oidc.js b/lib/resources/oidc.js index 85a8ba853..8f5e9c44e 100644 --- a/lib/resources/oidc.js +++ b/lib/resources/oidc.js @@ -121,7 +121,7 @@ module.exports = (service, endpoint) => { const authUrl = buildAuthorizationUrl(oidcConfig, { scope: SCOPES.join(' '), - resource: `${envDomain}/v1`, // TODO is this still required? + resource: `${envDomain}/v1`, code_challenge, code_challenge_method: CODE_CHALLENGE_METHOD, redirect_uri: getRedirectUri(), From d8849cbb175ca08fac997103fb0b31081593d499 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Wed, 12 Feb 2025 06:18:45 +0000 Subject: [PATCH 05/13] ?? --- test/integration/api/oidc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/api/oidc.js b/test/integration/api/oidc.js index d13f3e207..0c7358232 100644 --- a/test/integration/api/oidc.js +++ b/test/integration/api/oidc.js @@ -35,6 +35,7 @@ describe('api: /oidc/...', () => { url.searchParams.get('code_challenge').should.match(/^[a-zA-Z0-9-_]{43}$/); url.searchParams.get('state' ).should.match(/^[a-zA-Z0-9-_]{43}:$/); // eslint-disable-line space-in-parens,no-multi-spaces }))); + }); describe('GET /oidc/callback', () => { it('should redirect to error page if no parameters are provided', testService(service => From b9e195d83dc567a5dabc1796cbdba13a2bbb1fc7 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Wed, 12 Feb 2025 06:21:22 +0000 Subject: [PATCH 06/13] revert test changes --- test/integration/api/oidc.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/integration/api/oidc.js b/test/integration/api/oidc.js index 0c7358232..db8032e25 100644 --- a/test/integration/api/oidc.js +++ b/test/integration/api/oidc.js @@ -35,9 +35,7 @@ describe('api: /oidc/...', () => { url.searchParams.get('code_challenge').should.match(/^[a-zA-Z0-9-_]{43}$/); url.searchParams.get('state' ).should.match(/^[a-zA-Z0-9-_]{43}:$/); // eslint-disable-line space-in-parens,no-multi-spaces }))); - }); - describe('GET /oidc/callback', () => { it('should redirect to error page if no parameters are provided', testService(service => service.get('/v1/oidc/callback') .expect(303) @@ -51,9 +49,7 @@ describe('api: /oidc/...', () => { it('should not exist', testService(service => service.get('/v1/oidc/login') .expect(404))); - }); - describe('GET /oidc/callback', () => { it('should not exist', testService(service => service.get('/v1/oidc/callback') .expect(404))); From 1790465ba3309d1b2cb096b5ebe54b7640e3a6a5 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Wed, 12 Feb 2025 06:22:51 +0000 Subject: [PATCH 07/13] revert error handling change --- lib/util/oidc.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/oidc.js b/lib/util/oidc.js index f6af9c699..1ca468236 100644 --- a/lib/util/oidc.js +++ b/lib/util/oidc.js @@ -109,9 +109,9 @@ async function initConfig() { assertHas('token endpoint auth method', token_endpoint_auth_methods_supported, TOKEN_ENDPOINT_AUTH_METHOD); return discoveredConfig; - } catch (cause) { + } catch (err) { // N.B. don't include the config here - it might include the client secret, perhaps in the wrong place. - throw new Error(`Failed to configure OpenID Connect client`, { cause }); + throw new Error(`Failed to configure OpenID Connect client: ${err}`); } } From 97a524e868f807692b5e6493b361f84e08881b93 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Wed, 12 Feb 2025 09:11:16 +0000 Subject: [PATCH 08/13] rename tokens --- lib/resources/oidc.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/resources/oidc.js b/lib/resources/oidc.js index 8f5e9c44e..7b2785ac4 100644 --- a/lib/resources/oidc.js +++ b/lib/resources/oidc.js @@ -154,10 +154,10 @@ module.exports = (service, endpoint) => { // N.B. use req.originalUrl in preference to req.url, as the latter is corrupted somewhere upstream. const requestUrl = new URL(req.originalUrl, getRedirectUri()); - const tokenSet = await authorizationCodeGrant(oidcConfig, requestUrl, { pkceCodeVerifier, expectedState }); + const tokens = await authorizationCodeGrant(oidcConfig, requestUrl, { pkceCodeVerifier, expectedState }); - const { access_token } = tokenSet; - const expectedSubject = tokenSet.claims().sub; + const { access_token } = tokens; + const expectedSubject = tokens.claims().sub; const userinfo = await fetchUserInfo(oidcConfig, access_token, expectedSubject); const { email, email_verified } = userinfo; From 05bd36f0ec1429edbb8ecaca256bd6b5acd3a90c Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Wed, 26 Mar 2025 06:02:40 +0000 Subject: [PATCH 09/13] Update to latest openid-client release --- package-lock.json | 54 +++++++++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a63ff64c..22dbef958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "mustache": "~2.3", "nodemailer": "~6", "odata-v4-parser": "~0.1", - "openid-client": "^6.1.7", + "openid-client": "^6.3.4", "path-to-regexp": "^6.2.2", "pg": "~8", "pg-query-stream": "~4", @@ -6299,6 +6299,7 @@ "version": "5.9.6", "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -7827,9 +7828,9 @@ } }, "node_modules/oauth4webapi": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", - "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.3.1.tgz", + "integrity": "sha512-ZwX7UqYrP3Lr+Glhca3a1/nF2jqf7VVyJfhGuW5JtrfDUxt0u+IoBPzFjZ2dd7PJGkdM6CFPVVYzuDYKHv101A==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -8176,18 +8177,27 @@ "dev": true }, "node_modules/openid-client": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", - "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.3.4.tgz", + "integrity": "sha512-CGZGk9Y6Bv9R4bXlrzVoxzD1n4h8iP914UhjVyRSftqzqO4CWaRqKpOmW253Jmpv4EWkz7/Gut/90iiWW8t0ow==", "license": "MIT", "dependencies": { - "jose": "^5.9.6", - "oauth4webapi": "^3.1.4" + "jose": "^6.0.10", + "oauth4webapi": "^3.3.1" }, "funding": { "url": "https://github.com/sponsors/panva" } }, + "node_modules/openid-client/node_modules/jose": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", + "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -16361,7 +16371,8 @@ "jose": { "version": "5.9.6", "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", - "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==" + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "dev": true }, "js-git": { "version": "0.7.8", @@ -17527,9 +17538,9 @@ } }, "oauth4webapi": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", - "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.3.1.tgz", + "integrity": "sha512-ZwX7UqYrP3Lr+Glhca3a1/nF2jqf7VVyJfhGuW5JtrfDUxt0u+IoBPzFjZ2dd7PJGkdM6CFPVVYzuDYKHv101A==" }, "object-assign": { "version": "4.1.1", @@ -17777,12 +17788,19 @@ "dev": true }, "openid-client": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", - "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.3.4.tgz", + "integrity": "sha512-CGZGk9Y6Bv9R4bXlrzVoxzD1n4h8iP914UhjVyRSftqzqO4CWaRqKpOmW253Jmpv4EWkz7/Gut/90iiWW8t0ow==", "requires": { - "jose": "^5.9.6", - "oauth4webapi": "^3.1.4" + "jose": "^6.0.10", + "oauth4webapi": "^3.3.1" + }, + "dependencies": { + "jose": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", + "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==" + } } }, "optionator": { diff --git a/package.json b/package.json index d2475923e..e8f86f051 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "mustache": "~2.3", "nodemailer": "~6", "odata-v4-parser": "~0.1", - "openid-client": "^6.1.7", + "openid-client": "^6.3.4", "path-to-regexp": "^6.2.2", "pg": "~8", "pg-query-stream": "~4", From c504b0714b36b2c51873b902e9a91348b7530c47 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Mon, 14 Jul 2025 10:06:04 +0000 Subject: [PATCH 10/13] bump openid-client --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f120cedd..0a3d1486e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "mustache": "^4.2.0", "nodemailer": "^7.0.3", "odata-v4-parser": "~0.1", - "openid-client": "^6.3.4", + "openid-client": "^6.6.2", "path-to-regexp": "^8.2.0", "pg": "~8.8.0", "pg-query-stream": "~4", diff --git a/package.json b/package.json index d99ce5214..dfee19aa9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "mustache": "^4.2.0", "nodemailer": "^7.0.3", "odata-v4-parser": "~0.1", - "openid-client": "^6.3.4", + "openid-client": "^6.6.2", "path-to-regexp": "^8.2.0", "pg": "~8.8.0", "pg-query-stream": "~4", From 3e511417230098b761b7c055d27c884fbe777702 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Tue, 21 Oct 2025 10:58:08 +0000 Subject: [PATCH 11/13] Update to latest `openid-client` --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea8028b00..8c6990462 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "mustache": "^4.2.0", "nodemailer": "^7.0.3", "odata-v4-parser": "~0.1", - "openid-client": "^6.6.2", + "openid-client": "^6.8.1", "path-to-regexp": "^8.2.0", "pg": "~8.8.0", "pg-query-stream": "~4", @@ -10292,9 +10292,9 @@ } }, "node_modules/oauth4webapi": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.1.tgz", - "integrity": "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.2.tgz", + "integrity": "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -10659,13 +10659,13 @@ } }, "node_modules/openid-client": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.7.1.tgz", - "integrity": "sha512-kOiE4q0kNogr90hXsxPrKeEDuY+V0kkZazvZScOwZkYept9slsaQ3usXTaKkm6I04vLNuw5caBoX7UfrwC6x8w==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.1.tgz", + "integrity": "sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==", "license": "MIT", "dependencies": { "jose": "^6.1.0", - "oauth4webapi": "^3.8.0" + "oauth4webapi": "^3.8.2" }, "funding": { "url": "https://github.com/sponsors/panva" @@ -22180,9 +22180,9 @@ } }, "oauth4webapi": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.1.tgz", - "integrity": "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==" + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.2.tgz", + "integrity": "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==" }, "object-assign": { "version": "4.1.1", @@ -22451,12 +22451,12 @@ } }, "openid-client": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.7.1.tgz", - "integrity": "sha512-kOiE4q0kNogr90hXsxPrKeEDuY+V0kkZazvZScOwZkYept9slsaQ3usXTaKkm6I04vLNuw5caBoX7UfrwC6x8w==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.1.tgz", + "integrity": "sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==", "requires": { "jose": "^6.1.0", - "oauth4webapi": "^3.8.0" + "oauth4webapi": "^3.8.2" }, "dependencies": { "jose": { diff --git a/package.json b/package.json index c9f969342..234759e2d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "mustache": "^4.2.0", "nodemailer": "^7.0.3", "odata-v4-parser": "~0.1", - "openid-client": "^6.6.2", + "openid-client": "^6.8.1", "path-to-regexp": "^8.2.0", "pg": "~8.8.0", "pg-query-stream": "~4", From 0a65c0926b6abd364c0d038a770d5a1f5f033ea6 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Mon, 20 Apr 2026 13:16:33 +0000 Subject: [PATCH 12/13] wip --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bda53439f..39722b6fc 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "mustache": "^4.2.0", "nodemailer": "^8.0.4", "odata-v4-parser": "~0.1", - "openid-client": "^6.8.1", + "openid-client": "^6.8.3", "path-to-regexp": "^8.3.0", "pg": "~8.8.0", "pg-query-stream": "^4.14.0", From 37cfc71d8f2fe7fcbf3c747b3d57336baaaf6014 Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Mon, 20 Apr 2026 13:18:33 +0000 Subject: [PATCH 13/13] update openid-client to latest release --- package-lock.json | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78f97095d..7e15495b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "mustache": "^4.2.0", "nodemailer": "^8.0.4", "odata-v4-parser": "~0.1", - "openid-client": "^6.8.1", + "openid-client": "^6.8.3", "path-to-regexp": "^8.3.0", "pg": "~8.8.0", "pg-query-stream": "^4.14.0", @@ -11075,9 +11075,9 @@ } }, "node_modules/oauth4webapi": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.2.tgz", - "integrity": "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==", + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", + "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -11517,22 +11517,22 @@ "license": "MIT" }, "node_modules/openid-client": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.1.tgz", - "integrity": "sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.3.tgz", + "integrity": "sha512-AoY/NaN9esS3+xvHInFSK0g3skSfeE0uqQAKRj4rB6/GsBIvzwTUaYo9+HcqpKIaP0dP85p5W07hayKgS4GAeA==", "license": "MIT", "dependencies": { - "jose": "^6.1.0", - "oauth4webapi": "^3.8.2" + "jose": "^6.2.2", + "oauth4webapi": "^3.8.5" }, "funding": { "url": "https://github.com/sponsors/panva" } }, "node_modules/openid-client/node_modules/jose": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", - "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -23955,9 +23955,9 @@ } }, "oauth4webapi": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.2.tgz", - "integrity": "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==" + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", + "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==" }, "object-assign": { "version": "4.1.1", @@ -24272,18 +24272,18 @@ } }, "openid-client": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.1.tgz", - "integrity": "sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.3.tgz", + "integrity": "sha512-AoY/NaN9esS3+xvHInFSK0g3skSfeE0uqQAKRj4rB6/GsBIvzwTUaYo9+HcqpKIaP0dP85p5W07hayKgS4GAeA==", "requires": { - "jose": "^6.1.0", - "oauth4webapi": "^3.8.2" + "jose": "^6.2.2", + "oauth4webapi": "^3.8.5" }, "dependencies": { "jose": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", - "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==" + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==" } } },