backend-{plugin,app}-api: refactored HttpAuth to separate allowing principals from auth method

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Carl-Erik Bergström <cbergstrom@spotify.com>
Co-authored-by: blam <ben@blam.sh>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-12 16:28:16 +01:00
parent 99dadac0af
commit 6b19a73abc
6 changed files with 40 additions and 76 deletions
@@ -17,7 +17,7 @@
import {
AuthService,
BackstageCredentials,
BackstageHttpAccessToPrincipalTypesMapping,
BackstagePrincipalTypes,
DiscoveryService,
HttpAuthService,
coreServices,
@@ -96,33 +96,33 @@ class DefaultHttpAuthService implements HttpAuthService {
this.#extractCredentialsFromRequest(req));
}
async credentials<
TAllowed extends
| keyof BackstageHttpAccessToPrincipalTypesMapping = 'unknown',
>(
async credentials<TAllowed extends keyof BackstagePrincipalTypes = 'unknown'>(
req: Request,
options?: {
allow: Array<TAllowed>;
allow?: Array<TAllowed>;
allowedAuthMethods?: Array<'token' | 'cookie'>;
},
): Promise<
BackstageCredentials<BackstageHttpAccessToPrincipalTypesMapping[TAllowed]>
> {
): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>> {
const credentials = toInternalBackstageCredentials(
await this.#getCredentials(req),
);
// TODO break out into more readable function as we always
// want to check cookie regardless if options are set or not
// Probably asserts function that ensures authMethod = 'token' after cookie check
if (credentials.authMethod === 'cookie') {
if (options && !options.allow.includes('user-cookie' as TAllowed)) {
throw new NotAllowedError(
`This endpoint does not allow 'user-cookie' credentials`,
);
}
} else if (
options &&
!options.allow.includes(credentials.principal.type as TAllowed)
const allowedPrincipalTypes = options?.allow;
const allowedAuthMethods: Array<'token' | 'cookie' | 'none'> =
options?.allowedAuthMethods ?? ['token'];
if (
credentials.authMethod !== 'none' &&
!allowedAuthMethods.includes(credentials.authMethod)
) {
throw new NotAllowedError(
`This endpoint does not allow the '${credentials.authMethod}' auth method`,
);
}
if (
allowedPrincipalTypes &&
!allowedPrincipalTypes.includes(credentials.principal.type as TAllowed)
) {
throw new NotAllowedError(
`This endpoint does not allow '${credentials.principal.type}' credentials`,
@@ -175,10 +175,8 @@ class DefaultHttpAuthService implements HttpAuthService {
export const httpAuthServiceFactory = createServiceFactory({
service: coreServices.httpAuth,
deps: {
config: coreServices.rootConfig,
logger: coreServices.rootLogger,
discovery: coreServices.discovery,
auth: coreServices.auth,
discovery: coreServices.discovery,
plugin: coreServices.pluginMetadata,
},
async factory({ auth, discovery, plugin }) {
@@ -60,18 +60,11 @@ export function createCredentialsBarrier(options: {
predicate(req.path),
);
if (allowsCookie) {
// don't we need a user-cookie allow type here?
await httpAuth.credentials(req, {
allow: ['user-cookie', 'user', 'service'],
});
next();
return;
}
await httpAuth.credentials(req, {
allow: ['user', 'service'],
allowedAuthMethods: allowsCookie ? ['token', 'cookie'] : ['token'],
});
next();
};
+6 -16
View File
@@ -101,15 +101,6 @@ export type BackstageCredentials<TPrincipal = unknown> = {
principal: TPrincipal;
};
// @public (undocumented)
export type BackstageHttpAccessToPrincipalTypesMapping = {
user: BackstageUserPrincipal;
'user-cookie': BackstageUserPrincipal;
service: BackstageServicePrincipal;
unauthenticated: BackstageNonePrincipal;
unknown: unknown;
};
// @public (undocumented)
export type BackstageNonePrincipal = {
type: 'none';
@@ -119,6 +110,8 @@ export type BackstageNonePrincipal = {
export type BackstagePrincipalTypes = {
user: BackstageUserPrincipal;
service: BackstageServicePrincipal;
unauthenticated: BackstageNonePrincipal;
unknown: unknown;
};
// @public (undocumented)
@@ -293,16 +286,13 @@ export interface ExtensionPointConfig {
// @public (undocumented)
export interface HttpAuthService {
// (undocumented)
credentials<
TAllowed extends keyof BackstageHttpAccessToPrincipalTypesMapping = 'unknown',
>(
credentials<TAllowed extends keyof BackstagePrincipalTypes = 'unknown'>(
req: Request_2,
options?: {
allow: Array<TAllowed>;
allow?: Array<TAllowed>;
allowedAuthMethods?: Array<'token' | 'cookie'>;
},
): Promise<
BackstageCredentials<BackstageHttpAccessToPrincipalTypesMapping[TAllowed]>
>;
): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>>;
// (undocumented)
issueUserCookie(res: Response_2): Promise<void>;
// (undocumented)
@@ -55,6 +55,8 @@ export type BackstageCredentials<TPrincipal = unknown> = {
export type BackstagePrincipalTypes = {
user: BackstageUserPrincipal;
service: BackstageServicePrincipal;
unauthenticated: BackstageNonePrincipal;
unknown: unknown;
};
/**
@@ -15,33 +15,17 @@
*/
import { Request, Response } from 'express';
import {
BackstageCredentials,
BackstageServicePrincipal,
BackstageNonePrincipal,
BackstageUserPrincipal,
} from './AuthService';
/** @public */
export type BackstageHttpAccessToPrincipalTypesMapping = {
user: BackstageUserPrincipal;
'user-cookie': BackstageUserPrincipal;
service: BackstageServicePrincipal;
unauthenticated: BackstageNonePrincipal;
unknown: unknown;
};
import { BackstageCredentials, BackstagePrincipalTypes } from './AuthService';
/** @public */
export interface HttpAuthService {
credentials<
TAllowed extends
| keyof BackstageHttpAccessToPrincipalTypesMapping = 'unknown',
>(
credentials<TAllowed extends keyof BackstagePrincipalTypes = 'unknown'>(
req: Request,
options?: { allow: Array<TAllowed> },
): Promise<
BackstageCredentials<BackstageHttpAccessToPrincipalTypesMapping[TAllowed]>
>;
options?: {
allow?: Array<TAllowed>;
allowedAuthMethods?: Array<'token' | 'cookie'>;
},
): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>>;
requestHeaders(options: {
forward: BackstageCredentials;
@@ -35,10 +35,7 @@ export type {
HttpRouterService,
HttpRouterServiceAuthPolicy,
} from './HttpRouterService';
export type {
HttpAuthService,
BackstageHttpAccessToPrincipalTypesMapping,
} from './HttpAuthService';
export type { HttpAuthService } from './HttpAuthService';
export type {
LifecycleService,
LifecycleServiceStartupHook,