From 42315f5015a9c81b0d15e52032019ea8f6f4a28c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Apr 2022 13:22:37 +0200 Subject: [PATCH] auth-backend: migrate aws-alb provider to use resolver context Signed-off-by: Patrik Oldsberg --- .../src/providers/aws-alb/provider.test.ts | 91 +++++++------------ .../src/providers/aws-alb/provider.ts | 47 +++------- 2 files changed, 45 insertions(+), 93 deletions(-) diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts index eb801fdd46..a917858743 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; + import express from 'express'; import { JWT } from 'jose'; - import { ALB_ACCESS_TOKEN_HEADER, ALB_JWT_HEADER, AwsAlbAuthProvider, } from './provider'; -import { TokenIssuer } from '../../identity/types'; -import { CatalogIdentityClient } from '../../lib/catalog'; import { makeProfileInfo } from '../../lib/passport'; +import { AuthResolverContext } from '../types'; +import { AuthenticationError } from '@backstage/errors'; const jwtMock = JWT as jest.Mocked; @@ -66,16 +65,6 @@ beforeEach(() => { }); describe('AwsAlbAuthProvider', () => { - const tokenIssuer: TokenIssuer = { - listPublicKeys: jest.fn(), - async issueToken(params) { - return `token-for-${params.claims.sub}`; - }, - }; - const catalogIdentityClient: CatalogIdentityClient = { - findUser: jest.fn(), - } as unknown as CatalogIdentityClient; - const mockRequest = { header: jest.fn(name => { if (name === ALB_JWT_HEADER) { @@ -115,9 +104,7 @@ describe('AwsAlbAuthProvider', () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }), @@ -161,9 +148,7 @@ describe('AwsAlbAuthProvider', () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }), @@ -172,18 +157,16 @@ describe('AwsAlbAuthProvider', () => { }, }); - await provider.refresh(mockRequestWithoutAccessToken, mockResponse); - - expect(mockResponse.status).toHaveBeenCalledWith(401); + await expect( + provider.refresh(mockRequestWithoutAccessToken, mockResponse), + ).rejects.toThrow(AuthenticationError); }); it('JWT is missing', async () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }), @@ -192,18 +175,16 @@ describe('AwsAlbAuthProvider', () => { }, }); - await provider.refresh(mockRequestWithoutJwt, mockResponse); - - expect(mockResponse.status).toHaveBeenCalledWith(401); + await expect( + provider.refresh(mockRequestWithoutJwt, mockResponse), + ).rejects.toThrow(AuthenticationError); }); it('JWT is invalid', async () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }), @@ -216,18 +197,16 @@ describe('AwsAlbAuthProvider', () => { throw new Error('bad JWT'); }); - await provider.refresh(mockRequest, mockResponse); - - expect(mockResponse.status).toHaveBeenCalledWith(401); + await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow( + AuthenticationError, + ); }); it('issuer is missing', async () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }), @@ -238,17 +217,16 @@ describe('AwsAlbAuthProvider', () => { jwtMock.verify.mockReturnValueOnce({}); - await provider.refresh(mockRequest, mockResponse); - expect(mockResponse.status).toHaveBeenCalledWith(401); + await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow( + AuthenticationError, + ); }); it('issuer is invalid', async () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }), @@ -261,17 +239,16 @@ describe('AwsAlbAuthProvider', () => { iss: 'INVALID_ISSUE_URL', }); - await provider.refresh(mockRequest, mockResponse); - expect(mockResponse.status).toHaveBeenCalledWith(401); + await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow( + AuthenticationError, + ); }); it('SignInResolver rejects', async () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }), @@ -282,19 +259,16 @@ describe('AwsAlbAuthProvider', () => { jwtMock.verify.mockReturnValueOnce(mockClaims); - await provider.refresh(mockRequest, mockResponse); - - expect(mockResponse.status).toHaveBeenCalledWith(401); - expect(mockResponse.end).toHaveBeenCalledTimes(1); + await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow( + AuthenticationError, + ); }); it('AuthHandler rejects', async () => { const provider = new AwsAlbAuthProvider({ region: 'eu-west-1', issuer: 'ISSUER_URL', - logger: getVoidLogger(), - catalogIdentityClient, - tokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async () => { throw new Error(); }, @@ -305,10 +279,9 @@ describe('AwsAlbAuthProvider', () => { jwtMock.verify.mockReturnValueOnce(mockClaims); - await provider.refresh(mockRequest, mockResponse); - - expect(mockResponse.status).toHaveBeenCalledWith(401); - expect(mockResponse.end).toHaveBeenCalledTimes(1); + await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow( + AuthenticationError, + ); }); }); }); diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.ts b/plugins/auth-backend/src/providers/aws-alb/provider.ts index 20b6f6f2ef..e65eab4455 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.ts @@ -18,6 +18,7 @@ import { AuthHandler, AuthProviderFactory, AuthProviderRouteHandlers, + AuthResolverContext, AuthResponse, SignInResolver, } from '../types'; @@ -25,11 +26,8 @@ import express from 'express'; import fetch from 'node-fetch'; import * as crypto from 'crypto'; import { KeyObject } from 'crypto'; -import { Logger } from 'winston'; import NodeCache from 'node-cache'; import { JWT } from 'jose'; -import { TokenIssuer } from '../../identity/types'; -import { CatalogIdentityClient } from '../../lib/catalog'; import { Profile as PassportProfile } from 'passport'; import { makeProfileInfo } from '../../lib/passport'; import { AuthenticationError } from '@backstage/errors'; @@ -41,11 +39,9 @@ export const ALB_ACCESS_TOKEN_HEADER = 'x-amzn-oidc-accesstoken'; type Options = { region: string; issuer?: string; - logger: Logger; authHandler: AuthHandler; signInResolver: SignInResolver; - tokenIssuer: TokenIssuer; - catalogIdentityClient: CatalogIdentityClient; + resolverContext: AuthResolverContext; }; export const getJWTHeaders = (input: string): AwsAlbHeaders => { @@ -95,9 +91,7 @@ export type AwsAlbResponse = AuthResponse; export class AwsAlbAuthProvider implements AuthProviderRouteHandlers { private readonly region: string; private readonly issuer?: string; - private readonly tokenIssuer: TokenIssuer; - private readonly catalogIdentityClient: CatalogIdentityClient; - private readonly logger: Logger; + private readonly resolverContext: AuthResolverContext; private readonly keyCache: NodeCache; private readonly authHandler: AuthHandler; private readonly signInResolver: SignInResolver; @@ -107,9 +101,7 @@ export class AwsAlbAuthProvider implements AuthProviderRouteHandlers { this.issuer = options.issuer; this.authHandler = options.authHandler; this.signInResolver = options.signInResolver; - this.tokenIssuer = options.tokenIssuer; - this.catalogIdentityClient = options.catalogIdentityClient; - this.logger = options.logger; + this.resolverContext = options.resolverContext; this.keyCache = new NodeCache({ stdTTL: 3600 }); } @@ -123,9 +115,10 @@ export class AwsAlbAuthProvider implements AuthProviderRouteHandlers { const response = await this.handleResult(result); res.json(response); } catch (e) { - this.logger.error('Exception occurred during AWS ALB token refresh', e); - res.status(401); - res.end(); + throw new AuthenticationError( + 'Exception occurred during AWS ALB token refresh', + e, + ); } } @@ -182,18 +175,13 @@ export class AwsAlbAuthProvider implements AuthProviderRouteHandlers { } private async handleResult(result: AwsAlbResult): Promise { - const context = { - tokenIssuer: this.tokenIssuer, - catalogIdentityClient: this.catalogIdentityClient, - logger: this.logger, - }; - const { profile } = await this.authHandler(result, context); + const { profile } = await this.authHandler(result, this.resolverContext); const backstageIdentity = await this.signInResolver( { result, profile, }, - context, + this.resolverContext, ); return { @@ -258,7 +246,7 @@ export const createAwsAlbProvider = (options?: { resolver: SignInResolver; }; }): AuthProviderFactory => { - return ({ config, tokenIssuer, catalogApi, logger, tokenManager }) => { + return ({ config, resolverContext }) => { const region = config.getString('region'); const issuer = config.getOptionalString('iss'); @@ -268,27 +256,18 @@ export const createAwsAlbProvider = (options?: { ); } - const catalogIdentityClient = new CatalogIdentityClient({ - catalogApi, - tokenManager, - }); - const authHandler: AuthHandler = options?.authHandler ? options.authHandler : async ({ fullProfile }) => ({ profile: makeProfileInfo(fullProfile), }); - const signInResolver = options?.signIn.resolver; - return new AwsAlbAuthProvider({ region, issuer, - signInResolver, + signInResolver: options?.signIn.resolver, authHandler, - tokenIssuer, - catalogIdentityClient, - logger, + resolverContext, }); }; };