Extract rfc8693 tokenexchange logic to a helper function
Signed-off-by: Ruben Vallejo <rvallejo@vmware.com> Co-authored-by: Jamie Klassen <jklassen@vmware.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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<string | undefined> => {
|
||||
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) => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user