backend-*-api: updates auth APIs for BEP changes
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: Camila Belo <camilaibs@gmail.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
+9
-6
@@ -51,8 +51,9 @@ describe('authServiceFactory', () => {
|
||||
const searchAuth = await tester.get('search');
|
||||
const catalogAuth = await tester.get('catalog');
|
||||
|
||||
const { token: searchToken } = await searchAuth.issueServiceToken({
|
||||
forward: await searchAuth.getOwnCredentials(),
|
||||
const { token: searchToken } = await searchAuth.getPluginRequestToken({
|
||||
onBehalfOf: await searchAuth.getOwnServiceCredentials(),
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
|
||||
await expect(searchAuth.authenticate(searchToken)).resolves.toEqual(
|
||||
@@ -81,8 +82,8 @@ describe('authServiceFactory', () => {
|
||||
const catalogAuth = await tester.get('catalog');
|
||||
|
||||
await expect(
|
||||
catalogAuth.issueServiceToken({
|
||||
forward: {
|
||||
catalogAuth.getPluginRequestToken({
|
||||
onBehalfOf: {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
authMethod: 'token',
|
||||
@@ -92,6 +93,7 @@ describe('authServiceFactory', () => {
|
||||
userEntityRef: 'user:default/alice',
|
||||
},
|
||||
} as InternalBackstageCredentials<BackstageUserPrincipal>,
|
||||
targetPluginId: 'catalog',
|
||||
}),
|
||||
).resolves.toEqual({ token: 'alice-token' });
|
||||
});
|
||||
@@ -103,8 +105,8 @@ describe('authServiceFactory', () => {
|
||||
|
||||
const catalogAuth = await tester.get('catalog');
|
||||
|
||||
const { token } = await catalogAuth.issueServiceToken({
|
||||
forward: {
|
||||
const { token } = await catalogAuth.getPluginRequestToken({
|
||||
onBehalfOf: {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
authMethod: 'token',
|
||||
@@ -114,6 +116,7 @@ describe('authServiceFactory', () => {
|
||||
subject: 'external:upstream-service',
|
||||
},
|
||||
} as InternalBackstageCredentials<BackstageServicePrincipal>,
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
|
||||
expect(decodeJwt(token)).toEqual(
|
||||
|
||||
@@ -154,16 +154,17 @@ class DefaultAuthService implements AuthService {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getOwnCredentials(): Promise<
|
||||
async getOwnServiceCredentials(): Promise<
|
||||
BackstageCredentials<BackstageServicePrincipal>
|
||||
> {
|
||||
return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);
|
||||
}
|
||||
|
||||
async issueServiceToken(options: {
|
||||
forward: BackstageCredentials;
|
||||
async getPluginRequestToken(options: {
|
||||
onBehalfOf: BackstageCredentials;
|
||||
targetPluginId: string;
|
||||
}): Promise<{ token: string }> {
|
||||
const internalForward = toInternalBackstageCredentials(options.forward);
|
||||
const internalForward = toInternalBackstageCredentials(options.onBehalfOf);
|
||||
const { type } = internalForward.principal;
|
||||
|
||||
switch (type) {
|
||||
|
||||
+1
-9
@@ -63,7 +63,7 @@ type RequestWithCredentials = Request & {
|
||||
[credentialsSymbol]?: Promise<BackstageCredentials>;
|
||||
};
|
||||
|
||||
class DefaultHttpAuthService implements HttpAuthService {
|
||||
export class DefaultHttpAuthService implements HttpAuthService {
|
||||
constructor(
|
||||
private readonly auth: AuthService,
|
||||
private readonly discovery: DiscoveryService,
|
||||
@@ -132,14 +132,6 @@ class DefaultHttpAuthService implements HttpAuthService {
|
||||
return credentials as any;
|
||||
}
|
||||
|
||||
async requestHeaders(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<Record<string, string>> {
|
||||
return {
|
||||
Authorization: `Bearer ${await this.auth.issueServiceToken(options)}`,
|
||||
};
|
||||
}
|
||||
|
||||
async issueUserCookie(res: Response): Promise<void> {
|
||||
const credentials = await this.credentials(res.req, { allow: ['user'] });
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ export function createDatabaseClient(
|
||||
},
|
||||
): knexFactory.Knex<any, any[]>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function createLegacyAuthAdapters<
|
||||
TOptions extends {
|
||||
auth?: AuthService;
|
||||
|
||||
@@ -63,42 +63,43 @@ class AuthCompat implements AuthService {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getOwnCredentials(): Promise<
|
||||
async getOwnServiceCredentials(): Promise<
|
||||
BackstageCredentials<BackstageServicePrincipal>
|
||||
> {
|
||||
return createCredentialsWithServicePrincipal('external:backstage-plugin');
|
||||
}
|
||||
|
||||
async authenticate(token: string): Promise<BackstageCredentials> {
|
||||
const { sub, aud } = decodeJwt(token);
|
||||
const { aud } = decodeJwt(token);
|
||||
|
||||
// Legacy service-to-service token
|
||||
if (sub === 'backstage-server' && !aud) {
|
||||
await this.tokenManager.authenticate(token);
|
||||
return createCredentialsWithServicePrincipal('external:backstage-plugin');
|
||||
if (aud === 'backstage') {
|
||||
// User Backstage token
|
||||
const identity = await this.identity.getIdentity({
|
||||
request: {
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
},
|
||||
} as IdentityApiGetIdentityRequest);
|
||||
|
||||
if (!identity) {
|
||||
throw new AuthenticationError('Invalid user token');
|
||||
}
|
||||
|
||||
return createCredentialsWithUserPrincipal(
|
||||
identity.identity.userEntityRef,
|
||||
token,
|
||||
);
|
||||
}
|
||||
|
||||
// User Backstage token
|
||||
const identity = await this.identity.getIdentity({
|
||||
request: {
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
},
|
||||
} as IdentityApiGetIdentityRequest);
|
||||
await this.tokenManager.authenticate(token);
|
||||
|
||||
if (!identity) {
|
||||
throw new AuthenticationError('Invalid user token');
|
||||
}
|
||||
|
||||
return createCredentialsWithUserPrincipal(
|
||||
identity.identity.userEntityRef,
|
||||
token,
|
||||
);
|
||||
return createCredentialsWithServicePrincipal('external:backstage-plugin');
|
||||
}
|
||||
|
||||
async issueServiceToken(options: {
|
||||
forward: BackstageCredentials;
|
||||
async getPluginRequestToken(options: {
|
||||
onBehalfOf: BackstageCredentials;
|
||||
targetPluginId: string;
|
||||
}): Promise<{ token: string }> {
|
||||
const internalForward = toInternalBackstageCredentials(options.forward);
|
||||
const internalForward = toInternalBackstageCredentials(options.onBehalfOf);
|
||||
const { type } = internalForward.principal;
|
||||
|
||||
switch (type) {
|
||||
@@ -199,14 +200,6 @@ class HttpAuthCompat implements HttpAuthService {
|
||||
return credentials as any;
|
||||
}
|
||||
|
||||
async requestHeaders(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<Record<string, string>> {
|
||||
return {
|
||||
Authorization: `Bearer ${await this.auth.issueServiceToken(options)}`,
|
||||
};
|
||||
}
|
||||
|
||||
async issueUserCookie(_res: Response): Promise<void> {}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,16 +22,21 @@ export interface AuthService {
|
||||
// (undocumented)
|
||||
authenticate(token: string): Promise<BackstageCredentials>;
|
||||
// (undocumented)
|
||||
getOwnCredentials(): Promise<BackstageCredentials<BackstageServicePrincipal>>;
|
||||
getOwnServiceCredentials(): Promise<
|
||||
BackstageCredentials<BackstageServicePrincipal>
|
||||
>;
|
||||
// (undocumented)
|
||||
getPluginRequestToken(options: {
|
||||
onBehalfOf: BackstageCredentials;
|
||||
targetPluginId: string;
|
||||
}): Promise<{
|
||||
token: string;
|
||||
}>;
|
||||
// (undocumented)
|
||||
isPrincipal<TType extends keyof BackstagePrincipalTypes>(
|
||||
credentials: BackstageCredentials,
|
||||
type: TType,
|
||||
): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]>;
|
||||
// (undocumented)
|
||||
issueServiceToken(options: { forward: BackstageCredentials }): Promise<{
|
||||
token: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -295,10 +300,6 @@ export interface HttpAuthService {
|
||||
): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>>;
|
||||
// (undocumented)
|
||||
issueUserCookie(res: Response_2): Promise<void>;
|
||||
// (undocumented)
|
||||
requestHeaders(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<Record<string, string>>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -70,10 +70,12 @@ export interface AuthService {
|
||||
type: TType,
|
||||
): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]>;
|
||||
|
||||
getOwnCredentials(): Promise<BackstageCredentials<BackstageServicePrincipal>>;
|
||||
getOwnServiceCredentials(): Promise<
|
||||
BackstageCredentials<BackstageServicePrincipal>
|
||||
>;
|
||||
|
||||
// TODO: should the caller provide the target plugin ID?
|
||||
issueServiceToken(options: {
|
||||
forward: BackstageCredentials;
|
||||
getPluginRequestToken(options: {
|
||||
onBehalfOf: BackstageCredentials;
|
||||
targetPluginId: string;
|
||||
}): Promise<{ token: string }>;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,5 @@ export interface HttpAuthService {
|
||||
},
|
||||
): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>>;
|
||||
|
||||
requestHeaders(options: {
|
||||
forward: BackstageCredentials;
|
||||
}): Promise<Record<string, string>>;
|
||||
|
||||
issueUserCookie(res: Response): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class MockAuthService implements AuthService {
|
||||
throw new AuthenticationError('Invalid token');
|
||||
}
|
||||
|
||||
async getOwnCredentials(): Promise<
|
||||
async getOwnServiceCredentials(): Promise<
|
||||
BackstageCredentials<BackstageServicePrincipal>
|
||||
> {
|
||||
return {
|
||||
@@ -68,10 +68,11 @@ export class MockAuthService implements AuthService {
|
||||
return true;
|
||||
}
|
||||
|
||||
async issueServiceToken(options: {
|
||||
forward: BackstageCredentials;
|
||||
async getPluginRequestToken(options: {
|
||||
onBehalfOf: BackstageCredentials;
|
||||
targetPluginId: string;
|
||||
}): Promise<{ token: string }> {
|
||||
const principal = options.forward.principal as
|
||||
const principal = options.onBehalfOf.principal as
|
||||
| BackstageUserPrincipal
|
||||
| BackstageServicePrincipal
|
||||
| BackstageNonePrincipal;
|
||||
@@ -80,7 +81,7 @@ export class MockAuthService implements AuthService {
|
||||
case 'user':
|
||||
return { token: 'mock-user-token' };
|
||||
case 'service':
|
||||
return { token: 'mock-service-token' };
|
||||
return { token: `mock-service-token-for-${options.targetPluginId}` };
|
||||
default:
|
||||
throw new AuthenticationError(
|
||||
`Refused to issue service token for credential type '${principal.type}'`,
|
||||
|
||||
@@ -180,9 +180,9 @@ export namespace mockServices {
|
||||
});
|
||||
export const mock = simpleMock(coreServices.auth, () => ({
|
||||
authenticate: jest.fn(),
|
||||
getOwnCredentials: jest.fn(),
|
||||
getOwnServiceCredentials: jest.fn(),
|
||||
isPrincipal: jest.fn() as any,
|
||||
issueServiceToken: jest.fn(),
|
||||
getPluginRequestToken: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -217,7 +217,6 @@ export namespace mockServices {
|
||||
export const mock = simpleMock(coreServices.httpAuth, () => ({
|
||||
credentials: jest.fn(),
|
||||
issueUserCookie: jest.fn(),
|
||||
requestHeaders: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -3287,6 +3287,7 @@ __metadata:
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/integration": "workspace:^"
|
||||
"@backstage/integration-aws-node": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@google-cloud/storage": ^7.0.0
|
||||
"@keyv/memcache": ^1.3.5
|
||||
|
||||
Reference in New Issue
Block a user