auth-backend: migrate oauth2-proxy provider to use resolver context

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-04-08 13:58:36 +02:00
parent 2d126853ef
commit 3811722d98
2 changed files with 23 additions and 56 deletions
@@ -21,18 +21,14 @@ jest.mock('jose', () => ({
}));
jest.mock('@backstage/catalog-client');
import { AuthenticationError } from '@backstage/errors';
import express from 'express';
import { JWT } from 'jose';
import { Logger } from 'winston';
import { AuthHandler, SignInResolver } from '../types';
import { CatalogIdentityClient } from '../../lib/catalog';
import { TokenIssuer } from '../../identity/types';
import { AuthHandler, AuthResolverContext, SignInResolver } from '../types';
import {
createOauth2ProxyProvider,
Oauth2ProxyAuthProvider,
Oauth2ProxyProviderOptions,
OAuth2ProxyResult,
OAUTH2_PROXY_JWT_HEADER,
} from './provider';
@@ -72,10 +68,10 @@ describe('Oauth2ProxyAuthProvider', () => {
provider = new Oauth2ProxyAuthProvider<any>({
authHandler,
logger,
signInResolver,
catalogIdentityClient: {} as CatalogIdentityClient,
tokenIssuer: {} as TokenIssuer,
resolverContext: {
_: 'resolver-context',
} as unknown as AuthResolverContext,
});
});
@@ -99,17 +95,17 @@ describe('Oauth2ProxyAuthProvider', () => {
it('should throw an error when auth header is missing', async () => {
mockRequest.header.mockReturnValue(undefined);
await provider.refresh(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(401);
await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow(
AuthenticationError,
);
});
it('should throw an error if the bearer token is invalid', async () => {
mockRequest.header.mockReturnValue('Basic asdf=');
await provider.refresh(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(401);
await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow(
AuthenticationError,
);
});
it('should return if auth header is set and valid', async () => {
@@ -152,7 +148,7 @@ describe('Oauth2ProxyAuthProvider', () => {
fullProfile: decodedToken,
},
},
{ catalogIdentityClient: {}, logger, tokenIssuer: {} },
{ _: 'resolver-context' },
);
expect(mockResponse.json).toHaveBeenCalledWith({
backstageIdentity: {
@@ -183,12 +179,10 @@ describe('Oauth2ProxyAuthProvider', () => {
});
it('should create a valid provider', async () => {
const providerOptions = {
const factory = createOauth2ProxyProvider({
authHandler,
signIn: { resolver: signInResolver },
} as Oauth2ProxyProviderOptions<any>;
const factory = createOauth2ProxyProvider(providerOptions);
});
const handler = factory({
logger,
catalogApi: {},
@@ -15,7 +15,6 @@
*/
import express from 'express';
import { Logger } from 'winston';
import { AuthenticationError } from '@backstage/errors';
import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node';
import {
@@ -24,10 +23,9 @@ import {
AuthProviderFactory,
AuthProviderRouteHandlers,
AuthResponse,
AuthResolverContext,
} from '../types';
import { CatalogIdentityClient } from '../../lib/catalog';
import { JWT } from 'jose';
import { TokenIssuer } from '../../identity/types';
import { prepareBackstageIdentityResponse } from '../prepareBackstageIdentityResponse';
export const OAUTH2_PROXY_JWT_HEADER = 'X-OAUTH2-PROXY-ID-TOKEN';
@@ -71,28 +69,22 @@ export type Oauth2ProxyProviderOptions<JWTPayload> = {
};
interface Options<JWTPayload> {
logger: Logger;
resolverContext: AuthResolverContext;
signInResolver: SignInResolver<OAuth2ProxyResult<JWTPayload>>;
authHandler: AuthHandler<OAuth2ProxyResult<JWTPayload>>;
tokenIssuer: TokenIssuer;
catalogIdentityClient: CatalogIdentityClient;
}
export class Oauth2ProxyAuthProvider<JWTPayload>
implements AuthProviderRouteHandlers
{
private readonly logger: Logger;
private readonly catalogIdentityClient: CatalogIdentityClient;
private readonly resolverContext: AuthResolverContext;
private readonly signInResolver: SignInResolver<
OAuth2ProxyResult<JWTPayload>
>;
private readonly authHandler: AuthHandler<OAuth2ProxyResult<JWTPayload>>;
private readonly tokenIssuer: TokenIssuer;
constructor(options: Options<JWTPayload>) {
this.catalogIdentityClient = options.catalogIdentityClient;
this.logger = options.logger;
this.tokenIssuer = options.tokenIssuer;
this.resolverContext = options.resolverContext;
this.signInResolver = options.signInResolver;
this.authHandler = options.authHandler;
}
@@ -104,17 +96,10 @@ export class Oauth2ProxyAuthProvider<JWTPayload>
async refresh(req: express.Request, res: express.Response): Promise<void> {
try {
const result = this.getResult(req);
const response = await this.handleResult(result);
res.json(response);
} catch (e) {
this.logger.error(
`Exception occurred during ${OAUTH2_PROXY_JWT_HEADER} refresh`,
e,
);
res.status(401);
res.end();
throw new AuthenticationError('Refresh failed', e);
}
}
@@ -125,20 +110,14 @@ export class Oauth2ProxyAuthProvider<JWTPayload>
private async handleResult(
result: OAuth2ProxyResult<JWTPayload>,
): Promise<AuthResponse<{ accessToken: string }>> {
const ctx = {
logger: this.logger,
tokenIssuer: this.tokenIssuer,
catalogIdentityClient: this.catalogIdentityClient,
};
const { profile } = await this.authHandler(result, ctx);
const { profile } = await this.authHandler(result, this.resolverContext);
const backstageSignInResult = await this.signInResolver(
{
result,
profile,
},
ctx,
this.resolverContext,
);
return {
@@ -193,18 +172,12 @@ export const createOauth2ProxyProvider =
resolver: SignInResolver<OAuth2ProxyResult<JWTPayload>>;
};
}): AuthProviderFactory =>
({ catalogApi, logger, tokenIssuer, tokenManager }) => {
({ resolverContext }) => {
const signInResolver = options.signIn.resolver;
const authHandler = options.authHandler;
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenManager,
});
return new Oauth2ProxyAuthProvider<JWTPayload>({
logger,
resolverContext,
signInResolver,
authHandler,
tokenIssuer,
catalogIdentityClient,
});
};