moved over IdentityClient as well
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
'@backstage/plugin-auth-backend': minor
|
||||
---
|
||||
|
||||
- Moved `IdentityClient`, `BackstageSignInResult`, `BackstageIdentityResponse`,
|
||||
and `BackstageUserIdentity` to `@backstage/plugin-auth-node`.
|
||||
|
||||
While moving over, `IdentityClient` was also changed in the following ways:
|
||||
|
||||
- Made `IdentityClient.listPublicKeys` private. It was only used in tests, and
|
||||
should not be part of the API surface of that class.
|
||||
- Removed the static `IdentityClient.getBearerToken`. It is now replaced by
|
||||
@@ -9,6 +14,3 @@
|
||||
|
||||
Since the `IdentityClient` interface is marked as experimental, this is a
|
||||
breaking change without a deprecation period.
|
||||
|
||||
- Moved `BackstageSignInResult`, `BackstageIdentityResponse`, and
|
||||
`BackstageUserIdentity` to `@backstage/plugin-auth-node`.
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@backstage/integration": "^0.7.2",
|
||||
"@backstage/plugin-app-backend": "^0.3.24-next.0",
|
||||
"@backstage/plugin-auth-backend": "^0.10.0-next.0",
|
||||
"@backstage/plugin-auth-node": "^0.0.0",
|
||||
"@backstage/plugin-azure-devops-backend": "^0.3.3-next.0",
|
||||
"@backstage/plugin-badges-backend": "^0.1.18-next.0",
|
||||
"@backstage/plugin-catalog-backend": "^0.21.3-next.0",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IdentityClient } from '@backstage/plugin-auth-backend';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { createRouter } from '@backstage/plugin-permission-backend';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import {
|
||||
|
||||
@@ -402,14 +402,6 @@ export type GoogleProviderOptions = {
|
||||
};
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "IdentityClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export class IdentityClient {
|
||||
constructor(options: { discovery: PluginEndpointDiscovery; issuer: string });
|
||||
authenticate(token: string | undefined): Promise<BackstageIdentityResponse>;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "microsoftEmailSignInResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
export { createOidcRouter } from './router';
|
||||
export { IdentityClient } from './IdentityClient';
|
||||
export { TokenFactory } from './TokenFactory';
|
||||
export { DatabaseKeyStore } from './DatabaseKeyStore';
|
||||
export { MemoryKeyStore } from './MemoryKeyStore';
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
*/
|
||||
|
||||
export * from './service/router';
|
||||
export { IdentityClient } from './identity';
|
||||
export type { TokenIssuer } from './identity';
|
||||
export * from './providers';
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
```ts
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
// @public
|
||||
export interface BackstageIdentityResponse extends BackstageSignInResult {
|
||||
@@ -30,4 +31,10 @@ export type BackstageUserIdentity = {
|
||||
export function getBearerTokenFromAuthorizationHeader(
|
||||
authorizationHeader: unknown,
|
||||
): string | undefined;
|
||||
|
||||
// @public
|
||||
export class IdentityClient {
|
||||
constructor(options: { discovery: PluginEndpointDiscovery; issuer: string });
|
||||
authenticate(token: string | undefined): Promise<BackstageIdentityResponse>;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -22,10 +22,15 @@
|
||||
"@backstage/backend-common": "^0.10.7-next.0",
|
||||
"@backstage/catalog-model": "^0.9.10",
|
||||
"@backstage/config": "^0.1.13",
|
||||
"@backstage/errors": "^0.2.0",
|
||||
"jose": "^1.27.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.13.2-next.0"
|
||||
"@backstage/cli": "^0.13.2-next.0",
|
||||
"msw": "^0.35.0",
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
+55
-21
@@ -14,19 +14,61 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JWT, JSONWebKey } from 'jose';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { JSONWebKey, JWK, JWS, JWT } from 'jose';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import {
|
||||
getVoidLogger,
|
||||
PluginEndpointDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IdentityClient } from './IdentityClient';
|
||||
import { MemoryKeyStore } from './MemoryKeyStore';
|
||||
import { TokenFactory } from './TokenFactory';
|
||||
import { KeyStore } from './types';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
interface AnyJWK extends Record<string, string> {
|
||||
use: 'sig';
|
||||
alg: string;
|
||||
kid: string;
|
||||
kty: string;
|
||||
}
|
||||
|
||||
// Simplified copy of TokenFactory in @backstage/plugin-auth-backend
|
||||
class FakeTokenFactory {
|
||||
private readonly keys = new Array<AnyJWK>();
|
||||
|
||||
constructor(
|
||||
private readonly options: {
|
||||
issuer: string;
|
||||
keyDurationSeconds: number;
|
||||
},
|
||||
) {}
|
||||
|
||||
async issueToken(params: {
|
||||
claims: {
|
||||
sub: string;
|
||||
ent?: string[];
|
||||
};
|
||||
}): Promise<string> {
|
||||
const key = await JWK.generate('EC', 'P-256', {
|
||||
use: 'sig',
|
||||
kid: uuid(),
|
||||
alg: 'ES256',
|
||||
});
|
||||
this.keys.push(key.toJWK(false) as unknown as AnyJWK);
|
||||
|
||||
const iss = this.options.issuer;
|
||||
const sub = params.claims.sub;
|
||||
const ent = params.claims.ent;
|
||||
const aud = 'backstage';
|
||||
const iat = Math.floor(Date.now() / 1000);
|
||||
const exp = iat + this.options.keyDurationSeconds;
|
||||
|
||||
return JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
|
||||
alg: key.alg,
|
||||
kid: key.kid,
|
||||
});
|
||||
}
|
||||
|
||||
async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {
|
||||
return { keys: this.keys };
|
||||
}
|
||||
}
|
||||
|
||||
function jwtKid(jwt: string): string {
|
||||
const { header } = JWT.decode(jwt, { complete: true }) as {
|
||||
@@ -48,8 +90,7 @@ const discovery: PluginEndpointDiscovery = {
|
||||
|
||||
describe('IdentityClient', () => {
|
||||
let client: IdentityClient;
|
||||
let factory: TokenFactory;
|
||||
let keyStore: KeyStore;
|
||||
let factory: FakeTokenFactory;
|
||||
const keyDurationSeconds = 5;
|
||||
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
|
||||
@@ -58,12 +99,9 @@ describe('IdentityClient', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
client = new IdentityClient({ discovery, issuer: mockBaseUrl });
|
||||
keyStore = new MemoryKeyStore();
|
||||
factory = new TokenFactory({
|
||||
factory = new FakeTokenFactory({
|
||||
issuer: mockBaseUrl,
|
||||
keyStore: keyStore,
|
||||
keyDurationSeconds,
|
||||
logger,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,11 +146,9 @@ describe('IdentityClient', () => {
|
||||
});
|
||||
|
||||
it('should throw on incorrect issuer', async () => {
|
||||
const hackerFactory = new TokenFactory({
|
||||
const hackerFactory = new FakeTokenFactory({
|
||||
issuer: 'hacker',
|
||||
keyStore,
|
||||
keyDurationSeconds,
|
||||
logger,
|
||||
});
|
||||
return expect(async () => {
|
||||
const token = await hackerFactory.issueToken({
|
||||
@@ -137,11 +173,9 @@ describe('IdentityClient', () => {
|
||||
});
|
||||
|
||||
it('should throw on incorrect signing key', async () => {
|
||||
const hackerFactory = new TokenFactory({
|
||||
const hackerFactory = new FakeTokenFactory({
|
||||
issuer: mockBaseUrl,
|
||||
keyStore: new MemoryKeyStore(),
|
||||
keyDurationSeconds,
|
||||
logger,
|
||||
});
|
||||
return expect(async () => {
|
||||
const token = await hackerFactory.issueToken({
|
||||
+6
-5
@@ -14,19 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
import { JWK, JWT, JWKS, JSONWebKey } from 'jose';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
|
||||
import { JSONWebKey, JWK, JWKS, JWT } from 'jose';
|
||||
import fetch from 'node-fetch';
|
||||
import { BackstageIdentityResponse } from './types';
|
||||
|
||||
const CLOCK_MARGIN_S = 10;
|
||||
|
||||
/**
|
||||
* A identity client to interact with auth-backend
|
||||
* and authenticate backstage identity tokens
|
||||
* An identity client to interact with auth-backend and authenticate Backstage
|
||||
* tokens
|
||||
*
|
||||
* @experimental This is not a stable API yet
|
||||
* @public
|
||||
*/
|
||||
export class IdentityClient {
|
||||
private readonly discovery: PluginEndpointDiscovery;
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
export { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader';
|
||||
export { IdentityClient } from './IdentityClient';
|
||||
export type {
|
||||
BackstageIdentityResponse,
|
||||
BackstageSignInResult,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
```ts
|
||||
import express from 'express';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-backend';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
import { PermissionPolicy } from '@backstage/plugin-permission-node';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"@backstage/backend-common": "^0.10.7-next.0",
|
||||
"@backstage/config": "^0.1.13",
|
||||
"@backstage/errors": "^0.2.0",
|
||||
"@backstage/plugin-auth-backend": "^0.10.0-next.0",
|
||||
"@backstage/plugin-auth-node": "^0.0.0",
|
||||
"@backstage/plugin-permission-common": "^0.4.0",
|
||||
"@backstage/plugin-permission-node": "^0.4.3-next.0",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-backend';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import {
|
||||
ApplyConditionsRequestEntry,
|
||||
|
||||
@@ -23,10 +23,10 @@ import {
|
||||
PluginEndpointDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-backend';
|
||||
import {
|
||||
getBearerTokenFromAuthorizationHeader,
|
||||
BackstageIdentityResponse,
|
||||
IdentityClient,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
|
||||
Reference in New Issue
Block a user