From 90fe0e2534bf23f4e45de2da89eb6aacccc83ec5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Apr 2022 14:02:16 +0200 Subject: [PATCH] auth-backend: migrate okta provider to use resolver context Signed-off-by: Patrik Oldsberg --- .../src/providers/okta/provider.test.ts | 17 +--- .../src/providers/okta/provider.ts | 80 ++++++------------- 2 files changed, 25 insertions(+), 72 deletions(-) diff --git a/plugins/auth-backend/src/providers/okta/provider.test.ts b/plugins/auth-backend/src/providers/okta/provider.test.ts index 939981e81c..468c4cdcc8 100644 --- a/plugins/auth-backend/src/providers/okta/provider.test.ts +++ b/plugins/auth-backend/src/providers/okta/provider.test.ts @@ -17,9 +17,7 @@ import { OktaAuthProvider } from './provider'; import * as helpers from '../../lib/passport/PassportStrategyHelper'; import { OAuthResult } from '../../lib/oauth'; -import { getVoidLogger } from '@backstage/backend-common'; -import { TokenIssuer } from '../../identity/types'; -import { CatalogIdentityClient } from '../../lib/catalog'; +import { AuthResolverContext } from '../types'; const mockFrameHandler = jest.spyOn( helpers, @@ -30,19 +28,8 @@ const mockFrameHandler = jest.spyOn( describe('createOktaProvider', () => { it('should auth', async () => { - const tokenIssuer = { - issueToken: jest.fn(), - listPublicKeys: jest.fn(), - }; - const catalogIdentityClient = { - findUser: jest.fn(), - }; - const provider = new OktaAuthProvider({ - logger: getVoidLogger(), - catalogIdentityClient: - catalogIdentityClient as unknown as CatalogIdentityClient, - tokenIssuer: tokenIssuer as unknown as TokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: { email: fullProfile.emails![0]!.value, diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 74fbe8159c..606a9bbce7 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -41,11 +41,9 @@ import { AuthHandler, RedirectInfo, SignInResolver, + AuthResolverContext, } from '../types'; import { StateStore } from 'passport-oauth2'; -import { CatalogIdentityClient, getEntityClaims } from '../../lib/catalog'; -import { TokenIssuer } from '../../identity'; -import { Logger } from 'winston'; type PrivateInfo = { refreshToken: string; @@ -55,18 +53,14 @@ export type OktaAuthProviderOptions = OAuthProviderOptions & { audience: string; signInResolver?: SignInResolver; authHandler: AuthHandler; - tokenIssuer: TokenIssuer; - catalogIdentityClient: CatalogIdentityClient; - logger: Logger; + resolverContext: AuthResolverContext; }; export class OktaAuthProvider implements OAuthHandlers { - private readonly _strategy: any; - private readonly _signInResolver?: SignInResolver; - private readonly _authHandler: AuthHandler; - private readonly _tokenIssuer: TokenIssuer; - private readonly _catalogIdentityClient: CatalogIdentityClient; - private readonly _logger: Logger; + private readonly strategy: any; + private readonly signInResolver?: SignInResolver; + private readonly authHandler: AuthHandler; + private readonly resolverContext: AuthResolverContext; /** * Due to passport-okta-oauth forcing options.state = true, @@ -76,7 +70,7 @@ export class OktaAuthProvider implements OAuthHandlers { * passport-oauth2, which is the StateStore implementation used when options.state = false, * allowing us to avoid using express-session in order to integrate with Okta. */ - private _store: StateStore = { + private store: StateStore = { store(_req: express.Request, cb: any) { cb(null, null); }, @@ -86,20 +80,18 @@ export class OktaAuthProvider implements OAuthHandlers { }; constructor(options: OktaAuthProviderOptions) { - this._signInResolver = options.signInResolver; - this._authHandler = options.authHandler; - this._tokenIssuer = options.tokenIssuer; - this._catalogIdentityClient = options.catalogIdentityClient; - this._logger = options.logger; + this.signInResolver = options.signInResolver; + this.authHandler = options.authHandler; + this.resolverContext = options.resolverContext; - this._strategy = new OktaStrategy( + this.strategy = new OktaStrategy( { clientID: options.clientId, clientSecret: options.clientSecret, callbackURL: options.callbackUrl, audience: options.audience, passReqToCallback: false as true, - store: this._store, + store: this.store, response_type: 'code', }, ( @@ -126,7 +118,7 @@ export class OktaAuthProvider implements OAuthHandlers { } async start(req: OAuthStartRequest): Promise { - return await executeRedirectStrategy(req, this._strategy, { + return await executeRedirectStrategy(req, this.strategy, { accessType: 'offline', prompt: 'consent', scope: req.scope, @@ -138,7 +130,7 @@ export class OktaAuthProvider implements OAuthHandlers { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo - >(req, this._strategy); + >(req, this.strategy); return { response: await this.handleResult(result), @@ -149,13 +141,13 @@ export class OktaAuthProvider implements OAuthHandlers { async refresh(req: OAuthRefreshRequest) { const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy( - this._strategy, + this.strategy, req.refreshToken, req.scope, ); const fullProfile = await executeFetchUserProfileStrategy( - this._strategy, + this.strategy, accessToken, ); @@ -170,12 +162,7 @@ export class OktaAuthProvider implements OAuthHandlers { } private async handleResult(result: OAuthResult) { - const context = { - logger: this._logger, - catalogIdentityClient: this._catalogIdentityClient, - tokenIssuer: this._tokenIssuer, - }; - const { profile } = await this._authHandler(result, context); + const { profile } = await this.authHandler(result, this.resolverContext); const response: OAuthResponse = { providerInfo: { @@ -187,13 +174,13 @@ export class OktaAuthProvider implements OAuthHandlers { profile, }; - if (this._signInResolver) { - response.backstageIdentity = await this._signInResolver( + if (this.signInResolver) { + response.backstageIdentity = await this.signInResolver( { result, profile, }, - context, + this.resolverContext, ); } @@ -211,16 +198,11 @@ export const oktaEmailSignInResolver: SignInResolver = async ( throw new Error('Okta profile contained no email'); } - const entity = await ctx.catalogIdentityClient.findUser({ + return ctx.signInWithCatalogUser({ annotations: { 'okta.com/email': profile.email, }, }); - - const claims = getEntityClaims(entity); - const token = await ctx.tokenIssuer.issueToken({ claims }); - - return { id: entity.metadata.name, entity, token }; }; /** @@ -261,15 +243,7 @@ export const createOktaProvider = (_options?: { resolver: SignInResolver; }; }): AuthProviderFactory => { - return ({ - providerId, - globalConfig, - config, - tokenIssuer, - tokenManager, - catalogApi, - logger, - }) => + return ({ providerId, globalConfig, config, resolverContext }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); @@ -286,11 +260,6 @@ export const createOktaProvider = (_options?: { throw new Error("URL for 'audience' must start with 'https://'."); } - const catalogIdentityClient = new CatalogIdentityClient({ - catalogApi, - tokenManager, - }); - const authHandler: AuthHandler = _options?.authHandler ? _options.authHandler : async ({ fullProfile, params }) => ({ @@ -304,15 +273,12 @@ export const createOktaProvider = (_options?: { callbackUrl, authHandler, signInResolver: _options?.signIn?.resolver, - tokenIssuer, - catalogIdentityClient, - logger, + resolverContext, }); return OAuthAdapter.fromConfig(globalConfig, provider, { disableRefresh: false, providerId, - tokenIssuer, callbackUrl, }); });