backend-plugin-api: auth refactor to issueServiceToken with required credentials + getOwnCredentials
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
+5
-3
@@ -51,7 +51,9 @@ describe('authServiceFactory', () => {
|
||||
const searchAuth = await tester.get('search');
|
||||
const catalogAuth = await tester.get('catalog');
|
||||
|
||||
const { token: searchToken } = await searchAuth.issueToken({});
|
||||
const { token: searchToken } = await searchAuth.issueServiceToken({
|
||||
forward: await searchAuth.getOwnCredentials(),
|
||||
});
|
||||
|
||||
await expect(searchAuth.authenticate(searchToken)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -79,7 +81,7 @@ describe('authServiceFactory', () => {
|
||||
const catalogAuth = await tester.get('catalog');
|
||||
|
||||
await expect(
|
||||
catalogAuth.issueToken({
|
||||
catalogAuth.issueServiceToken({
|
||||
forward: {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
@@ -101,7 +103,7 @@ describe('authServiceFactory', () => {
|
||||
|
||||
const catalogAuth = await tester.get('catalog');
|
||||
|
||||
const { token } = await catalogAuth.issueToken({
|
||||
const { token } = await catalogAuth.issueServiceToken({
|
||||
forward: {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
|
||||
@@ -43,12 +43,10 @@ export type InternalBackstageCredentials<TPrincipal = unknown> =
|
||||
|
||||
export function createCredentialsWithServicePrincipal(
|
||||
sub: string,
|
||||
token: string,
|
||||
): InternalBackstageCredentials<BackstageServicePrincipal> {
|
||||
return {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
token,
|
||||
principal: {
|
||||
type: 'service',
|
||||
subject: sub,
|
||||
@@ -112,6 +110,7 @@ class DefaultAuthService implements AuthService {
|
||||
constructor(
|
||||
private readonly tokenManager: TokenManager,
|
||||
private readonly identity: IdentityService,
|
||||
private readonly pluginId: string,
|
||||
) {}
|
||||
|
||||
async authenticate(token: string): Promise<BackstageCredentials> {
|
||||
@@ -120,10 +119,7 @@ class DefaultAuthService implements AuthService {
|
||||
// Legacy service-to-service token
|
||||
if (sub === 'backstage-server' && !aud) {
|
||||
await this.tokenManager.authenticate(token);
|
||||
return createCredentialsWithServicePrincipal(
|
||||
'external:backstage-plugin',
|
||||
token,
|
||||
);
|
||||
return createCredentialsWithServicePrincipal('external:backstage-plugin');
|
||||
}
|
||||
|
||||
// User Backstage token
|
||||
@@ -158,28 +154,31 @@ class DefaultAuthService implements AuthService {
|
||||
return true;
|
||||
}
|
||||
|
||||
async issueToken(options?: {
|
||||
forward?: BackstageCredentials;
|
||||
}): Promise<{ token: string }> {
|
||||
const internalForward =
|
||||
options?.forward && toInternalBackstageCredentials(options.forward);
|
||||
async getOwnCredentials(): Promise<
|
||||
BackstageCredentials<BackstageServicePrincipal>
|
||||
> {
|
||||
return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);
|
||||
}
|
||||
|
||||
if (internalForward) {
|
||||
const { type } = internalForward.principal;
|
||||
if (type === 'user') {
|
||||
async issueServiceToken(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<{ token: string }> {
|
||||
const internalForward = toInternalBackstageCredentials(options.forward);
|
||||
const { type } = internalForward.principal;
|
||||
|
||||
switch (type) {
|
||||
case 'service':
|
||||
return this.tokenManager.getToken();
|
||||
case 'user':
|
||||
if (!internalForward.token) {
|
||||
throw new Error('User credentials is unexpectedly missing token');
|
||||
}
|
||||
return { token: internalForward.token };
|
||||
} else if (type !== 'service') {
|
||||
default:
|
||||
throw new AuthenticationError(
|
||||
`Refused to issue service token for credential type '${type}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { token } = await this.tokenManager.getToken();
|
||||
return { token };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,12 +189,13 @@ export const authServiceFactory = createServiceFactory({
|
||||
config: coreServices.rootConfig,
|
||||
logger: coreServices.rootLogger,
|
||||
discovery: coreServices.discovery,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
createRootContext({ config, logger }) {
|
||||
return ServerTokenManager.fromConfig(config, { logger });
|
||||
},
|
||||
async factory({ discovery }, tokenManager) {
|
||||
async factory({ discovery, plugin }, tokenManager) {
|
||||
const identity = DefaultIdentityClient.create({ discovery });
|
||||
return new DefaultAuthService(tokenManager, identity);
|
||||
return new DefaultAuthService(tokenManager, identity, plugin.getId());
|
||||
},
|
||||
});
|
||||
|
||||
+2
-2
@@ -133,10 +133,10 @@ class DefaultHttpAuthService implements HttpAuthService {
|
||||
}
|
||||
|
||||
async requestHeaders(options: {
|
||||
forward?: BackstageCredentials;
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<Record<string, string>> {
|
||||
return {
|
||||
Authorization: `Bearer ${await this.auth.issueToken(options)}`,
|
||||
Authorization: `Bearer ${await this.auth.issueServiceToken(options)}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,14 @@ export interface AuthService {
|
||||
// (undocumented)
|
||||
authenticate(token: string): Promise<BackstageCredentials>;
|
||||
// (undocumented)
|
||||
getOwnCredentials(): Promise<BackstageCredentials<BackstageServicePrincipal>>;
|
||||
// (undocumented)
|
||||
isPrincipal<TType extends keyof BackstagePrincipalTypes>(
|
||||
credentials: BackstageCredentials,
|
||||
type: TType,
|
||||
): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]>;
|
||||
// (undocumented)
|
||||
issueToken(options: { forward?: BackstageCredentials }): Promise<{
|
||||
issueServiceToken(options: { forward: BackstageCredentials }): Promise<{
|
||||
token: string;
|
||||
}>;
|
||||
}
|
||||
@@ -305,8 +307,8 @@ export interface HttpAuthService {
|
||||
// (undocumented)
|
||||
issueUserCookie(res: Response_2): Promise<void>;
|
||||
// (undocumented)
|
||||
requestHeaders(options?: {
|
||||
forward?: BackstageCredentials;
|
||||
requestHeaders(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<Record<string, string>>;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +71,10 @@ export interface AuthService {
|
||||
type: TType,
|
||||
): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]>;
|
||||
|
||||
getOwnCredentials(): Promise<BackstageCredentials<BackstageServicePrincipal>>;
|
||||
|
||||
// TODO: should the caller provide the target plugin ID?
|
||||
// TODO: how can we make it very difficult to forget to forward credentials
|
||||
issueToken(options: {
|
||||
forward?: BackstageCredentials;
|
||||
issueServiceToken(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<{ token: string }>;
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ export interface HttpAuthService {
|
||||
BackstageCredentials<BackstageHttpAccessToPrincipalTypesMapping[TAllowed]>
|
||||
>;
|
||||
|
||||
requestHeaders(options?: {
|
||||
forward?: BackstageCredentials;
|
||||
requestHeaders(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<Record<string, string>>;
|
||||
|
||||
issueUserCookie(res: Response): Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user