From b497b6e9f3456cc6def12a44d2dd8b80cef3eb60 Mon Sep 17 00:00:00 2001 From: Ruben Vallejo Date: Tue, 12 Sep 2023 15:05:37 -0400 Subject: [PATCH] Extract rfc8693 tokenexchange logic to a helper function Signed-off-by: Ruben Vallejo Co-authored-by: Jamie Klassen --- .../dev/index.ts | 2 +- .../src/authenticator.test.ts | 2 +- .../src/authenticator.ts | 82 +++++++++++++------ plugins/auth-backend/package.json | 2 - yarn.lock | 17 +--- 5 files changed, 61 insertions(+), 44 deletions(-) diff --git a/plugins/auth-backend-module-pinniped-provider/dev/index.ts b/plugins/auth-backend-module-pinniped-provider/dev/index.ts index cf0a6ebac9..bd09f77a1f 100644 --- a/plugins/auth-backend-module-pinniped-provider/dev/index.ts +++ b/plugins/auth-backend-module-pinniped-provider/dev/index.ts @@ -15,7 +15,7 @@ */ import { createBackend } from '@backstage/backend-defaults'; -import { authPlugin } from '@backstage/plugin-auth-backend'; +import authPlugin from '@backstage/plugin-auth-backend'; import { authModulePinnipedProvider } from '../src'; const backend = createBackend(); diff --git a/plugins/auth-backend-module-pinniped-provider/src/authenticator.test.ts b/plugins/auth-backend-module-pinniped-provider/src/authenticator.test.ts index ea2ca451b5..f908c1ea13 100644 --- a/plugins/auth-backend-module-pinniped-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-pinniped-provider/src/authenticator.test.ts @@ -415,7 +415,7 @@ describe('pinnipedAuthenticator', () => { await expect( pinnipedAuthenticator.authenticate(handlerRequest, implementation), ).rejects.toThrow( - `Failed to get cluster specific ID token for "test_cluster", RFC8693 token exchange failed with error: NetworkError: Connection timed out`, + `Failed to get cluster specific ID token for "test_cluster": Error: RFC8693 token exchange failed with error: NetworkError: Connection timed out`, ); }); diff --git a/plugins/auth-backend-module-pinniped-provider/src/authenticator.ts b/plugins/auth-backend-module-pinniped-provider/src/authenticator.ts index 0b178cc080..83c4204ad0 100644 --- a/plugins/auth-backend-module-pinniped-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-pinniped-provider/src/authenticator.ts @@ -19,7 +19,39 @@ import { decodeOAuthState, encodeOAuthState, } from '@backstage/plugin-auth-node'; -import { Issuer, TokenSet, Strategy as OidcStrategy } from 'openid-client'; +import { + Client, + Issuer, + TokenSet, + Strategy as OidcStrategy, +} from 'openid-client'; + +const rfc8693TokenExchange = async ({ + subject_token, + target_audience, + ctx, +}: { + subject_token: string; + target_audience: string; + ctx: Promise<{ + providerStrategy: OidcStrategy<{}>; + client: Client; + }>; +}): Promise => { + const { client } = await ctx; + return client + .grant({ + grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', + subject_token, + audience: target_audience, + subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', + requested_token_type: 'urn:ietf:params:oauth:token-type:jwt', + }) + .then(tokenset => tokenset.access_token) + .catch(err => { + throw new Error(`RFC8693 token exchange failed with error: ${err}`); + }); +}; /** @public */ export const pinnipedAuthenticator = createOAuthAuthenticator({ @@ -39,7 +71,7 @@ export const pinnipedAuthenticator = createOAuthAuthenticator({ scope: config.getOptionalString('scope') || '', id_token_signed_response_alg: 'ES256', }); - const strategy = new OidcStrategy( + const providerStrategy = new OidcStrategy( { client, passReqToCallback: false, @@ -57,11 +89,11 @@ export const pinnipedAuthenticator = createOAuthAuthenticator({ }, ); - return { strategy, client }; + return { providerStrategy, client }; }, - async start(input, implementation) { - const { strategy } = await implementation; + async start(input, ctx) { + const { providerStrategy } = await ctx; const stringifiedAudience = input.req.query?.audience as string; const decodedState = decodeOAuthState(input.state); const state = { ...decodedState, audience: stringifiedAudience }; @@ -73,6 +105,7 @@ export const pinnipedAuthenticator = createOAuthAuthenticator({ }; return new Promise((resolve, reject) => { + const strategy = Object.create(providerStrategy); strategy.redirect = (url: string) => { resolve({ url }); }; @@ -83,8 +116,8 @@ export const pinnipedAuthenticator = createOAuthAuthenticator({ }); }, - async authenticate(input, implementation) { - const { strategy, client } = await implementation; + async authenticate(input, ctx) { + const { providerStrategy } = await ctx; const { req } = input; const { searchParams } = new URL(req.url, 'https://pinniped.com'); const stateParam = searchParams.get('state'); @@ -93,25 +126,20 @@ export const pinnipedAuthenticator = createOAuthAuthenticator({ : undefined; return new Promise((resolve, reject) => { - strategy.success = user => { + const strategy = Object.create(providerStrategy); + strategy.success = (user: any) => { (audience - ? client - .grant({ - grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', - subject_token: user.tokenset.access_token, - audience, - subject_token_type: - 'urn:ietf:params:oauth:token-type:access_token', - requested_token_type: 'urn:ietf:params:oauth:token-type:jwt', - }) - .then(tokenset => tokenset.access_token) - .catch(err => - reject( - new Error( - `Failed to get cluster specific ID token for "${audience}", RFC8693 token exchange failed with error: ${err}`, - ), + ? rfc8693TokenExchange({ + subject_token: user.tokenset.access_token, + target_audience: audience, + ctx, + }).catch(err => + reject( + new Error( + `Failed to get cluster specific ID token for "${audience}": ${err}`, ), - ) + ), + ) : Promise.resolve(user.tokenset.id_token) ).then(idToken => { resolve({ @@ -127,7 +155,7 @@ export const pinnipedAuthenticator = createOAuthAuthenticator({ }); }; - strategy.fail = info => { + strategy.fail = (info: any) => { reject(new Error(`Authentication rejected, ${info.message || ''}`)); }; @@ -143,8 +171,8 @@ export const pinnipedAuthenticator = createOAuthAuthenticator({ }); }, - async refresh(input, implementation) { - const { client } = await implementation; + async refresh(input, ctx) { + const { client } = await ctx; const tokenset = await client.refresh(input.refreshToken); return new Promise((resolve, reject) => { diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 8cc8cf6b98..360ea52785 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -54,9 +54,7 @@ "@types/passport": "^1.0.3", "compression": "^1.7.4", "connect-session-knex": "^3.0.1", - "cookie": "^0.5.0", "cookie-parser": "^1.4.5", - "cookie-signature": "^1.2.1", "cors": "^2.8.5", "express": "^4.17.1", "express-promise-router": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index d28d24fc14..186c06cad5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5036,9 +5036,7 @@ __metadata: "@types/xml2js": ^0.4.7 compression: ^1.7.4 connect-session-knex: ^3.0.1 - cookie: ^0.5.0 cookie-parser: ^1.4.5 - cookie-signature: ^1.2.1 cors: ^2.8.5 express: ^4.17.1 express-promise-router: ^4.1.0 @@ -22775,13 +22773,6 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:^1.2.1": - version: 1.2.1 - resolution: "cookie-signature@npm:1.2.1" - checksum: bb464aacac390b5d7d8ead2d6fff7c1c3b7378c7d0250921f48923fe889688e081ab33950448929db5f24d4f9f1506589a7ee1c685de8f12a3fdb30c49667ec5 - languageName: node - linkType: hard - "cookie@npm:0.4.1": version: 0.4.1 resolution: "cookie@npm:0.4.1" @@ -22796,7 +22787,7 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.5.0, cookie@npm:^0.5.0, cookie@npm:~0.5.0": +"cookie@npm:0.5.0, cookie@npm:~0.5.0": version: 0.5.0 resolution: "cookie@npm:0.5.0" checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 @@ -30257,9 +30248,9 @@ __metadata: linkType: hard "jose@npm:^4.14.6": - version: 4.14.6 - resolution: "jose@npm:4.14.6" - checksum: eae81a234e7bf1446b1bd80722b3462b014e3835b155c3a7799c1c5043163a53a0dc28d347004151b031e6b7b863403aabf8814d9cc217ce21f8c2f3ebd4b335 + version: 4.15.2 + resolution: "jose@npm:4.15.2" + checksum: 8f0cab1eef31243abe14a935b2b330cd95f10f9b69808fd642088ae5000e50e566664934537d2c6413ab2f6b54acd8265a5033da05157aa1260c5f1d7e57fab0 languageName: node linkType: hard