moved over IdentityClient as well

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2022-02-09 19:32:59 +01:00
parent 86b40d464f
commit bf5222bfa1
15 changed files with 85 additions and 45 deletions
+5 -3
View File
@@ -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`.
+1
View File
@@ -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",
+1 -1
View File
@@ -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 {
-8
View File
@@ -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';
-1
View File
@@ -21,7 +21,6 @@
*/
export * from './service/router';
export { IdentityClient } from './identity';
export type { TokenIssuer } from './identity';
export * from './providers';
+7
View File
@@ -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>;
}
```
+6 -1
View File
@@ -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"
@@ -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({
@@ -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;
+1
View File
@@ -21,6 +21,7 @@
*/
export { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader';
export { IdentityClient } from './IdentityClient';
export type {
BackstageIdentityResponse,
BackstageSignInResult,
+1 -1
View File
@@ -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';
-1
View File
@@ -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,