From cc59bec698748ea91242043e8f4065229dd6a1e7 Mon Sep 17 00:00:00 2001 From: Adam Mcgrath Date: Thu, 17 Feb 2022 10:21:46 +0000 Subject: [PATCH] [SDK-3109] Add ability to pass custom logout params (#329) * Add ability to pass custom logout params * Revert basic example --- .eslintrc.json | 1 + index.d.ts | 10 ++++ lib/client.js | 15 +++++- lib/config.js | 1 + lib/context.js | 2 + test/logout.tests.js | 110 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3a9fe607..9038bef0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,7 @@ "extends": "eslint:recommended", "rules": { "no-useless-escape": 1, + "no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": true }], "no-console": 0, "linebreak-style": ["error", "unix"] }, diff --git a/index.d.ts b/index.d.ts index 85ee010a..7229e823 100644 --- a/index.d.ts +++ b/index.d.ts @@ -218,6 +218,11 @@ interface LogoutOptions { * URL to returnTo after logout, overrides the Default in {@link ConfigParams.routes.postLogoutRedirect routes.postLogoutRedirect} */ returnTo?: string; + + /** + * Additional custom parameters to pass to the logout endpoint. + */ + logoutParams?: { [key: string]: any }; } /** @@ -303,6 +308,11 @@ interface ConfigParams { */ authorizationParams?: AuthorizationParameters; + /** + * Additional custom parameters to pass to the logout endpoint. + */ + logoutParams?: { [key: string]: any }; + /** * REQUIRED. The root URL for the application router, eg https://localhost * Can use env key BASE_URL instead. diff --git a/lib/client.js b/lib/client.js index 48e2be75..1517f004 100644 --- a/lib/client.js +++ b/lib/client.js @@ -101,11 +101,24 @@ async function get(config) { ) { Object.defineProperty(client, 'endSessionUrl', { value(params) { + const { + id_token_hint, + post_logout_redirect_uri, + ...extraParams + } = params; const parsedUrl = url.parse(urlJoin(issuer.issuer, '/v2/logout')); parsedUrl.query = { - returnTo: params.post_logout_redirect_uri, + ...extraParams, + returnTo: post_logout_redirect_uri, client_id: client.client_id, }; + + Object.entries(parsedUrl.query).forEach(([key, value]) => { + if (value === null || value === undefined) { + delete parsedUrl.query[key]; + } + }); + return url.format(parsedUrl); }, }); diff --git a/lib/config.js b/lib/config.js index 66082e1b..bd903b70 100644 --- a/lib/config.js +++ b/lib/config.js @@ -110,6 +110,7 @@ const paramsSchema = Joi.object({ .optional() .unknown(true) .default(), + logoutParams: Joi.object().optional(), baseURL: Joi.string() .uri() .required() diff --git a/lib/context.js b/lib/context.js index 99b771e0..c95e363e 100644 --- a/lib/context.js +++ b/lib/context.js @@ -300,6 +300,8 @@ class ResponseContext { try { returnURL = client.endSessionUrl({ + ...config.logoutParams, + ...params.logoutParams, post_logout_redirect_uri: returnURL, id_token_hint, }); diff --git a/test/logout.tests.js b/test/logout.tests.js index 1f495deb..163b65a5 100644 --- a/test/logout.tests.js +++ b/test/logout.tests.js @@ -1,4 +1,5 @@ const { assert } = require('chai'); +const { URL } = require('url'); const { create: createServer } = require('./fixture/server'); const { makeIdToken } = require('./fixture/cert'); const { auth } = require('./..'); @@ -210,4 +211,113 @@ describe('logout route', async () => { jar.getCookies(baseUrl).find(({ key }) => key === 'skipSilentLogin') ); }); + + it('should pass logout params to end session url', async () => { + server = await createServer( + auth({ ...defaultConfig, idpLogout: true, logoutParams: { foo: 'bar' } }) + ); + + const { jar } = await login(); + const { + response: { + headers: { location }, + }, + } = await logout(jar); + const params = new URL(location).searchParams; + assert.equal(params.get('foo'), 'bar'); + }); + + it('should override logout params per request', async () => { + const router = auth({ + ...defaultConfig, + idpLogout: true, + logoutParams: { foo: 'bar' }, + routes: { logout: false }, + }); + server = await createServer(router); + router.get('/logout', (req, res) => + res.oidc.logout({ logoutParams: { foo: 'baz' } }) + ); + + const { jar } = await login(); + const { + response: { + headers: { location }, + }, + } = await logout(jar); + const params = new URL(location).searchParams; + assert.equal(params.get('foo'), 'baz'); + }); + + it('should pass logout params to auth0 logout url', async () => { + server = await createServer( + auth({ + ...defaultConfig, + issuerBaseURL: 'https://test.eu.auth0.com', + idpLogout: true, + auth0Logout: true, + logoutParams: { foo: 'bar' }, + }) + ); + + const { jar } = await login(); + const { + response: { + headers: { location }, + }, + } = await logout(jar); + const url = new URL(location); + assert.equal(url.pathname, '/v2/logout'); + assert.equal(url.searchParams.get('foo'), 'bar'); + }); + + it('should honor logout url config over logout params', async () => { + server = await createServer( + auth({ + ...defaultConfig, + routes: { postLogoutRedirect: 'http://foo.com' }, + idpLogout: true, + logoutParams: { + foo: 'bar', + post_logout_redirect_uri: 'http://bar.com', + }, + }) + ); + + const { jar } = await login(); + const { + response: { + headers: { location }, + }, + } = await logout(jar); + const url = new URL( + new URL(location).searchParams.get('post_logout_redirect_uri') + ); + assert.equal(url.hostname, 'foo.com'); + }); + + it('should ignore undefined or null logout params', async () => { + server = await createServer( + auth({ + ...defaultConfig, + issuerBaseURL: 'https://test.eu.auth0.com', + idpLogout: true, + auth0Logout: true, + logoutParams: { foo: 'bar', bar: undefined, baz: null, qux: '' }, + }) + ); + + const { jar } = await login(); + const { + response: { + headers: { location }, + }, + } = await logout(jar); + const url = new URL(location); + assert.equal(url.pathname, '/v2/logout'); + assert.equal(url.searchParams.get('foo'), 'bar'); + assert.isFalse(url.searchParams.has('bar')); + assert.isFalse(url.searchParams.has('baz')); + assert.equal(url.searchParams.get('qux'), ''); + }); });