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:
+22
-24
@@ -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 }) {
|
||||
|
||||
+2
-9
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user