From 6bc86fcf2dabc9b2f329e1bd3dc22dc1366a5b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 8 Feb 2022 11:25:13 +0100 Subject: [PATCH 1/6] make IdentityClient.listPublicKeys private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/giant-nails-grow.md | 5 +++++ plugins/auth-backend/api-report.md | 4 ---- plugins/auth-backend/src/identity/IdentityClient.test.ts | 4 ++-- plugins/auth-backend/src/identity/IdentityClient.ts | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 .changeset/giant-nails-grow.md diff --git a/.changeset/giant-nails-grow.md b/.changeset/giant-nails-grow.md new file mode 100644 index 0000000000..c715f78d5c --- /dev/null +++ b/.changeset/giant-nails-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Made `IdentityClient.listPublicKeys` private. It was only used in tests, and should not be part of the API surface of that class. The interface is marked as experimental, and therefore this is a breaking change without a deprecation period. diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index aaacd05815..09f1c5185e 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -10,7 +10,6 @@ import { Config } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; import express from 'express'; import { JsonValue } from '@backstage/types'; -import { JSONWebKey } from 'jose'; import { Logger as Logger_2 } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -432,9 +431,6 @@ export class IdentityClient { static getBearerToken( authorizationHeader: string | undefined, ): string | undefined; - listPublicKeys(): Promise<{ - keys: JSONWebKey[]; - }>; } // Warning: (ae-missing-release-tag) "microsoftEmailSignInResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/auth-backend/src/identity/IdentityClient.test.ts b/plugins/auth-backend/src/identity/IdentityClient.test.ts index 9f5e1ce489..da7a8e7dce 100644 --- a/plugins/auth-backend/src/identity/IdentityClient.test.ts +++ b/plugins/auth-backend/src/identity/IdentityClient.test.ts @@ -83,7 +83,7 @@ describe('IdentityClient', () => { it('should use the correct endpoint', async () => { await factory.issueToken({ claims: { sub: 'foo' } }); const keys = await factory.listPublicKeys(); - const response = await client.listPublicKeys(); + const response = await (client as any).listPublicKeys(); expect(response).toEqual(keys); }); @@ -257,7 +257,7 @@ describe('IdentityClient', () => { }); it('should use the correct endpoint', async () => { - const response = await client.listPublicKeys(); + const response = await (client as any).listPublicKeys(); expect(response).toEqual(defaultServiceResponse); }); }); diff --git a/plugins/auth-backend/src/identity/IdentityClient.ts b/plugins/auth-backend/src/identity/IdentityClient.ts index 552d231e1f..810ae56722 100644 --- a/plugins/auth-backend/src/identity/IdentityClient.ts +++ b/plugins/auth-backend/src/identity/IdentityClient.ts @@ -125,7 +125,7 @@ export class IdentityClient { /** * Lists public part of keys used to sign Backstage Identity tokens */ - async listPublicKeys(): Promise<{ + private async listPublicKeys(): Promise<{ keys: JSONWebKey[]; }> { const url = `${await this.discovery.getBaseUrl( From 9058bb1b5e4683f25098482c27e531f038ee40eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 8 Feb 2022 11:26:33 +0100 Subject: [PATCH 2/6] add empty @backstage/plugin-auth-node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/famous-hats-decide.md | 6 ++++++ plugins/auth-node/.eslintrc.js | 3 +++ plugins/auth-node/README.md | 3 +++ plugins/auth-node/api-report.md | 8 ++++++++ plugins/auth-node/package.json | 32 +++++++++++++++++++++++++++++ plugins/auth-node/src/index.ts | 28 +++++++++++++++++++++++++ plugins/auth-node/src/setupTests.ts | 17 +++++++++++++++ scripts/api-extractor.ts | 1 + 8 files changed, 98 insertions(+) create mode 100644 .changeset/famous-hats-decide.md create mode 100644 plugins/auth-node/.eslintrc.js create mode 100644 plugins/auth-node/README.md create mode 100644 plugins/auth-node/api-report.md create mode 100644 plugins/auth-node/package.json create mode 100644 plugins/auth-node/src/index.ts create mode 100644 plugins/auth-node/src/setupTests.ts diff --git a/.changeset/famous-hats-decide.md b/.changeset/famous-hats-decide.md new file mode 100644 index 0000000000..e6f3a4bd5a --- /dev/null +++ b/.changeset/famous-hats-decide.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-auth-node': minor +--- + +Added this package, to hold shared types and functionality that other backend +packages need to import. diff --git a/plugins/auth-node/.eslintrc.js b/plugins/auth-node/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/auth-node/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/auth-node/README.md b/plugins/auth-node/README.md new file mode 100644 index 0000000000..3558e031b2 --- /dev/null +++ b/plugins/auth-node/README.md @@ -0,0 +1,3 @@ +# Auth Node + +Common functionality and types for the Backstage `auth` plugin. diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md new file mode 100644 index 0000000000..bbbf455b18 --- /dev/null +++ b/plugins/auth-node/api-report.md @@ -0,0 +1,8 @@ +## API Report File for "@backstage/plugin-auth-node" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public +export const COMMON_CONSTANT = 1; +``` diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json new file mode 100644 index 0000000000..3231c6993e --- /dev/null +++ b/plugins/auth-node/package.json @@ -0,0 +1,32 @@ +{ + "name": "@backstage/plugin-auth-node", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.10.6", + "@backstage/config": "^0.1.13", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.13.1" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/auth-node/src/index.ts b/plugins/auth-node/src/index.ts new file mode 100644 index 0000000000..04d073a748 --- /dev/null +++ b/plugins/auth-node/src/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Common functionality and types for the Backstage auth plugin. + * + * @packageDocumentation + */ + +/** + * Dummy. + * + * @public + */ +export const COMMON_CONSTANT = 1; diff --git a/plugins/auth-node/src/setupTests.ts b/plugins/auth-node/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/auth-node/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/scripts/api-extractor.ts b/scripts/api-extractor.ts index 808af8754d..f026adbc9b 100644 --- a/scripts/api-extractor.ts +++ b/scripts/api-extractor.ts @@ -218,6 +218,7 @@ const NO_WARNING_PACKAGES = [ 'packages/types', 'packages/release-manifests', 'packages/version-bridge', + 'plugins/auth-node', 'plugins/catalog-backend-module-ldap', 'plugins/catalog-backend-module-msgraph', 'plugins/catalog-common', From b3f3e420363c6e795af5846ae91bf2236f43eaf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 8 Feb 2022 12:25:27 +0100 Subject: [PATCH 3/6] move `IdentityClient.getBearerToken` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/giant-nails-grow.md | 8 ++- .changeset/little-onions-fly.md | 6 +++ plugins/auth-backend/api-report.md | 3 -- plugins/auth-backend/package.json | 1 + .../src/identity/IdentityClient.test.ts | 32 ------------ .../src/identity/IdentityClient.ts | 14 ------ .../src/providers/oauth2-proxy/provider.ts | 4 +- plugins/auth-node/api-report.md | 4 +- plugins/auth-node/package.json | 4 +- ...BearerTokenFromAuthorizationHeader.test.ts | 50 +++++++++++++++++++ .../getBearerTokenFromAuthorizationHeader.ts | 37 ++++++++++++++ plugins/auth-node/src/index.ts | 7 +-- plugins/permission-backend/package.json | 1 + .../permission-backend/src/service/router.ts | 5 +- plugins/search-backend/package.json | 2 +- plugins/search-backend/src/service/router.ts | 6 ++- 16 files changed, 119 insertions(+), 65 deletions(-) create mode 100644 .changeset/little-onions-fly.md create mode 100644 plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.test.ts create mode 100644 plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.ts diff --git a/.changeset/giant-nails-grow.md b/.changeset/giant-nails-grow.md index c715f78d5c..bed8c422aa 100644 --- a/.changeset/giant-nails-grow.md +++ b/.changeset/giant-nails-grow.md @@ -2,4 +2,10 @@ '@backstage/plugin-auth-backend': minor --- -Made `IdentityClient.listPublicKeys` private. It was only used in tests, and should not be part of the API surface of that class. The interface is marked as experimental, and therefore this is a breaking change without a deprecation period. +- 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 + `getBearerTokenFromAuthorizationHeader` from `@backstage/plugin-auth-node`. + +Since the `IdentityClient` interface is marked as experimental, this is a +breaking change without a deprecation period. diff --git a/.changeset/little-onions-fly.md b/.changeset/little-onions-fly.md new file mode 100644 index 0000000000..8bff96d866 --- /dev/null +++ b/.changeset/little-onions-fly.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-search-backend': patch +--- + +Use `getBearerTokenFromAuthorizationHeader` from `@backstage/plugin-auth-node` instead of the deprecated `IdentityClient` method. diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 09f1c5185e..6a77f1a7cf 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -428,9 +428,6 @@ export type GoogleProviderOptions = { export class IdentityClient { constructor(options: { discovery: PluginEndpointDiscovery; issuer: string }); authenticate(token: string | undefined): Promise; - static getBearerToken( - authorizationHeader: string | undefined, - ): string | undefined; } // Warning: (ae-missing-release-tag) "microsoftEmailSignInResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 3dab33d8da..4f2ef4e76c 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -30,6 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/plugin-auth-node": "^0.0.0", "@backstage/backend-common": "^0.10.7-next.0", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", diff --git a/plugins/auth-backend/src/identity/IdentityClient.test.ts b/plugins/auth-backend/src/identity/IdentityClient.test.ts index da7a8e7dce..cf60fd0852 100644 --- a/plugins/auth-backend/src/identity/IdentityClient.test.ts +++ b/plugins/auth-backend/src/identity/IdentityClient.test.ts @@ -199,38 +199,6 @@ describe('IdentityClient', () => { }); }); - describe('getBearerToken', () => { - it('should return undefined on undefined input', async () => { - const token = IdentityClient.getBearerToken(undefined); - expect(token).toBeUndefined(); - }); - - it('should return undefined on malformed input', async () => { - const token = IdentityClient.getBearerToken('malformed'); - expect(token).toBeUndefined(); - }); - - it('should return undefined on unexpected scheme', async () => { - const token = IdentityClient.getBearerToken('Basic token'); - expect(token).toBeUndefined(); - }); - - it('should return Bearer token', async () => { - const token = IdentityClient.getBearerToken('Bearer token'); - expect(token).toEqual('token'); - }); - - it('should return Bearer token despite extra space', async () => { - const token = IdentityClient.getBearerToken('Bearer \n token '); - expect(token).toEqual('token'); - }); - - it('should return Bearer token despite unconventionial case', async () => { - const token = IdentityClient.getBearerToken('bEARER token'); - expect(token).toEqual('token'); - }); - }); - describe('listPublicKeys', () => { const defaultServiceResponse: { keys: JSONWebKey[]; diff --git a/plugins/auth-backend/src/identity/IdentityClient.ts b/plugins/auth-backend/src/identity/IdentityClient.ts index 810ae56722..5fbd126cdf 100644 --- a/plugins/auth-backend/src/identity/IdentityClient.ts +++ b/plugins/auth-backend/src/identity/IdentityClient.ts @@ -84,20 +84,6 @@ export class IdentityClient { return user; } - /** - * Parses the given authorization header and returns - * the bearer token, or null if no bearer token is given - */ - static getBearerToken( - authorizationHeader: string | undefined, - ): string | undefined { - if (typeof authorizationHeader !== 'string') { - return undefined; - } - const matches = authorizationHeader.match(/Bearer\s+(\S+)/i); - return matches?.[1]; - } - /** * Returns the public signing key matching the given jwt token, * or null if no matching key was found diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts index 24bb4d0362..8c4dcc3249 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts @@ -17,6 +17,7 @@ import express from 'express'; import { Logger } from 'winston'; import { AuthenticationError } from '@backstage/errors'; +import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; import { AuthHandler, SignInResolver, @@ -26,7 +27,6 @@ import { } from '../types'; import { CatalogIdentityClient } from '../../lib/catalog'; import { JWT } from 'jose'; -import { IdentityClient } from '../../identity'; import { TokenIssuer } from '../../identity/types'; import { prepareBackstageIdentityResponse } from '../prepareBackstageIdentityResponse'; @@ -156,7 +156,7 @@ export class Oauth2ProxyAuthProvider private getResult(req: express.Request): OAuth2ProxyResult { const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER); - const jwt = IdentityClient.getBearerToken(authHeader); + const jwt = getBearerTokenFromAuthorizationHeader(authHeader); if (!jwt) { throw new AuthenticationError( diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index bbbf455b18..e32a35880b 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -4,5 +4,7 @@ ```ts // @public -export const COMMON_CONSTANT = 1; +export function getBearerTokenFromAuthorizationHeader( + authorizationHeader: unknown, +): string | undefined; ``` diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 3231c6993e..7e4b4bfe77 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -19,12 +19,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.6", + "@backstage/backend-common": "^0.10.7-next.0", "@backstage/config": "^0.1.13", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.1" + "@backstage/cli": "^0.13.2-next.0" }, "files": [ "dist" diff --git a/plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.test.ts b/plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.test.ts new file mode 100644 index 0000000000..0bd7b5ea93 --- /dev/null +++ b/plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader'; + +describe('getBearerToken', () => { + it('should return undefined on bad input', async () => { + expect(getBearerTokenFromAuthorizationHeader(undefined)).toBeUndefined(); + expect(getBearerTokenFromAuthorizationHeader(7)).toBeUndefined(); + expect( + getBearerTokenFromAuthorizationHeader('Bearer \n token'), + ).toBeUndefined(); + expect( + getBearerTokenFromAuthorizationHeader('Bearer token '), + ).toBeUndefined(); + }); + + it('should return undefined on malformed input', async () => { + const token = getBearerTokenFromAuthorizationHeader('malformed'); + expect(token).toBeUndefined(); + }); + + it('should return undefined on unexpected scheme', async () => { + const token = getBearerTokenFromAuthorizationHeader('Basic token'); + expect(token).toBeUndefined(); + }); + + it('should return Bearer token', async () => { + const token = getBearerTokenFromAuthorizationHeader('Bearer token'); + expect(token).toEqual('token'); + }); + + it('should return Bearer token despite unconventional case', async () => { + const token = getBearerTokenFromAuthorizationHeader('bEARER token'); + expect(token).toEqual('token'); + }); +}); diff --git a/plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.ts b/plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.ts new file mode 100644 index 0000000000..8451cd6ab1 --- /dev/null +++ b/plugins/auth-node/src/getBearerTokenFromAuthorizationHeader.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Parses the given authorization header and returns the bearer token, or + * undefined if no bearer token is given. + * + * @remarks + * + * This function is explicitly built to tolerate bad inputs safely, so you may + * call it directly with e.g. the output of `req.header('authorization')` + * without first checking that it exists. + * + * @public + */ +export function getBearerTokenFromAuthorizationHeader( + authorizationHeader: unknown, +): string | undefined { + if (typeof authorizationHeader !== 'string') { + return undefined; + } + const matches = authorizationHeader.match(/^Bearer[ ]+(\S+)$/i); + return matches?.[1]; +} diff --git a/plugins/auth-node/src/index.ts b/plugins/auth-node/src/index.ts index 04d073a748..83037f75be 100644 --- a/plugins/auth-node/src/index.ts +++ b/plugins/auth-node/src/index.ts @@ -20,9 +20,4 @@ * @packageDocumentation */ -/** - * Dummy. - * - * @public - */ -export const COMMON_CONSTANT = 1; +export { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader'; diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index bc6faae17b..9ee959243d 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -23,6 +23,7 @@ "@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", "@types/express": "*", diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index e22aec8129..10a70b01db 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -27,6 +27,7 @@ import { BackstageIdentityResponse, IdentityClient, } from '@backstage/plugin-auth-backend'; +import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; import { AuthorizeResult, AuthorizeDecision, @@ -157,7 +158,9 @@ export async function createRouter( req: Request, res: Response, ) => { - const token = IdentityClient.getBearerToken(req.header('authorization')); + const token = getBearerTokenFromAuthorizationHeader( + req.header('authorization'), + ); const user = token ? await identity.authenticate(token) : undefined; const parseResult = requestSchema.safeParse(req.body); diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index ee064851fa..3770dbd875 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -24,7 +24,7 @@ "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/search-common": "^0.2.2", - "@backstage/plugin-auth-backend": "^0.10.0-next.0", + "@backstage/plugin-auth-node": "^0.0.0", "@backstage/plugin-permission-common": "^0.4.0-next.0", "@backstage/plugin-permission-node": "^0.4.3-next.0", "@backstage/plugin-search-backend-node": "^0.4.5", diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 28a3247919..3f25c4f24a 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -22,7 +22,7 @@ import { errorHandler } from '@backstage/backend-common'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; import { JsonObject, JsonValue } from '@backstage/types'; -import { IdentityClient } from '@backstage/plugin-auth-backend'; +import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { DocumentTypeInfo, SearchResultSet } from '@backstage/search-common'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; @@ -106,7 +106,9 @@ export async function createRouter( }, pageCursor=${query.pageCursor ?? ''}`, ); - const token = IdentityClient.getBearerToken(req.header('authorization')); + const token = getBearerTokenFromAuthorizationHeader( + req.header('authorization'), + ); try { const resultSet = await engine?.query(query, { token }); From 86b40d464fc71c0fdb3becca964f78cf5c421f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 9 Feb 2022 17:10:18 +0100 Subject: [PATCH 4/6] move over BackstageSignInResult, BackstageIdentityResponse, BackstageUserIdentity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/giant-nails-grow.md | 3 + plugins/auth-backend/api-report.md | 24 +---- .../src/identity/IdentityClient.ts | 2 +- .../src/lib/oauth/OAuthAdapter.ts | 6 +- plugins/auth-backend/src/lib/oauth/types.ts | 7 +- plugins/auth-backend/src/providers/index.ts | 9 +- .../src/providers/oidc/provider.ts | 2 +- .../prepareBackstageIdentityResponse.ts | 7 +- plugins/auth-backend/src/providers/types.ts | 82 ++--------------- plugins/auth-node/api-report.md | 23 +++++ plugins/auth-node/package.json | 1 + plugins/auth-node/src/index.ts | 5 ++ plugins/auth-node/src/types.ts | 88 +++++++++++++++++++ .../permission-backend/src/service/router.ts | 6 +- plugins/permission-node/api-report.md | 2 +- plugins/permission-node/package.json | 2 +- plugins/permission-node/src/policy/types.ts | 2 +- 17 files changed, 149 insertions(+), 122 deletions(-) create mode 100644 plugins/auth-node/src/types.ts diff --git a/.changeset/giant-nails-grow.md b/.changeset/giant-nails-grow.md index bed8c422aa..be4d5793e7 100644 --- a/.changeset/giant-nails-grow.md +++ b/.changeset/giant-nails-grow.md @@ -9,3 +9,6 @@ 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`. diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 6a77f1a7cf..e555eb4a36 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -5,9 +5,10 @@ ```ts /// +import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; +import { BackstageSignInResult } from '@backstage/plugin-auth-node'; import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; -import { Entity } from '@backstage/catalog-model'; import express from 'express'; import { JsonValue } from '@backstage/types'; import { Logger as Logger_2 } from 'winston'; @@ -130,27 +131,6 @@ export type AwsAlbProviderOptions = { // @public @deprecated export type BackstageIdentity = BackstageSignInResult; -// @public -export interface BackstageIdentityResponse extends BackstageSignInResult { - identity: BackstageUserIdentity; -} - -// @public -export interface BackstageSignInResult { - // @deprecated - entity?: Entity; - // @deprecated - id: string; - token: string; -} - -// @public -export type BackstageUserIdentity = { - type: 'user'; - userEntityRef: string; - ownershipEntityRefs: string[]; -}; - // Warning: (ae-missing-release-tag) "BitbucketOAuthResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/auth-backend/src/identity/IdentityClient.ts b/plugins/auth-backend/src/identity/IdentityClient.ts index 5fbd126cdf..6c6c2ca4b1 100644 --- a/plugins/auth-backend/src/identity/IdentityClient.ts +++ b/plugins/auth-backend/src/identity/IdentityClient.ts @@ -18,7 +18,7 @@ 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 '../providers/types'; +import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; const CLOCK_MARGIN_S = 10; diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 82a43c7c64..75a3605483 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -23,10 +23,12 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { - AuthProviderRouteHandlers, - AuthProviderConfig, BackstageIdentityResponse, BackstageSignInResult, +} from '@backstage/plugin-auth-node'; +import { + AuthProviderRouteHandlers, + AuthProviderConfig, } from '../../providers/types'; import { AuthenticationError, diff --git a/plugins/auth-backend/src/lib/oauth/types.ts b/plugins/auth-backend/src/lib/oauth/types.ts index 6973e99569..6e5fe19859 100644 --- a/plugins/auth-backend/src/lib/oauth/types.ts +++ b/plugins/auth-backend/src/lib/oauth/types.ts @@ -16,11 +16,8 @@ import express from 'express'; import { Profile as PassportProfile } from 'passport'; -import { - RedirectInfo, - BackstageSignInResult, - ProfileInfo, -} from '../../providers/types'; +import { BackstageSignInResult } from '@backstage/plugin-auth-node'; +import { RedirectInfo, ProfileInfo } from '../../providers/types'; /** * Common options for passport.js-based OAuth providers diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 1207254e11..37ce09978c 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -48,13 +48,6 @@ export type { // These types are needed for a postMessage from the login pop-up // to the frontend -export type { - AuthResponse, - BackstageIdentity, - BackstageUserIdentity, - BackstageIdentityResponse, - BackstageSignInResult, - ProfileInfo, -} from './types'; +export type { AuthResponse, BackstageIdentity, ProfileInfo } from './types'; export { prepareBackstageIdentityResponse } from './prepareBackstageIdentityResponse'; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index e77812e750..9ccdf25f74 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -234,7 +234,7 @@ export const oAuth2DefaultSignInResolver: SignInResolver< * can be passed while creating a OIDC provider. * * authHandler : called after sign in was successful, a new object must be returned which includes a profile - * signInResolver: called after sign in was successful, expects to return a new {@link BackstageSignInResult} + * signInResolver: called after sign in was successful, expects to return a new {@link @backstage/plugin-auth-node#BackstageSignInResult} * * Both options are optional. There is fallback for authHandler where the default handler expect an e-mail explicitly * otherwise it throws an error diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts index bb99cb3487..f3b234c08e 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts @@ -19,7 +19,10 @@ import { parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; -import { BackstageIdentityResponse, BackstageSignInResult } from './types'; +import { + BackstageIdentityResponse, + BackstageSignInResult, +} from '@backstage/plugin-auth-node'; function parseJwtPayload(token: string) { const [_header, payload, _signature] = token.split('.'); @@ -28,7 +31,7 @@ function parseJwtPayload(token: string) { /** * Parses a Backstage-issued token and decorates the - * {@link BackstageIdentityResponse} with identity information sourced from the + * {@link @backstage/plugin-auth-node#BackstageIdentityResponse} with identity information sourced from the * token. * * @public diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index f3a1a95919..6bef80fd4c 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -19,8 +19,11 @@ import { TokenManager, } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; -import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; +import { + BackstageIdentityResponse, + BackstageSignInResult, +} from '@backstage/plugin-auth-node'; import express from 'express'; import { Logger } from 'winston'; import { TokenIssuer } from '../identity/types'; @@ -162,84 +165,13 @@ export type AuthResponse = { }; /** - * User identity information within Backstage. + * The old exported symbol for {@link @backstage/plugin-auth-node#BackstageSignInResult}. * * @public - */ -export type BackstageUserIdentity = { - /** - * The type of identity that this structure represents. In the frontend app - * this will currently always be 'user'. - */ - type: 'user'; - - /** - * The entityRef of the user in the catalog. - * For example User:default/sandra - */ - userEntityRef: string; - - /** - * The user and group entities that the user claims ownership through - */ - ownershipEntityRefs: string[]; -}; - -/** - * A representation of a successful Backstage sign-in. - * - * Compared to the {@link BackstageIdentityResponse} this type omits - * the decoded identity information embedded in the token. - * - * @public - */ -export interface BackstageSignInResult { - /** - * An opaque ID that uniquely identifies the user within Backstage. - * - * This is typically the same as the user entity `metadata.name`. - * - * @deprecated Use the `identity` field instead - */ - id: string; - - /** - * The entity that the user is represented by within Backstage. - * - * This entity may or may not exist within the Catalog, and it can be used - * to read and store additional metadata about the user. - * - * @deprecated Use the `identity` field instead. - */ - entity?: Entity; - - /** - * The token used to authenticate the user within Backstage. - */ - token: string; -} - -/** - * The old exported symbol for {@link BackstageSignInResult}. - * - * @public - * @deprecated Use the {@link BackstageSignInResult} instead. + * @deprecated Use the {@link @backstage/plugin-auth-node#BackstageSignInResult} instead. */ export type BackstageIdentity = BackstageSignInResult; -/** - * Response object containing the {@link BackstageUserIdentity} and the token - * from the authentication provider. - * - * @public - */ -export interface BackstageIdentityResponse extends BackstageSignInResult { - /** - * A plaintext description of the identity that is encapsulated within the token. - */ - identity: BackstageUserIdentity; -} - /** * Used to display login information to user, i.e. sidebar popup. * @@ -286,7 +218,7 @@ export type SignInInfo = { /** * Describes the function which handles the result of a successful - * authentication. Must return a valid {@link BackstageSignInResult}. + * authentication. Must return a valid {@link @backstage/plugin-auth-node#BackstageSignInResult}. * * @public */ diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index e32a35880b..7795cae2ca 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -3,6 +3,29 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { Entity } from '@backstage/catalog-model'; + +// @public +export interface BackstageIdentityResponse extends BackstageSignInResult { + identity: BackstageUserIdentity; +} + +// @public +export interface BackstageSignInResult { + // @deprecated + entity?: Entity; + // @deprecated + id: string; + token: string; +} + +// @public +export type BackstageUserIdentity = { + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; +}; + // @public export function getBearerTokenFromAuthorizationHeader( authorizationHeader: unknown, diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 7e4b4bfe77..6b66f37c6a 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@backstage/backend-common": "^0.10.7-next.0", + "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "winston": "^3.2.1" }, diff --git a/plugins/auth-node/src/index.ts b/plugins/auth-node/src/index.ts index 83037f75be..0ecee97e40 100644 --- a/plugins/auth-node/src/index.ts +++ b/plugins/auth-node/src/index.ts @@ -21,3 +21,8 @@ */ export { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader'; +export type { + BackstageIdentityResponse, + BackstageSignInResult, + BackstageUserIdentity, +} from './types'; diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts new file mode 100644 index 0000000000..b574c2204d --- /dev/null +++ b/plugins/auth-node/src/types.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; + +/** + * A representation of a successful Backstage sign-in. + * + * Compared to the {@link BackstageIdentityResponse} this type omits + * the decoded identity information embedded in the token. + * + * @public + */ +export interface BackstageSignInResult { + /** + * An opaque ID that uniquely identifies the user within Backstage. + * + * This is typically the same as the user entity `metadata.name`. + * + * @deprecated Use the `identity` field instead + */ + id: string; + + /** + * The entity that the user is represented by within Backstage. + * + * This entity may or may not exist within the Catalog, and it can be used + * to read and store additional metadata about the user. + * + * @deprecated Use the `identity` field instead. + */ + entity?: Entity; + + /** + * The token used to authenticate the user within Backstage. + */ + token: string; +} + +/** + * Response object containing the {@link BackstageUserIdentity} and the token + * from the authentication provider. + * + * @public + */ +export interface BackstageIdentityResponse extends BackstageSignInResult { + /** + * A plaintext description of the identity that is encapsulated within the token. + */ + identity: BackstageUserIdentity; +} + +/** + * User identity information within Backstage. + * + * @public + */ +export type BackstageUserIdentity = { + /** + * The type of identity that this structure represents. In the frontend app + * this will currently always be 'user'. + */ + type: 'user'; + + /** + * The entityRef of the user in the catalog. + * For example User:default/sandra + */ + userEntityRef: string; + + /** + * The user and group entities that the user claims ownership through + */ + ownershipEntityRefs: string[]; +}; diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index 10a70b01db..e68f2497e4 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -23,11 +23,11 @@ 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-backend'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; +} from '@backstage/plugin-auth-node'; import { AuthorizeResult, AuthorizeDecision, diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 43bb548a06..534ad45110 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -7,7 +7,7 @@ import { AuthorizeDecision } from '@backstage/plugin-permission-common'; import { AuthorizeQuery } from '@backstage/plugin-permission-common'; import { AuthorizeRequestOptions } from '@backstage/plugin-permission-common'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { BackstageIdentityResponse } from '@backstage/plugin-auth-backend'; +import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; import { Config } from '@backstage/config'; import express from 'express'; import { Identified } from '@backstage/plugin-permission-common'; diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 6fb58251e3..7977e9057f 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -32,7 +32,7 @@ "@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", "@types/express": "^4.17.6", "express": "^4.17.1", diff --git a/plugins/permission-node/src/policy/types.ts b/plugins/permission-node/src/policy/types.ts index 2e344d6d96..19b12b8f28 100644 --- a/plugins/permission-node/src/policy/types.ts +++ b/plugins/permission-node/src/policy/types.ts @@ -20,7 +20,7 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; -import { BackstageIdentityResponse } from '@backstage/plugin-auth-backend'; +import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; /** * An authorization request to be evaluated by the {@link PermissionPolicy}. From bf5222bfa1e7a41aa8b1afbc535ca917f442cbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 9 Feb 2022 19:32:59 +0100 Subject: [PATCH 5/6] moved over IdentityClient as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/giant-nails-grow.md | 8 +- packages/backend/package.json | 1 + packages/backend/src/plugins/permission.ts | 2 +- plugins/auth-backend/api-report.md | 8 -- plugins/auth-backend/src/identity/index.ts | 1 - plugins/auth-backend/src/index.ts | 1 - plugins/auth-node/api-report.md | 7 ++ plugins/auth-node/package.json | 7 +- .../src}/IdentityClient.test.ts | 76 ++++++++++++++----- .../src}/IdentityClient.ts | 11 +-- plugins/auth-node/src/index.ts | 1 + plugins/permission-backend/api-report.md | 2 +- plugins/permission-backend/package.json | 1 - .../src/service/router.test.ts | 2 +- .../permission-backend/src/service/router.ts | 2 +- 15 files changed, 85 insertions(+), 45 deletions(-) rename plugins/{auth-backend/src/identity => auth-node/src}/IdentityClient.test.ts (81%) rename plugins/{auth-backend/src/identity => auth-node/src}/IdentityClient.ts (95%) diff --git a/.changeset/giant-nails-grow.md b/.changeset/giant-nails-grow.md index be4d5793e7..f96efce2ba 100644 --- a/.changeset/giant-nails-grow.md +++ b/.changeset/giant-nails-grow.md @@ -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`. diff --git a/packages/backend/package.json b/packages/backend/package.json index c2e8711757..1c709195b0 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -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", diff --git a/packages/backend/src/plugins/permission.ts b/packages/backend/src/plugins/permission.ts index 71a9b90311..6ba24ba1f7 100644 --- a/packages/backend/src/plugins/permission.ts +++ b/packages/backend/src/plugins/permission.ts @@ -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 { diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index e555eb4a36..66153fd0af 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -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; -} - // 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) diff --git a/plugins/auth-backend/src/identity/index.ts b/plugins/auth-backend/src/identity/index.ts index 73858d7e07..cb2f369668 100644 --- a/plugins/auth-backend/src/identity/index.ts +++ b/plugins/auth-backend/src/identity/index.ts @@ -15,7 +15,6 @@ */ export { createOidcRouter } from './router'; -export { IdentityClient } from './IdentityClient'; export { TokenFactory } from './TokenFactory'; export { DatabaseKeyStore } from './DatabaseKeyStore'; export { MemoryKeyStore } from './MemoryKeyStore'; diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index 66c1b42dd9..d0cade087e 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -21,7 +21,6 @@ */ export * from './service/router'; -export { IdentityClient } from './identity'; export type { TokenIssuer } from './identity'; export * from './providers'; diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index 7795cae2ca..7a04ba8181 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -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; +} ``` diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 6b66f37c6a..cec1006ef0 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -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" diff --git a/plugins/auth-backend/src/identity/IdentityClient.test.ts b/plugins/auth-node/src/IdentityClient.test.ts similarity index 81% rename from plugins/auth-backend/src/identity/IdentityClient.test.ts rename to plugins/auth-node/src/IdentityClient.test.ts index cf60fd0852..cddc5f33a2 100644 --- a/plugins/auth-backend/src/identity/IdentityClient.test.ts +++ b/plugins/auth-node/src/IdentityClient.test.ts @@ -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 { + use: 'sig'; + alg: string; + kid: string; + kty: string; +} + +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend +class FakeTokenFactory { + private readonly keys = new Array(); + + constructor( + private readonly options: { + issuer: string; + keyDurationSeconds: number; + }, + ) {} + + async issueToken(params: { + claims: { + sub: string; + ent?: string[]; + }; + }): Promise { + 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({ diff --git a/plugins/auth-backend/src/identity/IdentityClient.ts b/plugins/auth-node/src/IdentityClient.ts similarity index 95% rename from plugins/auth-backend/src/identity/IdentityClient.ts rename to plugins/auth-node/src/IdentityClient.ts index 6c6c2ca4b1..ddbccff027 100644 --- a/plugins/auth-backend/src/identity/IdentityClient.ts +++ b/plugins/auth-node/src/IdentityClient.ts @@ -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; diff --git a/plugins/auth-node/src/index.ts b/plugins/auth-node/src/index.ts index 0ecee97e40..5551f2c85d 100644 --- a/plugins/auth-node/src/index.ts +++ b/plugins/auth-node/src/index.ts @@ -21,6 +21,7 @@ */ export { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader'; +export { IdentityClient } from './IdentityClient'; export type { BackstageIdentityResponse, BackstageSignInResult, diff --git a/plugins/permission-backend/api-report.md b/plugins/permission-backend/api-report.md index 4857c8b044..843c9a315c 100644 --- a/plugins/permission-backend/api-report.md +++ b/plugins/permission-backend/api-report.md @@ -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'; diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 9ee959243d..01efa7af04 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -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", diff --git a/plugins/permission-backend/src/service/router.test.ts b/plugins/permission-backend/src/service/router.test.ts index fd09cca828..498fcb748b 100644 --- a/plugins/permission-backend/src/service/router.test.ts +++ b/plugins/permission-backend/src/service/router.test.ts @@ -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, diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index e68f2497e4..413f3e6b45 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -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, From 3c9aed1b16c5e89a05087999625eb4d9ccee0136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 10 Feb 2022 10:36:45 +0100 Subject: [PATCH 6/6] review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/giant-nails-grow.md | 47 ++++++++++++++++--- .../tutorials/authenticate-api-requests.md | 11 +++-- packages/backend/src/plugins/permission.ts | 2 +- plugins/auth-backend/api-report.md | 3 -- plugins/auth-backend/src/providers/index.ts | 2 +- plugins/auth-backend/src/providers/types.ts | 8 ---- plugins/auth-node/api-report.md | 5 +- plugins/auth-node/src/IdentityClient.test.ts | 2 +- plugins/auth-node/src/IdentityClient.ts | 15 +++++- 9 files changed, 69 insertions(+), 26 deletions(-) diff --git a/.changeset/giant-nails-grow.md b/.changeset/giant-nails-grow.md index f96efce2ba..7ea36968b9 100644 --- a/.changeset/giant-nails-grow.md +++ b/.changeset/giant-nails-grow.md @@ -2,15 +2,50 @@ '@backstage/plugin-auth-backend': minor --- -- Moved `IdentityClient`, `BackstageSignInResult`, `BackstageIdentityResponse`, - and `BackstageUserIdentity` to `@backstage/plugin-auth-node`. +The following breaking changes were made, which may imply specifically needing +to make small adjustments in your custom auth providers. + +- **BREAKING**: Moved `IdentityClient`, `BackstageSignInResult`, + `BackstageIdentityResponse`, and `BackstageUserIdentity` to + `@backstage/plugin-auth-node`. +- **BREAKING**: Removed deprecated type `BackstageIdentity`, please use + `BackstageSignInResult` from `@backstage/plugin-auth-node` instead. 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 - `getBearerTokenFromAuthorizationHeader` from `@backstage/plugin-auth-node`. +- **BREAKING**: Made `IdentityClient.listPublicKeys` private. It was only used + in tests, and should not be part of the API surface of that class. +- **BREAKING**: Removed the static `IdentityClient.getBearerToken`. It is now + replaced by `getBearerTokenFromAuthorizationHeader` from + `@backstage/plugin-auth-node`. +- **BREAKING**: Removed the constructor. Please use the `IdentityClient.create` + static method instead. Since the `IdentityClient` interface is marked as experimental, this is a breaking change without a deprecation period. + +In your auth providers, you may need to update your imports and usages as +follows (example code; yours may be slightly different): + +````diff +-import { IdentityClient } from '@backstage/plugin-auth-backend'; ++import { ++ IdentityClient, ++ getBearerTokenFromAuthorizationHeader ++} from '@backstage/plugin-auth-node'; + + // ... + +- const identity = new IdentityClient({ ++ const identity = IdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + });``` + + // ... + + const token = +- IdentityClient.getBearerToken(req.headers.authorization) || ++ getBearerTokenFromAuthorizationHeader(req.headers.authorization) || + req.cookies['token']; +```` diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index 25dd08e054..45ae38b8b7 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -15,7 +15,10 @@ import cookieParser from 'cookie-parser'; import { Request, Response, NextFunction } from 'express'; import { JWT } from 'jose'; import { URL } from 'url'; -import { IdentityClient } from '@backstage/plugin-auth-backend'; +import { + IdentityClient, + getBearerTokenFromAuthorizationHeader, +} from '@backstage/plugin-auth-node'; // ... @@ -44,7 +47,7 @@ async function main() { // ... const discovery = SingleHostDiscovery.fromConfig(config); - const identity = new IdentityClient({ + const identity = IdentityClient.create({ discovery, issuer: await discovery.getExternalBaseUrl('auth'), }); @@ -58,7 +61,7 @@ async function main() { ) => { try { const token = - IdentityClient.getBearerToken(req.headers.authorization) || + getBearerTokenFromAuthorizationHeader(req.headers.authorization) || req.cookies['token']; req.user = await identity.authenticate(token); if (!req.headers.authorization) { @@ -80,7 +83,7 @@ async function main() { const apiRouter = Router(); apiRouter.use(cookieParser()); - // The auth route must be publically available as it is used during login + // The auth route must be publicly available as it is used during login apiRouter.use('/auth', await auth(authEnv)); // Add a simple endpoint to be used when setting a token cookie apiRouter.use('/cookie', authMiddleware, (_req, res) => { diff --git a/packages/backend/src/plugins/permission.ts b/packages/backend/src/plugins/permission.ts index 6ba24ba1f7..276d16ff97 100644 --- a/packages/backend/src/plugins/permission.ts +++ b/packages/backend/src/plugins/permission.ts @@ -40,7 +40,7 @@ export default async function createPlugin( logger, discovery, policy: new AllowAllPermissionPolicy(), - identity: new IdentityClient({ + identity: IdentityClient.create({ discovery, issuer: await discovery.getExternalBaseUrl('auth'), }), diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 66153fd0af..416da59b46 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -128,9 +128,6 @@ export type AwsAlbProviderOptions = { }; }; -// @public @deprecated -export type BackstageIdentity = BackstageSignInResult; - // Warning: (ae-missing-release-tag) "BitbucketOAuthResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 37ce09978c..78814ae7e6 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -48,6 +48,6 @@ export type { // These types are needed for a postMessage from the login pop-up // to the frontend -export type { AuthResponse, BackstageIdentity, ProfileInfo } from './types'; +export type { AuthResponse, ProfileInfo } from './types'; export { prepareBackstageIdentityResponse } from './prepareBackstageIdentityResponse'; diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index 6bef80fd4c..5bd52f0c94 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -164,14 +164,6 @@ export type AuthResponse = { backstageIdentity?: BackstageIdentityResponse; }; -/** - * The old exported symbol for {@link @backstage/plugin-auth-node#BackstageSignInResult}. - * - * @public - * @deprecated Use the {@link @backstage/plugin-auth-node#BackstageSignInResult} instead. - */ -export type BackstageIdentity = BackstageSignInResult; - /** * Used to display login information to user, i.e. sidebar popup. * diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index 7a04ba8181..7840fc7d74 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -34,7 +34,10 @@ export function getBearerTokenFromAuthorizationHeader( // @public export class IdentityClient { - constructor(options: { discovery: PluginEndpointDiscovery; issuer: string }); authenticate(token: string | undefined): Promise; + static create(options: { + discovery: PluginEndpointDiscovery; + issuer: string; + }): IdentityClient; } ``` diff --git a/plugins/auth-node/src/IdentityClient.test.ts b/plugins/auth-node/src/IdentityClient.test.ts index cddc5f33a2..72ef7f2a57 100644 --- a/plugins/auth-node/src/IdentityClient.test.ts +++ b/plugins/auth-node/src/IdentityClient.test.ts @@ -98,7 +98,7 @@ describe('IdentityClient', () => { afterEach(() => server.resetHandlers()); beforeEach(() => { - client = new IdentityClient({ discovery, issuer: mockBaseUrl }); + client = IdentityClient.create({ discovery, issuer: mockBaseUrl }); factory = new FakeTokenFactory({ issuer: mockBaseUrl, keyDurationSeconds, diff --git a/plugins/auth-node/src/IdentityClient.ts b/plugins/auth-node/src/IdentityClient.ts index ddbccff027..d8e841bf75 100644 --- a/plugins/auth-node/src/IdentityClient.ts +++ b/plugins/auth-node/src/IdentityClient.ts @@ -35,7 +35,20 @@ export class IdentityClient { private keyStore: JWKS.KeyStore; private keyStoreUpdated: number; - constructor(options: { discovery: PluginEndpointDiscovery; issuer: string }) { + /** + * Create a new {@link IdentityClient} instance. + */ + static create(options: { + discovery: PluginEndpointDiscovery; + issuer: string; + }): IdentityClient { + return new IdentityClient(options); + } + + private constructor(options: { + discovery: PluginEndpointDiscovery; + issuer: string; + }) { this.discovery = options.discovery; this.issuer = options.issuer; this.keyStore = new JWKS.KeyStore();