backend-test-utils: add MockHttpAuthService

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-16 11:47:37 +01:00
parent de0244f538
commit 25609ed662
3 changed files with 105 additions and 10 deletions
@@ -63,7 +63,7 @@ type RequestWithCredentials = Request & {
[credentialsSymbol]?: Promise<BackstageCredentials>;
};
export class DefaultHttpAuthService implements HttpAuthService {
class DefaultHttpAuthService implements HttpAuthService {
constructor(
private readonly auth: AuthService,
private readonly discovery: DiscoveryService,
@@ -0,0 +1,97 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AuthService,
BackstageCredentials,
BackstagePrincipalTypes,
HttpAuthService,
} from '@backstage/backend-plugin-api';
import { Request, Response } from 'express';
import { mockCredentials } from './mockCredentials';
import { MockAuthService } from './MockAuthService';
import { NotAllowedError, NotImplementedError } from '@backstage/errors';
// TODO: support mock cookie auth?
export class MockHttpAuthService implements HttpAuthService {
#auth: AuthService;
constructor(pluginId: string) {
this.#auth = new MockAuthService(pluginId);
}
async #getCredentials(req: Request) {
const header = req.headers.authorization;
const token =
typeof header === 'string'
? header.match(/^Bearer[ ]+(\S+)$/i)?.[1]
: undefined;
if (!token) {
return mockCredentials.none();
}
return await this.#auth.authenticate(token);
}
async credentials<TAllowed extends keyof BackstagePrincipalTypes = 'unknown'>(
req: Request,
options?: {
allow?: Array<TAllowed>;
allowedAuthMethods?: Array<'token' | 'cookie'>;
},
): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>> {
const credentials = await this.#getCredentials(req);
const allowedPrincipalTypes = options?.allow;
if (!allowedPrincipalTypes) {
return credentials as any;
}
if (this.#auth.isPrincipal(credentials, 'unauthenticated')) {
if (allowedPrincipalTypes.includes('none' as TAllowed)) {
return credentials as any;
}
throw new NotAllowedError(
`This endpoint does not allow 'unauthenticated' credentials`,
);
} else if (this.#auth.isPrincipal(credentials, 'user')) {
if (allowedPrincipalTypes.includes('user' as TAllowed)) {
return credentials as any;
}
throw new NotAllowedError(
`This endpoint does not allow 'user' credentials`,
);
} else if (this.#auth.isPrincipal(credentials, 'service')) {
if (allowedPrincipalTypes.includes('service' as TAllowed)) {
return credentials as any;
}
throw new NotAllowedError(
`This endpoint does not allow 'service' credentials`,
);
}
throw new NotAllowedError(
'Unknown principal type, this should never happen',
);
}
async issueUserCookie(_res: Response): Promise<void> {
throw new NotImplementedError('Not implemented');
}
}
@@ -38,7 +38,6 @@ import {
rootLifecycleServiceFactory,
schedulerServiceFactory,
urlReaderServiceFactory,
httpAuthServiceFactory,
discoveryServiceFactory,
HostDiscovery,
} from '@backstage/backend-app-api';
@@ -47,8 +46,7 @@ import { JsonObject } from '@backstage/types';
import { MockIdentityService } from './MockIdentityService';
import { MockRootLoggerService } from './MockRootLoggerService';
import { MockAuthService } from './MockAuthService';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { DefaultHttpAuthService } from '../../../../backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory';
import { MockHttpAuthService } from './MockHttpAuthService';
/** @internal */
function simpleFactory<
@@ -206,14 +204,14 @@ export namespace mockServices {
}
export function httpAuth(options?: { pluginId?: string }): HttpAuthService {
return new DefaultHttpAuthService(
auth(),
discovery(),
options?.pluginId ?? 'test',
);
return new MockHttpAuthService(options?.pluginId ?? 'test');
}
export namespace httpAuth {
export const factory = httpAuthServiceFactory;
export const factory = createServiceFactory({
service: coreServices.httpAuth,
deps: { plugin: coreServices.pluginMetadata },
factory: ({ plugin }) => new MockHttpAuthService(plugin.getId()),
});
export const mock = simpleMock(coreServices.httpAuth, () => ({
credentials: jest.fn(),
issueUserCookie: jest.fn(),