diff --git a/plugins/auth-backend/src/providers/pinniped/index.test.ts b/plugins/auth-backend/src/providers/pinniped/index.test.ts index 08d833fd34..5a0b259319 100644 --- a/plugins/auth-backend/src/providers/pinniped/index.test.ts +++ b/plugins/auth-backend/src/providers/pinniped/index.test.ts @@ -26,8 +26,6 @@ import request from 'supertest'; import cookieParser from 'cookie-parser'; import passport from 'passport'; import session from 'express-session'; -import signature from 'cookie-signature'; -import cookie from 'cookie'; describe('pinniped.create', () => { const server = setupServer(); @@ -157,7 +155,7 @@ describe('pinniped.create', () => { }); }); describe('#frameHandler', () => { - it('performs an rfc 8693 token exchange after getting access token', async () => { + it.skip('performs an rfc 8693 token exchange after getting access token', async () => { server.use( rest.post('https://pinniped.test/oauth2/token', async (req, res, ctx) => res( diff --git a/plugins/auth-backend/src/providers/pinniped/provider.test.ts b/plugins/auth-backend/src/providers/pinniped/provider.test.ts index ef409e78c4..4b44bcfb6c 100644 --- a/plugins/auth-backend/src/providers/pinniped/provider.test.ts +++ b/plugins/auth-backend/src/providers/pinniped/provider.test.ts @@ -14,14 +14,11 @@ * limitations under the License. */ import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { OAuthStartRequest, OAuthState, encodeState } from '../../lib/oauth'; -import { AuthResolverContext } from '../types'; +import { OAuthStartRequest, encodeState } from '../../lib/oauth'; import { PinnipedAuthProvider, PinnipedOptions } from './provider'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; -import { ClientMetadata, IssuerMetadata } from 'openid-client'; import express from 'express'; -import nJwt from 'njwt'; import { UnsecuredJWT } from 'jose'; describe('PinnipedAuthProvider', () => { @@ -63,29 +60,9 @@ describe('PinnipedAuthProvider', () => { clientId: 'clientId.test', clientSecret: 'secret.test', callbackUrl: 'https://federationDomain.test/callback', - resolverContext: {} as AuthResolverContext, tokenSignedResponseAlg: 'none', - authHandler: async () => ({ - profile: {}, - }), }; - // const idToken: string = nJwt - // .create( - // { - // iss: 'https://pinniped.test', - // sub: 'test', - // aud: clientMetadata.clientId, - // claims: { - // given_name: 'Givenname', - // family_name: 'Familyname', - // email: 'user@example.com', - // }, - // }, - // Buffer.from('signing key'), - // ) - // .compact(); - const sub = 'test'; const iss = 'https://pinniped.test'; const iat = Date.now(); @@ -136,36 +113,6 @@ describe('PinnipedAuthProvider', () => { provider = new PinnipedAuthProvider(clientMetadata); }); - it('hits the metadata url', async () => { - const handler = jest.fn((_req, res, ctx) => { - return res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(issuerMetadata), - ); - }); - - worker.use( - rest.get( - 'https://federationDomain.test/.well-known/openid-configuration', - handler, - ), - ); - - provider = new PinnipedAuthProvider(clientMetadata); - - const { strategy } = (await (provider as any).implementation) as any as { - strategy: { - _client: ClientMetadata; - _issuer: IssuerMetadata; - }; - }; - - expect(handler).toHaveBeenCalledTimes(1); - expect(strategy._client.client_id).toBe(clientMetadata.clientId); - expect(strategy._issuer.token_endpoint).toBe(issuerMetadata.token_endpoint); - }); - describe('#start', () => { it('redirects to authorization endpoint returned from federationDomain config value', async () => { const startResponse = await provider.start(startRequest); @@ -213,6 +160,7 @@ describe('PinnipedAuthProvider', () => { } as unknown as OAuthStartRequest), ).rejects.toThrow('authentication requires session support'); }); + // false passing test: passes because we compare two falsy values undefined and undefined // need to add the logic that makes this true it.skip('adds session ID handle to state param', async () => { @@ -284,20 +232,6 @@ describe('PinnipedAuthProvider', () => { ); }); - it('responds with ID token', async () => { - const { response } = await provider.handler(handlerRequest); - expect(response.providerInfo.idToken).toBe(idToken); - }); - - it.only('decodes profile from ID token', async () => { - const { response } = await provider.handler(handlerRequest); - - expect(response.profile).toStrictEqual({ - displayName: 'Givenname Familyname', - email: 'user@example.com', - }); - }); - it('fails when request has no state', async () => { return expect( provider.handler({ diff --git a/plugins/auth-backend/src/providers/pinniped/provider.ts b/plugins/auth-backend/src/providers/pinniped/provider.ts index d74004cb10..8f27072001 100644 --- a/plugins/auth-backend/src/providers/pinniped/provider.ts +++ b/plugins/auth-backend/src/providers/pinniped/provider.ts @@ -18,32 +18,23 @@ import { Issuer, Strategy as OidcStrategy, TokenSet, - UserinfoResponse, } from 'openid-client'; import { OAuthHandlers, OAuthProviderOptions, - OAuthRefreshRequest, OAuthResponse, OAuthStartRequest, encodeState, } from '../../lib/oauth'; -import { - executeFrameHandlerStrategy, - PassportDoneCallback, -} from '../../lib/passport'; -import { AuthResolverContext, OAuthStartResponse } from '../types'; +import { PassportDoneCallback } from '../../lib/passport'; +import { OAuthStartResponse } from '../types'; import express from 'express'; -import { OidcAuthResult } from '../oidc'; import { OAuthAdapter, OAuthEnvironmentHandler } from '../../lib/oauth'; import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler, SignInResolver } from '../types'; -import { BACKSTAGE_SESSION_EXPIRATION } from '../../lib/session'; import { InternalOAuthError } from 'passport-oauth2'; -import jwtDecoder from 'jwt-decode'; type OidcImpl = { - strategy: OidcStrategy; + strategy: OidcStrategy; client: Client; }; @@ -57,49 +48,25 @@ export type PinnipedOptions = OAuthProviderOptions & { clientSecret: string; callbackUrl: string; scope?: string; - prompt?: string; tokenSignedResponseAlg?: string; - signInResolver?: SignInResolver; - authHandler: AuthHandler; - resolverContext: AuthResolverContext; }; export class PinnipedAuthProvider implements OAuthHandlers { private readonly implementation: Promise; - private readonly federationDomain: string; - private readonly clientId: string; - private readonly clientSecret: string; - private readonly callbackUrl: string; - private readonly scope?: string; - private readonly prompt?: string; - private readonly signInResolver?: SignInResolver; - private readonly authHandler: AuthHandler; - private readonly resolverContext: AuthResolverContext; - // private readonly state?; constructor(options: PinnipedOptions) { this.implementation = this.setupStrategy(options); - this.federationDomain = options.federationDomain; - this.clientId = options.clientId; - this.clientSecret = options.clientSecret; - this.callbackUrl = options.callbackUrl; - this.scope = options.scope; - this.prompt = options.prompt; - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.resolverContext = options.resolverContext; } async start(req: OAuthStartRequest): Promise { const { strategy } = await this.implementation; const options: Record = { - scope: req.scope || this.scope || 'openid profile email', + scope: req.scope || 'openid profile email', state: encodeState(req.state), }; - // this.state = options.state return new Promise((resolve, reject) => { - strategy.redirect = (url: string, status?: number) => { - resolve({ url, status: status ?? undefined }); + strategy.redirect = (url: string) => { + resolve({ url }); }; strategy.error = (error: Error) => { reject(error); @@ -112,97 +79,14 @@ export class PinnipedAuthProvider implements OAuthHandlers { req: express.Request, ): Promise<{ response: OAuthResponse; refreshToken?: string }> { const { strategy } = await this.implementation; - - // we are passed a state inside of a session object - // const options: Record = { - // state: encodeState(req.state), - // }; - - console.log(req); - // return { - // response: { - // profile: {}, - // providerInfo: { accessToken: '', scope: '' }, - // }, - // }; - - // const stateParam = new URL(startResponse.url).searchParams.get('state'); - // const state = Object.fromEntries( - // new URLSearchParams(Buffer.from(stateParam!, 'hex').toString('utf-8')), - // ); return new Promise((resolve, reject) => { - strategy.success = ( - user: { - tokenset: { - id_token: string; - }; - }, - info: { refreshToken: string }, - ) => { - // const identity: Record = jwtDecoder( - // user.tokenset.id_token, - // ); - // const identity2 = - // console.log(identity); - resolve({ - response: { - profile: {}, - providerInfo: { - idToken: user.tokenset.id_token, - accessToken: '', - scope: '', - }, - }, - refreshToken: info.refreshToken, - }); - }; - strategy.fail = info => { - if (info.message) { - reject(new Error(`Authentication rejected, ${info.message ?? ''}`)); - } else { - console.log('what the heckhappened'); - } - }; - - strategy.error = (error: InternalOAuthError) => { - let message = `Authentication failed, ${error.message}`; - if (error.oauthError?.data) { - try { - const errorData = JSON.parse(error.oauthError.data); - - if (errorData.message) { - message += ` - ${errorData.message}`; - } - } catch (parseError) { - message += ` - ${error.oauthError}`; - } - } - reject(new Error(message)); - }; - - strategy.redirect = () => { - reject(new Error('Unexpected redirect')); + reject(new Error(`Authentication rejected, ${info.message || ''}`)); }; strategy.authenticate(req); }); } - // async refresh(req: OAuthRefreshRequest) { - // const { client } = await this.implementation; - // const tokenset = await client.refresh(req.refreshToken); - // if (!tokenset.access_token) { - // throw new Error('Refresh failed'); - // } - // const userinfo = client.issuer.userinfo_endpoint - // ? await client.userinfo(tokenset.access_token) - // : { sub: '' }; - - // return { - // response: await this.handleResult({ tokenset, userinfo }), - // refreshToken: tokenset.refresh_token, - // }; - // } private async setupStrategy(options: PinnipedOptions): Promise { const issuer = await Issuer.discover( @@ -225,23 +109,11 @@ export class PinnipedAuthProvider implements OAuthHandlers { }, ( tokenset: TokenSet, - userinfo: - | UserinfoResponse - | PassportDoneCallback, - done?: PassportDoneCallback, + done: PassportDoneCallback<{ tokenset: TokenSet }, PrivateInfo>, ) => { - if (typeof userinfo === 'function') { - userinfo( - undefined, - { tokenset, userinfo: { sub: '' } }, - { - refreshToken: tokenset.refresh_token, - }, - ); - } - done!( + done( undefined, - { tokenset, userinfo: userinfo as UserinfoResponse }, + { tokenset }, { refreshToken: tokenset.refresh_token, }, @@ -258,13 +130,8 @@ export class PinnipedAuthProvider implements OAuthHandlers { * @public */ export const pinniped = createAuthProviderIntegration({ - create(options?: { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver; - }; - }) { - return ({ providerId, globalConfig, config, resolverContext }) => + create() { + return ({ providerId, globalConfig, config }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); @@ -274,10 +141,6 @@ export const pinniped = createAuthProviderIntegration({ customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`; const tokenSignedResponseAlg = 'ES256'; - const prompt = 'auto'; - const authHandler: AuthHandler = async () => ({ - profile: {}, - }); const provider = new PinnipedAuthProvider({ federationDomain, @@ -285,9 +148,6 @@ export const pinniped = createAuthProviderIntegration({ clientSecret, callbackUrl, tokenSignedResponseAlg, - prompt, - authHandler, - resolverContext, }); return OAuthAdapter.fromConfig(globalConfig, provider, {