@backstage/backend-defaults 0.13.0-next.1 → 0.13.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/auth.cjs.js +4 -0
- package/dist/auth.cjs.js.map +1 -1
- package/dist/auth.d.ts +54 -2
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
- package/dist/entrypoints/auth/authServiceFactory.cjs.js +8 -5
- package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js +113 -0
- package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/helpers.cjs.js +4 -0
- package/dist/entrypoints/auth/external/helpers.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/jwks.cjs.js +31 -40
- package/dist/entrypoints/auth/external/jwks.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/legacy.cjs.js +21 -42
- package/dist/entrypoints/auth/external/legacy.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/static.cjs.js +14 -16
- package/dist/entrypoints/auth/external/static.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +1 -1
- package/package.json +4 -4
- package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js +0 -78
- package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js.map +0 -1
package/CHANGELOG.md
CHANGED
package/dist/auth.cjs.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var authServiceFactory = require('./entrypoints/auth/authServiceFactory.cjs.js');
|
|
4
|
+
var helpers = require('./entrypoints/auth/external/helpers.cjs.js');
|
|
5
|
+
var ExternalAuthTokenHandler = require('./entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js');
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
exports.authServiceFactory = authServiceFactory.authServiceFactory;
|
|
8
10
|
exports.pluginTokenHandlerDecoratorServiceRef = authServiceFactory.pluginTokenHandlerDecoratorServiceRef;
|
|
11
|
+
exports.createExternalTokenHandler = helpers.createExternalTokenHandler;
|
|
12
|
+
exports.externalTokenHandlersServiceRef = ExternalAuthTokenHandler.externalTokenHandlersServiceRef;
|
|
9
13
|
//# sourceMappingURL=auth.cjs.js.map
|
package/dist/auth.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
import { Config } from '@backstage/config';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @public
|
|
5
|
-
* Issues and verifies {@link https://backstage.
|
|
6
|
+
* Issues and verifies {@link https://backstage.io/docs/auth/service-to-service-auth | service-to-service tokens}.
|
|
6
7
|
*/
|
|
7
8
|
interface PluginTokenHandler {
|
|
8
9
|
verifyToken(token: string): Promise<{
|
|
@@ -37,4 +38,55 @@ declare const pluginTokenHandlerDecoratorServiceRef: _backstage_backend_plugin_a
|
|
|
37
38
|
*/
|
|
38
39
|
declare const authServiceFactory: _backstage_backend_plugin_api.ServiceFactory<_backstage_backend_plugin_api.AuthService, "plugin", "singleton">;
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
/**
|
|
42
|
+
* @public
|
|
43
|
+
* This interface is used to handle external tokens.
|
|
44
|
+
* It is used by the auth service to verify tokens and extract the subject.
|
|
45
|
+
*/
|
|
46
|
+
interface ExternalTokenHandler<TContext> {
|
|
47
|
+
type: string;
|
|
48
|
+
initialize(ctx: {
|
|
49
|
+
options: Config;
|
|
50
|
+
}): TContext;
|
|
51
|
+
verifyToken(token: string, ctx: TContext): Promise<{
|
|
52
|
+
subject: string;
|
|
53
|
+
} | undefined>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates an external token handler with the provided implementation.
|
|
58
|
+
*
|
|
59
|
+
* This helper function simplifies the creation of external token handlers by
|
|
60
|
+
* providing type safety and a consistent API. External token handlers are used
|
|
61
|
+
* to validate tokens from external systems that need to authenticate with Backstage.
|
|
62
|
+
*
|
|
63
|
+
* See {@link https://backstage.io/docs/auth/service-to-service-auth#adding-custom-externaltokenhandler | the service-to-service auth docs}
|
|
64
|
+
* for more information about implementing custom external token handlers.
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
* @param handler - The external token handler implementation with type, initialize, and verifyToken methods
|
|
68
|
+
* @returns The same handler instance, typed as ExternalTokenHandler<TContext>
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const customHandler = createExternalTokenHandler({
|
|
73
|
+
* type: 'custom',
|
|
74
|
+
* initialize({ options }) {
|
|
75
|
+
* return { apiKey: options.getString('apiKey') };
|
|
76
|
+
* },
|
|
77
|
+
* async verifyToken(token, context) {
|
|
78
|
+
* // Custom validation logic here
|
|
79
|
+
* return { subject: 'custom:user' };
|
|
80
|
+
* },
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
declare function createExternalTokenHandler<TContext>(handler: ExternalTokenHandler<TContext>): ExternalTokenHandler<TContext>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @public
|
|
88
|
+
* This service is used to add custom handlers for external token.
|
|
89
|
+
*/
|
|
90
|
+
declare const externalTokenHandlersServiceRef: _backstage_backend_plugin_api.ServiceRef<ExternalTokenHandler<unknown>, "plugin", "multiton">;
|
|
91
|
+
|
|
92
|
+
export { type ExternalTokenHandler, type PluginTokenHandler, authServiceFactory, createExternalTokenHandler, externalTokenHandlersServiceRef, pluginTokenHandlerDecoratorServiceRef };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n constructor(\n private readonly userTokenHandler: UserTokenHandler,\n private readonly pluginTokenHandler: PluginTokenHandler,\n private readonly externalTokenHandler: ExternalTokenHandler,\n private readonly pluginId: string,\n private readonly disableDefaultAuthPolicy: boolean,\n private readonly pluginKeySource: PluginKeySource,\n ) {}\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existence.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":";;;;;;AAuCO,MAAM,kBAAA,CAA0C;AAAA,EACrD,YACmB,gBAAA,EACA,kBAAA,EACA,oBAAA,EACA,QAAA,EACA,0BACA,eAAA,EACjB;AANiB,IAAA,IAAA,CAAA,gBAAA,GAAA,gBAAA;AACA,IAAA,IAAA,CAAA,kBAAA,GAAA,kBAAA;AACA,IAAA,IAAA,CAAA,oBAAA,GAAA,oBAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,wBAAA,GAAA,wBAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AAAA,EAChB;AAAA,EAEH,MAAM,YAAA,CACJ,KAAA,EACA,OAAA,EAG+B;AAC/B,IAAA,MAAM,YAAA,GAAe,MAAM,IAAA,CAAK,kBAAA,CAAmB,YAAY,KAAK,CAAA;AACpE,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,IAAI,aAAa,gBAAA,EAAkB;AACjC,QAAA,MAAMA,WAAAA,GAAa,MAAM,IAAA,CAAK,gBAAA,CAAiB,WAAA;AAAA,UAC7C,YAAA,CAAa;AAAA,SACf;AACA,QAAA,IAAI,CAACA,WAAAA,EAAY;AACf,UAAA,MAAM,IAAIC,0BAAA;AAAA,YACR;AAAA,WACF;AAAA,QACF;AACA,QAAA,OAAOC,0CAAA;AAAA,UACLF,WAAAA,CAAW,aAAA;AAAA,UACX,YAAA,CAAa,gBAAA;AAAA,UACb,IAAA,CAAK,iBAAA,CAAkB,YAAA,CAAa,gBAAgB,CAAA;AAAA,UACpD,YAAA,CAAa;AAAA,SACf;AAAA,MACF;AACA,MAAA,OAAOG,6CAAA,CAAsC,aAAa,OAAO,CAAA;AAAA,IACnE;AAEA,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,gBAAA,CAAiB,YAAY,KAAK,CAAA;AAChE,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,IACE,CAAC,OAAA,EAAS,kBAAA,IACV,KAAK,gBAAA,CAAiB,kBAAA,CAAmB,KAAK,CAAA,EAC9C;AACA,QAAA,MAAM,IAAIF,2BAAoB,4BAA4B,CAAA;AAAA,MAC5D;AAEA,MAAA,OAAOC,0CAAA;AAAA,QACL,UAAA,CAAW,aAAA;AAAA,QACX,KAAA;AAAA,QACA,IAAA,CAAK,kBAAkB,KAAK;AAAA,OAC9B;AAAA,IACF;AAEA,IAAA,MAAM,cAAA,GAAiB,MAAM,IAAA,CAAK,oBAAA,CAAqB,YAAY,KAAK,CAAA;AACxE,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,OAAOC,6CAAA;AAAA,QACL,cAAA,CAAe,OAAA;AAAA,QACf,MAAA;AAAA,QACA,cAAA,CAAe;AAAA,OACjB;AAAA,IACF;AAEA,IAAA,MAAM,IAAIF,2BAAoB,eAAe,CAAA;AAAA,EAC/C;AAAA,EAEA,WAAA,CACE,aACA,IAAA,EACqE;AACrE,IAAA,MAAM,YAAY,WAAA,CAAY,SAAA;AAI9B,IAAA,IAAI,SAAS,SAAA,EAAW;AACtB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IAAI,SAAA,CAAU,SAAS,IAAA,EAAM;AAC3B,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,kBAAA,GAEJ;AACA,IAAA,OAAOG,0CAAA,EAAmC;AAAA,EAC5C;AAAA,EAEA,MAAM,wBAAA,GAEJ;AACA,IAAA,OAAOD,6CAAA,CAAsC,CAAA,OAAA,EAAU,IAAA,CAAK,QAAQ,CAAA,CAAE,CAAA;AAAA,EACxE;AAAA,EAEA,MAAM,sBAAsB,OAAA,EAGG;AAC7B,IAAA,MAAM,EAAE,gBAAe,GAAI,OAAA;AAC3B,IAAA,MAAM,eAAA,GAAkBE,sCAAA,CAA+B,OAAA,CAAQ,UAAU,CAAA;AACzE,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,eAAA,CAAgB,SAAA;AAOjC,IAAA,IAAI,IAAA,KAAS,MAAA,IAAU,IAAA,CAAK,wBAAA,EAA0B;AACpD,MAAA,OAAO,EAAE,OAAO,EAAA,EAAG;AAAA,IACrB;AAIA,IAAA,QAAQ,IAAA;AAAM;AAAA,MAEZ,KAAK,SAAA;AACH,QAAA,OAAO,IAAA,CAAK,mBAAmB,UAAA,CAAW;AAAA,UACxC,UAAU,IAAA,CAAK,QAAA;AAAA,UACf;AAAA,SACD,CAAA;AAAA,MACH,KAAK,MAAA,EAAQ;AACX,QAAA,MAAM,EAAE,OAAM,GAAI,eAAA;AAClB,QAAA,IAAI,CAAC,KAAA,EAAO;AACV,UAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,QAClE;AACA,QAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,gBAAA,CAAiB,sBAAA;AAAA,UAC7C;AAAA,SACF;AACA,QAAA,OAAO,IAAA,CAAK,mBAAmB,UAAA,CAAW;AAAA,UACxC,UAAU,IAAA,CAAK,QAAA;AAAA,UACf,cAAA;AAAA,UACA,UAAA,EAAY;AAAA,YACV,kBAAkB,UAAA,CAAW,KAAA;AAAA,YAC7B,WAAW,UAAA,CAAW;AAAA;AACxB,SACD,CAAA;AAAA,MACH;AAAA,MACA;AACE,QAAA,MAAM,IAAIJ,0BAAA;AAAA,UACR,uDAAuD,IAAI,CAAA,CAAA;AAAA,SAC7D;AAAA;AACJ,EACF;AAAA,EAEA,MAAM,oBACJ,WAAA,EAC6C;AAC7C,IAAA,MAAM,EAAE,KAAA,EAAO,cAAA,EAAe,GAC5BI,uCAA+B,WAAW,CAAA;AAC5C,IAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,MAAA,MAAM,IAAIJ,0BAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AAEA,IAAA,OAAO,IAAA,CAAK,gBAAA,CAAiB,sBAAA,CAAuB,cAAc,CAAA;AAAA,EACpE;AAAA,EAEA,MAAM,qBAAA,GAAyD;AAC7D,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,IAAA,CAAK,gBAAgB,QAAA,EAAS;AACrD,IAAA,OAAO,EAAE,MAAM,IAAA,CAAK,GAAA,CAAI,CAAC,EAAE,GAAA,EAAI,KAAM,GAAG,CAAA,EAAE;AAAA,EAC5C;AAAA,EAEA,kBAAkB,KAAA,EAAe;AAC/B,IAAA,MAAM,EAAE,GAAA,EAAI,GAAIK,cAAA,CAAU,KAAK,CAAA;AAC/B,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,MAAM,IAAIL,2BAAoB,kCAAkC,CAAA;AAAA,IAClE;AACA,IAAA,OAAO,IAAI,IAAA,CAAK,GAAA,GAAM,GAAI,CAAA;AAAA,EAC5B;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalAuthTokenHandler } from './external/ExternalAuthTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n constructor(\n private readonly userTokenHandler: UserTokenHandler,\n private readonly pluginTokenHandler: PluginTokenHandler,\n private readonly externalTokenHandler: ExternalAuthTokenHandler,\n private readonly pluginId: string,\n private readonly disableDefaultAuthPolicy: boolean,\n private readonly pluginKeySource: PluginKeySource,\n ) {}\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existence.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
4
|
var DefaultAuthService = require('./DefaultAuthService.cjs.js');
|
|
5
|
-
var
|
|
5
|
+
var ExternalAuthTokenHandler = require('./external/ExternalAuthTokenHandler.cjs.js');
|
|
6
6
|
var PluginTokenHandler = require('./plugin/PluginTokenHandler.cjs.js');
|
|
7
7
|
var createPluginKeySource = require('./plugin/keys/createPluginKeySource.cjs.js');
|
|
8
8
|
var UserTokenHandler = require('./user/UserTokenHandler.cjs.js');
|
|
@@ -25,7 +25,8 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
25
25
|
discovery: backendPluginApi.coreServices.discovery,
|
|
26
26
|
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
27
27
|
database: backendPluginApi.coreServices.database,
|
|
28
|
-
pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef
|
|
28
|
+
pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef,
|
|
29
|
+
externalTokenHandlers: ExternalAuthTokenHandler.externalTokenHandlersServiceRef
|
|
29
30
|
},
|
|
30
31
|
async factory({
|
|
31
32
|
config,
|
|
@@ -33,7 +34,8 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
33
34
|
plugin,
|
|
34
35
|
logger,
|
|
35
36
|
database,
|
|
36
|
-
pluginTokenHandlerDecorator
|
|
37
|
+
pluginTokenHandlerDecorator,
|
|
38
|
+
externalTokenHandlers
|
|
37
39
|
}) {
|
|
38
40
|
const disableDefaultAuthPolicy = config.getOptionalBoolean(
|
|
39
41
|
"backend.auth.dangerouslyDisableDefaultAuthPolicy"
|
|
@@ -58,10 +60,11 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
58
60
|
discovery
|
|
59
61
|
})
|
|
60
62
|
);
|
|
61
|
-
const externalTokens =
|
|
63
|
+
const externalTokens = ExternalAuthTokenHandler.ExternalAuthTokenHandler.create({
|
|
62
64
|
ownPluginId: plugin.getId(),
|
|
63
65
|
config,
|
|
64
|
-
logger
|
|
66
|
+
logger,
|
|
67
|
+
externalTokenHandlers
|
|
65
68
|
});
|
|
66
69
|
return new DefaultAuthService.DefaultAuthService(
|
|
67
70
|
userTokens,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport {
|
|
1
|
+
{"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport {\n ExternalAuthTokenHandler,\n externalTokenHandlersServiceRef,\n} from './external/ExternalAuthTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef,\n externalTokenHandlers: externalTokenHandlersServiceRef,\n },\n async factory({\n config,\n discovery,\n plugin,\n logger,\n database,\n pluginTokenHandlerDecorator,\n externalTokenHandlers,\n }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n logger,\n });\n\n const pluginTokens = pluginTokenHandlerDecorator(\n DefaultPluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n }),\n );\n\n const externalTokens = ExternalAuthTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n externalTokenHandlers,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceRef","createServiceFactory","coreServices","externalTokenHandlersServiceRef","createPluginKeySource","UserTokenHandler","DefaultPluginTokenHandler","ExternalAuthTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAqCO,MAAM,wCAAwCA,iCAAA,CAEnD;AAAA,EACA,EAAA,EAAI,uCAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAA,CAAqB;AAAA,IACnB,OAAA;AAAA,IACA,MAAM,EAAC;AAAA,IACP,SAAS,YAAY;AACnB,MAAA,OAAO,CAAA,IAAA,KAAQ,IAAA;AAAA,IACjB;AAAA,GACD;AACL,CAAC;AAWM,MAAM,qBAAqBA,qCAAA,CAAqB;AAAA,EACrD,SAASC,6BAAA,CAAa,IAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,QAAQA,6BAAA,CAAa,UAAA;AAAA,IACrB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,WAAWA,6BAAA,CAAa,SAAA;AAAA,IACxB,QAAQA,6BAAA,CAAa,cAAA;AAAA,IACrB,UAAUA,6BAAA,CAAa,QAAA;AAAA,IACvB,2BAAA,EAA6B,qCAAA;AAAA,IAC7B,qBAAA,EAAuBC;AAAA,GACzB;AAAA,EACA,MAAM,OAAA,CAAQ;AAAA,IACZ,MAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,2BAAA;AAAA,IACA;AAAA,GACF,EAAG;AACD,IAAA,MAAM,2BACJ,MAAA,CAAO,kBAAA;AAAA,MACL;AAAA,KACF,IAAK,KAAA;AAEP,IAAA,MAAM,WAAA,GAAc,EAAE,KAAA,EAAO,CAAA,EAAE;AAE/B,IAAA,MAAM,SAAA,GAAY,MAAMC,2CAAA,CAAsB;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAA,GAAaC,kCAAiB,MAAA,CAAO;AAAA,MACzC,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,YAAA,GAAe,2BAAA;AAAA,MACnBC,6CAA0B,MAAA,CAAO;AAAA,QAC/B,WAAA,EAAa,OAAO,KAAA,EAAM;AAAA,QAC1B,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAEA,IAAA,MAAM,cAAA,GAAiBC,kDAAyB,MAAA,CAAO;AAAA,MACrD,WAAA,EAAa,OAAO,KAAA,EAAM;AAAA,MAC1B,MAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAA,EAAM;AAAA,MACb,wBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF,CAAC;;;;;"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
var legacy = require('./legacy.cjs.js');
|
|
6
|
+
var _static = require('./static.cjs.js');
|
|
7
|
+
var jwks = require('./jwks.cjs.js');
|
|
8
|
+
var helpers = require('./helpers.cjs.js');
|
|
9
|
+
|
|
10
|
+
const NEW_CONFIG_KEY = "backend.auth.externalAccess";
|
|
11
|
+
const OLD_CONFIG_KEY = "backend.auth.keys";
|
|
12
|
+
let loggedDeprecationWarning = false;
|
|
13
|
+
const externalTokenHandlersServiceRef = backendPluginApi.createServiceRef({
|
|
14
|
+
id: "core.auth.externalTokenHandlers",
|
|
15
|
+
multiton: true
|
|
16
|
+
});
|
|
17
|
+
const defaultHandlers = {
|
|
18
|
+
static: _static.staticTokenHandler,
|
|
19
|
+
legacy: legacy.legacyTokenHandler,
|
|
20
|
+
jwks: jwks.jwksTokenHandler
|
|
21
|
+
};
|
|
22
|
+
class ExternalAuthTokenHandler {
|
|
23
|
+
constructor(ownPluginId, contexts) {
|
|
24
|
+
this.ownPluginId = ownPluginId;
|
|
25
|
+
this.contexts = contexts;
|
|
26
|
+
}
|
|
27
|
+
static create(options) {
|
|
28
|
+
const {
|
|
29
|
+
ownPluginId,
|
|
30
|
+
config,
|
|
31
|
+
externalTokenHandlers: customHandlers = [],
|
|
32
|
+
logger
|
|
33
|
+
} = options;
|
|
34
|
+
const handlersTypes = customHandlers.reduce(
|
|
35
|
+
(acc, handler) => {
|
|
36
|
+
if (acc[handler.type]) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Duplicate external token handler type '${handler.type}', each handler must have a unique type`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
acc[handler.type] = handler;
|
|
42
|
+
return acc;
|
|
43
|
+
},
|
|
44
|
+
{ ...defaultHandlers }
|
|
45
|
+
);
|
|
46
|
+
const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];
|
|
47
|
+
const contexts = handlerConfigs.map(
|
|
48
|
+
(handlerConfig) => {
|
|
49
|
+
const type = handlerConfig.getString("type");
|
|
50
|
+
const handler = handlersTypes[type];
|
|
51
|
+
if (!handler) {
|
|
52
|
+
const valid = Object.keys(handlersTypes).map((h) => `'${h}'`).join(", ");
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
context: handler.initialize({
|
|
59
|
+
options: handlerConfig.getConfig("options")
|
|
60
|
+
}),
|
|
61
|
+
handler,
|
|
62
|
+
allAccessRestrictions: handlerConfig ? helpers.readAccessRestrictionsFromConfig(handlerConfig) : void 0
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];
|
|
67
|
+
if (legacyConfigs.length && !loggedDeprecationWarning) {
|
|
68
|
+
loggedDeprecationWarning = true;
|
|
69
|
+
logger.warn(
|
|
70
|
+
`DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
for (const legacyConfig of legacyConfigs) {
|
|
74
|
+
contexts.push({
|
|
75
|
+
context: legacy.legacyTokenHandler.initialize({
|
|
76
|
+
legacy: true,
|
|
77
|
+
options: legacyConfig
|
|
78
|
+
}),
|
|
79
|
+
handler: legacy.legacyTokenHandler,
|
|
80
|
+
allAccessRestrictions: helpers.readAccessRestrictionsFromConfig(legacyConfig)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return new ExternalAuthTokenHandler(ownPluginId, contexts);
|
|
84
|
+
}
|
|
85
|
+
async verifyToken(token) {
|
|
86
|
+
for (const { handler, allAccessRestrictions, context } of this.contexts) {
|
|
87
|
+
const result = await handler.verifyToken(token, context);
|
|
88
|
+
if (result) {
|
|
89
|
+
if (allAccessRestrictions) {
|
|
90
|
+
const accessRestrictions = allAccessRestrictions.get(
|
|
91
|
+
this.ownPluginId
|
|
92
|
+
);
|
|
93
|
+
if (!accessRestrictions) {
|
|
94
|
+
const valid = [...allAccessRestrictions.keys()].map((k) => `'${k}'`).join(", ");
|
|
95
|
+
throw new errors.NotAllowedError(
|
|
96
|
+
`This token's access is restricted to plugin(s) ${valid}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
...result,
|
|
101
|
+
accessRestrictions
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
exports.ExternalAuthTokenHandler = ExternalAuthTokenHandler;
|
|
112
|
+
exports.externalTokenHandlersServiceRef = externalTokenHandlersServiceRef;
|
|
113
|
+
//# sourceMappingURL=ExternalAuthTokenHandler.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalAuthTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/external/ExternalAuthTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstagePrincipalAccessRestrictions,\n createServiceRef,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport { legacyTokenHandler } from './legacy';\nimport { staticTokenHandler } from './static';\nimport { jwksTokenHandler } from './jwks';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\nimport { readAccessRestrictionsFromConfig } from './helpers';\n\nconst NEW_CONFIG_KEY = 'backend.auth.externalAccess';\nconst OLD_CONFIG_KEY = 'backend.auth.keys';\nlet loggedDeprecationWarning = false;\n\n/**\n * @public\n * This service is used to add custom handlers for external token.\n */\nexport const externalTokenHandlersServiceRef = createServiceRef<\n ExternalTokenHandler<unknown>\n>({\n id: 'core.auth.externalTokenHandlers',\n multiton: true,\n});\n\nconst defaultHandlers: Record<string, ExternalTokenHandler<unknown>> = {\n static: staticTokenHandler,\n legacy: legacyTokenHandler,\n jwks: jwksTokenHandler,\n};\n\ntype ContextMapEntry<T> = {\n context: T;\n handler: ExternalTokenHandler<T>;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\n/**\n * Handles all types of external caller token types (i.e. not Backstage user\n * tokens, nor Backstage backend plugin tokens).\n *\n * @internal\n */\nexport class ExternalAuthTokenHandler {\n static create(options: {\n ownPluginId: string;\n config: RootConfigService;\n logger: LoggerService;\n externalTokenHandlers?: ExternalTokenHandler<unknown>[];\n }): ExternalAuthTokenHandler {\n const {\n ownPluginId,\n config,\n externalTokenHandlers: customHandlers = [],\n logger,\n } = options;\n\n const handlersTypes = customHandlers.reduce<\n Record<string, ExternalTokenHandler<unknown>>\n >(\n (acc, handler) => {\n if (acc[handler.type]) {\n throw new Error(\n `Duplicate external token handler type '${handler.type}', each handler must have a unique type`,\n );\n }\n acc[handler.type] = handler;\n return acc;\n },\n { ...defaultHandlers },\n );\n\n const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];\n const contexts: ContextMapEntry<unknown>[] = handlerConfigs.map(\n handlerConfig => {\n const type = handlerConfig.getString('type');\n\n const handler = handlersTypes[type];\n if (!handler) {\n const valid = Object.keys(handlersTypes)\n .map(h => `'${h}'`)\n .join(', ');\n throw new Error(\n `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,\n );\n }\n return {\n context: handler.initialize({\n options: handlerConfig.getConfig('options'),\n }),\n handler,\n allAccessRestrictions: handlerConfig\n ? readAccessRestrictionsFromConfig(handlerConfig)\n : undefined,\n };\n },\n );\n\n // Load the old keys too\n const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];\n\n if (legacyConfigs.length && !loggedDeprecationWarning) {\n loggedDeprecationWarning = true;\n\n logger.warn(\n `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`,\n );\n }\n\n for (const legacyConfig of legacyConfigs) {\n contexts.push({\n context: legacyTokenHandler.initialize({\n legacy: true,\n options: legacyConfig,\n }),\n handler: legacyTokenHandler,\n allAccessRestrictions: readAccessRestrictionsFromConfig(legacyConfig),\n });\n }\n\n return new ExternalAuthTokenHandler(ownPluginId, contexts);\n }\n\n constructor(\n private readonly ownPluginId: string,\n private readonly contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[],\n ) {}\n\n async verifyToken(token: string): Promise<\n | {\n subject: string;\n accessRestrictions?: BackstagePrincipalAccessRestrictions;\n }\n | undefined\n > {\n for (const { handler, allAccessRestrictions, context } of this.contexts) {\n const result = await handler.verifyToken(token, context);\n if (result) {\n if (allAccessRestrictions) {\n const accessRestrictions = allAccessRestrictions.get(\n this.ownPluginId,\n );\n if (!accessRestrictions) {\n const valid = [...allAccessRestrictions.keys()]\n .map(k => `'${k}'`)\n .join(', ');\n throw new NotAllowedError(\n `This token's access is restricted to plugin(s) ${valid}`,\n );\n }\n\n return {\n ...result,\n accessRestrictions,\n };\n }\n\n return result;\n }\n }\n\n return undefined;\n }\n}\n"],"names":["createServiceRef","staticTokenHandler","legacyTokenHandler","jwksTokenHandler","readAccessRestrictionsFromConfig","NotAllowedError"],"mappings":";;;;;;;;;AA6BA,MAAM,cAAA,GAAiB,6BAAA;AACvB,MAAM,cAAA,GAAiB,mBAAA;AACvB,IAAI,wBAAA,GAA2B,KAAA;AAMxB,MAAM,kCAAkCA,iCAAA,CAE7C;AAAA,EACA,EAAA,EAAI,iCAAA;AAAA,EACJ,QAAA,EAAU;AACZ,CAAC;AAED,MAAM,eAAA,GAAiE;AAAA,EACrE,MAAA,EAAQC,0BAAA;AAAA,EACR,MAAA,EAAQC,yBAAA;AAAA,EACR,IAAA,EAAMC;AACR,CAAA;AAcO,MAAM,wBAAA,CAAyB;AAAA,EAgFpC,WAAA,CACmB,aACA,QAAA,EAKjB;AANiB,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA,EAKhB;AAAA,EAtFH,OAAO,OAAO,OAAA,EAKe;AAC3B,IAAA,MAAM;AAAA,MACJ,WAAA;AAAA,MACA,MAAA;AAAA,MACA,qBAAA,EAAuB,iBAAiB,EAAC;AAAA,MACzC;AAAA,KACF,GAAI,OAAA;AAEJ,IAAA,MAAM,gBAAgB,cAAA,CAAe,MAAA;AAAA,MAGnC,CAAC,KAAK,OAAA,KAAY;AAChB,QAAA,IAAI,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,EAAG;AACrB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,uCAAA,EAA0C,QAAQ,IAAI,CAAA,uCAAA;AAAA,WACxD;AAAA,QACF;AACA,QAAA,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,GAAI,OAAA;AACpB,QAAA,OAAO,GAAA;AAAA,MACT,CAAA;AAAA,MACA,EAAE,GAAG,eAAA;AAAgB,KACvB;AAEA,IAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,sBAAA,CAAuB,cAAc,KAAK,EAAC;AACzE,IAAA,MAAM,WAAuC,cAAA,CAAe,GAAA;AAAA,MAC1D,CAAA,aAAA,KAAiB;AACf,QAAA,MAAM,IAAA,GAAO,aAAA,CAAc,SAAA,CAAU,MAAM,CAAA;AAE3C,QAAA,MAAM,OAAA,GAAU,cAAc,IAAI,CAAA;AAClC,QAAA,IAAI,CAAC,OAAA,EAAS;AACZ,UAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,aAAa,CAAA,CACpC,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAG,CAAA,CACjB,IAAA,CAAK,IAAI,CAAA;AACZ,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,cAAA,EAAiB,IAAI,CAAA,KAAA,EAAQ,cAAc,qBAAqB,KAAK,CAAA;AAAA,WACvE;AAAA,QACF;AACA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,QAAQ,UAAA,CAAW;AAAA,YAC1B,OAAA,EAAS,aAAA,CAAc,SAAA,CAAU,SAAS;AAAA,WAC3C,CAAA;AAAA,UACD,OAAA;AAAA,UACA,qBAAA,EAAuB,aAAA,GACnBC,wCAAA,CAAiC,aAAa,CAAA,GAC9C;AAAA,SACN;AAAA,MACF;AAAA,KACF;AAGA,IAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,sBAAA,CAAuB,cAAc,KAAK,EAAC;AAExE,IAAA,IAAI,aAAA,CAAc,MAAA,IAAU,CAAC,wBAAA,EAA0B;AACrD,MAAA,wBAAA,GAA2B,IAAA;AAE3B,MAAA,MAAA,CAAO,IAAA;AAAA,QACL,CAAA,yBAAA,EAA4B,cAAc,CAAA,6BAAA,EAAgC,cAAc,CAAA,4DAAA;AAAA,OAC1F;AAAA,IACF;AAEA,IAAA,KAAA,MAAW,gBAAgB,aAAA,EAAe;AACxC,MAAA,QAAA,CAAS,IAAA,CAAK;AAAA,QACZ,OAAA,EAASF,0BAAmB,UAAA,CAAW;AAAA,UACrC,MAAA,EAAQ,IAAA;AAAA,UACR,OAAA,EAAS;AAAA,SACV,CAAA;AAAA,QACD,OAAA,EAASA,yBAAA;AAAA,QACT,qBAAA,EAAuBE,yCAAiC,YAAY;AAAA,OACrE,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,IAAI,wBAAA,CAAyB,WAAA,EAAa,QAAQ,CAAA;AAAA,EAC3D;AAAA,EAWA,MAAM,YAAY,KAAA,EAMhB;AACA,IAAA,KAAA,MAAW,EAAE,OAAA,EAAS,qBAAA,EAAuB,OAAA,EAAQ,IAAK,KAAK,QAAA,EAAU;AACvE,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,WAAA,CAAY,OAAO,OAAO,CAAA;AACvD,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,IAAI,qBAAA,EAAuB;AACzB,UAAA,MAAM,qBAAqB,qBAAA,CAAsB,GAAA;AAAA,YAC/C,IAAA,CAAK;AAAA,WACP;AACA,UAAA,IAAI,CAAC,kBAAA,EAAoB;AACvB,YAAA,MAAM,KAAA,GAAQ,CAAC,GAAG,qBAAA,CAAsB,MAAM,CAAA,CAC3C,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAG,CAAA,CACjB,KAAK,IAAI,CAAA;AACZ,YAAA,MAAM,IAAIC,sBAAA;AAAA,cACR,kDAAkD,KAAK,CAAA;AAAA,aACzD;AAAA,UACF;AAEA,UAAA,OAAO;AAAA,YACL,GAAG,MAAA;AAAA,YACH;AAAA,WACF;AAAA,QACF;AAEA,QAAA,OAAO,MAAA;AAAA,MACT;AAAA,IACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;;"}
|
|
@@ -86,7 +86,11 @@ function readPermissionAttributes(externalAccessEntryConfig) {
|
|
|
86
86
|
};
|
|
87
87
|
return Object.keys(result).length ? result : void 0;
|
|
88
88
|
}
|
|
89
|
+
function createExternalTokenHandler(handler) {
|
|
90
|
+
return handler;
|
|
91
|
+
}
|
|
89
92
|
|
|
93
|
+
exports.createExternalTokenHandler = createExternalTokenHandler;
|
|
90
94
|
exports.readAccessRestrictionsFromConfig = readAccessRestrictionsFromConfig;
|
|
91
95
|
exports.readStringOrStringArrayFromConfig = readStringOrStringArrayFromConfig;
|
|
92
96
|
//# sourceMappingURL=helpers.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs.js","sources":["../../../../src/entrypoints/auth/external/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {
|
|
1
|
+
{"version":3,"file":"helpers.cjs.js","sources":["../../../../src/entrypoints/auth/external/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\n\n/**\n * Parses and returns the `accessRestrictions` configuration from an\n * `externalAccess` entry, or undefined if there wasn't one.\n *\n * @internal\n */\nexport function readAccessRestrictionsFromConfig(\n externalAccessEntryConfig: Config,\n): AccessRestrictionsMap | undefined {\n const configs =\n externalAccessEntryConfig.getOptionalConfigArray('accessRestrictions') ??\n [];\n\n const result: AccessRestrictionsMap = new Map();\n for (const config of configs) {\n const validKeys = ['plugin', 'permission', 'permissionAttribute'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`,\n );\n }\n }\n\n const pluginId = config.getString('plugin');\n const permissionNames = readPermissionNames(config);\n const permissionAttributes = readPermissionAttributes(config);\n\n if (result.has(pluginId)) {\n throw new Error(\n `Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`,\n );\n }\n\n result.set(pluginId, {\n ...(permissionNames ? { permissionNames } : {}),\n ...(permissionAttributes ? { permissionAttributes } : {}),\n });\n }\n\n return result.size ? result : undefined;\n}\n\n/**\n * Reads a config value as a string or an array of strings, and deduplicates and\n * splits by comma/space into a string array. Can also validate against a known\n * set of values. Returns undefined if the key didn't exist or if the array\n * would have ended up being empty.\n *\n * @internal\n */\nexport function readStringOrStringArrayFromConfig<T extends string>(\n root: Config,\n key: string,\n validValues?: readonly T[],\n): T[] | undefined {\n if (!root.has(key)) {\n return undefined;\n }\n\n const rawValues = Array.isArray(root.get(key))\n ? root.getStringArray(key)\n : [root.getString(key)];\n\n const values = [\n ...new Set(\n rawValues\n .map(v => v.split(/[ ,]/))\n .flat()\n .filter(Boolean),\n ),\n ];\n\n if (!values.length) {\n return undefined;\n }\n\n if (validValues?.length) {\n for (const value of values) {\n if (!validValues.includes(value as T)) {\n const valid = validValues.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`,\n );\n }\n }\n }\n\n return values as T[];\n}\n\nfunction readPermissionNames(externalAccessEntryConfig: Config) {\n return readStringOrStringArrayFromConfig(\n externalAccessEntryConfig,\n 'permission',\n );\n}\n\nfunction readPermissionAttributes(externalAccessEntryConfig: Config) {\n const config = externalAccessEntryConfig.getOptionalConfig(\n 'permissionAttribute',\n );\n if (!config) {\n return undefined;\n }\n\n const validKeys = ['action'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`,\n );\n }\n }\n\n const action = readStringOrStringArrayFromConfig(config, 'action', [\n 'create',\n 'read',\n 'update',\n 'delete',\n ]);\n\n const result = {\n ...(action ? { action } : {}),\n };\n\n return Object.keys(result).length ? result : undefined;\n}\n\n/**\n * Creates an external token handler with the provided implementation.\n *\n * This helper function simplifies the creation of external token handlers by\n * providing type safety and a consistent API. External token handlers are used\n * to validate tokens from external systems that need to authenticate with Backstage.\n *\n * See {@link https://backstage.io/docs/auth/service-to-service-auth#adding-custom-externaltokenhandler | the service-to-service auth docs}\n * for more information about implementing custom external token handlers.\n *\n * @public\n * @param handler - The external token handler implementation with type, initialize, and verifyToken methods\n * @returns The same handler instance, typed as ExternalTokenHandler<TContext>\n *\n * @example\n * ```ts\n * const customHandler = createExternalTokenHandler({\n * type: 'custom',\n * initialize({ options }) {\n * return { apiKey: options.getString('apiKey') };\n * },\n * async verifyToken(token, context) {\n * // Custom validation logic here\n * return { subject: 'custom:user' };\n * },\n * });\n * ```\n */\nexport function createExternalTokenHandler<TContext>(\n handler: ExternalTokenHandler<TContext>,\n): ExternalTokenHandler<TContext> {\n return handler;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,28 +3,22 @@
|
|
|
3
3
|
var jose = require('jose');
|
|
4
4
|
var helpers = require('./helpers.cjs.js');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!
|
|
6
|
+
const jwksTokenHandler = helpers.createExternalTokenHandler({
|
|
7
|
+
type: "jwks",
|
|
8
|
+
initialize({ options }) {
|
|
9
|
+
if (!options.getString("url").match(/^\S+$/)) {
|
|
10
10
|
throw new Error(
|
|
11
11
|
"Illegal JWKS URL, must be a set of non-space characters"
|
|
12
12
|
);
|
|
13
13
|
}
|
|
14
|
-
const algorithms = helpers.readStringOrStringArrayFromConfig(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
18
|
-
const
|
|
19
|
-
const audiences = helpers.readStringOrStringArrayFromConfig(
|
|
20
|
-
config,
|
|
21
|
-
"options.audience"
|
|
22
|
-
);
|
|
23
|
-
const subjectPrefix = config.getOptionalString("options.subjectPrefix");
|
|
24
|
-
const url = new URL(config.getString("options.url"));
|
|
14
|
+
const algorithms = helpers.readStringOrStringArrayFromConfig(options, "algorithm");
|
|
15
|
+
const issuers = helpers.readStringOrStringArrayFromConfig(options, "issuer");
|
|
16
|
+
const audiences = helpers.readStringOrStringArrayFromConfig(options, "audience");
|
|
17
|
+
const subjectPrefix = options.getOptionalString("subjectPrefix");
|
|
18
|
+
const url = new URL(options.getString("url"));
|
|
25
19
|
const jwks = jose.createRemoteJWKSet(url);
|
|
26
|
-
const allAccessRestrictions = helpers.readAccessRestrictionsFromConfig(
|
|
27
|
-
|
|
20
|
+
const allAccessRestrictions = helpers.readAccessRestrictionsFromConfig(options);
|
|
21
|
+
return {
|
|
28
22
|
algorithms,
|
|
29
23
|
audiences,
|
|
30
24
|
issuers,
|
|
@@ -32,32 +26,29 @@ class JWKSHandler {
|
|
|
32
26
|
subjectPrefix,
|
|
33
27
|
url,
|
|
34
28
|
allAccessRestrictions
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async verifyToken(token) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
allAccessRestrictions: entry.allAccessRestrictions
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
continue;
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
async verifyToken(token, context) {
|
|
32
|
+
try {
|
|
33
|
+
const {
|
|
34
|
+
payload: { sub }
|
|
35
|
+
} = await jose.jwtVerify(token, context.jwks, {
|
|
36
|
+
algorithms: context.algorithms,
|
|
37
|
+
issuer: context.issuers,
|
|
38
|
+
audience: context.audiences
|
|
39
|
+
});
|
|
40
|
+
if (sub) {
|
|
41
|
+
const prefix = context.subjectPrefix ? `external:${context.subjectPrefix}:` : "external:";
|
|
42
|
+
return {
|
|
43
|
+
subject: `${prefix}${sub}`
|
|
44
|
+
};
|
|
56
45
|
}
|
|
46
|
+
} catch {
|
|
47
|
+
return void 0;
|
|
57
48
|
}
|
|
58
49
|
return void 0;
|
|
59
50
|
}
|
|
60
|
-
}
|
|
51
|
+
});
|
|
61
52
|
|
|
62
|
-
exports.
|
|
53
|
+
exports.jwksTokenHandler = jwksTokenHandler;
|
|
63
54
|
//# sourceMappingURL=jwks.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks.cjs.js","sources":["../../../../src/entrypoints/auth/external/jwks.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose';\nimport {
|
|
1
|
+
{"version":3,"file":"jwks.cjs.js","sources":["../../../../src/entrypoints/auth/external/jwks.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose';\nimport {\n createExternalTokenHandler,\n readAccessRestrictionsFromConfig,\n readStringOrStringArrayFromConfig,\n} from './helpers';\nimport { AccessRestrictionsMap } from './types';\n\ntype JWKSTokenContext = {\n algorithms?: string[];\n audiences?: string[];\n issuers?: string[];\n subjectPrefix?: string;\n url: URL;\n jwks: JWTVerifyGetKey;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\nexport const jwksTokenHandler = createExternalTokenHandler<JWKSTokenContext>({\n type: 'jwks',\n initialize({ options }): JWKSTokenContext {\n if (!options.getString('url').match(/^\\S+$/)) {\n throw new Error(\n 'Illegal JWKS URL, must be a set of non-space characters',\n );\n }\n\n const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm');\n const issuers = readStringOrStringArrayFromConfig(options, 'issuer');\n const audiences = readStringOrStringArrayFromConfig(options, 'audience');\n const subjectPrefix = options.getOptionalString('subjectPrefix');\n const url = new URL(options.getString('url'));\n const jwks = createRemoteJWKSet(url);\n const allAccessRestrictions = readAccessRestrictionsFromConfig(options);\n return {\n algorithms,\n audiences,\n issuers,\n jwks,\n subjectPrefix,\n url,\n allAccessRestrictions,\n };\n },\n\n async verifyToken(token: string, context: JWKSTokenContext) {\n try {\n const {\n payload: { sub },\n } = await jwtVerify(token, context.jwks, {\n algorithms: context.algorithms,\n issuer: context.issuers,\n audience: context.audiences,\n });\n\n if (sub) {\n const prefix = context.subjectPrefix\n ? `external:${context.subjectPrefix}:`\n : 'external:';\n return {\n subject: `${prefix}${sub}`,\n };\n }\n } catch {\n return undefined;\n }\n return undefined;\n },\n});\n"],"names":["createExternalTokenHandler","readStringOrStringArrayFromConfig","createRemoteJWKSet","readAccessRestrictionsFromConfig","jwtVerify"],"mappings":";;;;;AAkCO,MAAM,mBAAmBA,kCAAA,CAA6C;AAAA,EAC3E,IAAA,EAAM,MAAA;AAAA,EACN,UAAA,CAAW,EAAE,OAAA,EAAQ,EAAqB;AACxC,IAAA,IAAI,CAAC,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAA,CAAE,KAAA,CAAM,OAAO,CAAA,EAAG;AAC5C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAAaC,yCAAA,CAAkC,OAAA,EAAS,WAAW,CAAA;AACzE,IAAA,MAAM,OAAA,GAAUA,yCAAA,CAAkC,OAAA,EAAS,QAAQ,CAAA;AACnE,IAAA,MAAM,SAAA,GAAYA,yCAAA,CAAkC,OAAA,EAAS,UAAU,CAAA;AACvE,IAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,iBAAA,CAAkB,eAAe,CAAA;AAC/D,IAAA,MAAM,MAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAC,CAAA;AAC5C,IAAA,MAAM,IAAA,GAAOC,wBAAmB,GAAG,CAAA;AACnC,IAAA,MAAM,qBAAA,GAAwBC,yCAAiC,OAAO,CAAA;AACtE,IAAA,OAAO;AAAA,MACL,UAAA;AAAA,MACA,SAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,aAAA;AAAA,MACA,GAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EAEA,MAAM,WAAA,CAAY,KAAA,EAAe,OAAA,EAA2B;AAC1D,IAAA,IAAI;AACF,MAAA,MAAM;AAAA,QACJ,OAAA,EAAS,EAAE,GAAA;AAAI,OACjB,GAAI,MAAMC,cAAA,CAAU,KAAA,EAAO,QAAQ,IAAA,EAAM;AAAA,QACvC,YAAY,OAAA,CAAQ,UAAA;AAAA,QACpB,QAAQ,OAAA,CAAQ,OAAA;AAAA,QAChB,UAAU,OAAA,CAAQ;AAAA,OACnB,CAAA;AAED,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,MAAM,SAAS,OAAA,CAAQ,aAAA,GACnB,CAAA,SAAA,EAAY,OAAA,CAAQ,aAAa,CAAA,CAAA,CAAA,GACjC,WAAA;AACJ,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,CAAA,EAAG,MAAM,CAAA,EAAG,GAAG,CAAA;AAAA,SAC1B;AAAA,MACF;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAC;;;;"}
|
|
@@ -1,48 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var jose = require('jose');
|
|
4
|
-
var helpers = require('./helpers.cjs.js');
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
config.getString("options.secret"),
|
|
12
|
-
config.getString("options.subject"),
|
|
13
|
-
allAccessRestrictions
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
// used only for the old backend.auth.keys array
|
|
17
|
-
addOld(config) {
|
|
18
|
-
this.#doAdd(config.getString("secret"), "external:backstage-plugin");
|
|
19
|
-
}
|
|
20
|
-
#doAdd(secret, subject, allAccessRestrictions) {
|
|
5
|
+
const legacyTokenHandler = {
|
|
6
|
+
type: "legacy",
|
|
7
|
+
initialize(ctx) {
|
|
8
|
+
const secret = ctx.options.getString("secret");
|
|
9
|
+
const subject = ctx.legacy ? "external:backstage-plugin" : ctx.options.getString("subject");
|
|
21
10
|
if (!secret.match(/^\S+$/)) {
|
|
22
11
|
throw new Error("Illegal secret, must be a valid base64 string");
|
|
23
12
|
} else if (!subject.match(/^\S+$/)) {
|
|
24
13
|
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
25
14
|
}
|
|
26
|
-
let key;
|
|
27
15
|
try {
|
|
28
|
-
|
|
16
|
+
return {
|
|
17
|
+
key: jose.base64url.decode(secret),
|
|
18
|
+
subject
|
|
19
|
+
};
|
|
29
20
|
} catch {
|
|
30
21
|
throw new Error("Illegal secret, must be a valid base64 string");
|
|
31
22
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"Legacy externalAccess token was declared more than once"
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
this.#entries.push({
|
|
38
|
-
key,
|
|
39
|
-
result: {
|
|
40
|
-
subject,
|
|
41
|
-
allAccessRestrictions
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
async verifyToken(token) {
|
|
23
|
+
},
|
|
24
|
+
async verifyToken(token, context) {
|
|
46
25
|
try {
|
|
47
26
|
const { alg } = jose.decodeProtectedHeader(token);
|
|
48
27
|
if (alg !== "HS256") {
|
|
@@ -55,19 +34,19 @@ class LegacyTokenHandler {
|
|
|
55
34
|
} catch (e) {
|
|
56
35
|
return void 0;
|
|
57
36
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
37
|
+
try {
|
|
38
|
+
await jose.jwtVerify(token, context.key);
|
|
39
|
+
return {
|
|
40
|
+
subject: context.subject
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error.code !== "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") {
|
|
44
|
+
throw error;
|
|
66
45
|
}
|
|
67
46
|
}
|
|
68
47
|
return void 0;
|
|
69
48
|
}
|
|
70
|
-
}
|
|
49
|
+
};
|
|
71
50
|
|
|
72
|
-
exports.
|
|
51
|
+
exports.legacyTokenHandler = legacyTokenHandler;
|
|
73
52
|
//# sourceMappingURL=legacy.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy.cjs.js","sources":["../../../../src/entrypoints/auth/external/legacy.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose';\nimport {
|
|
1
|
+
{"version":3,"file":"legacy.cjs.js","sources":["../../../../src/entrypoints/auth/external/legacy.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose';\n\nimport { ExternalTokenHandler } from './types';\n\nexport type LegacyConfigWrapper = {\n legacy: boolean;\n config: Config;\n};\n\ntype LegacyTokenHandlerContext = {\n key: Uint8Array;\n\n subject: string;\n};\n\ntype LegacyTokenHandlerOverloaded =\n ExternalTokenHandler<LegacyTokenHandlerContext> & {\n initialize(ctx: {\n options: Config;\n legacy: true;\n }): LegacyTokenHandlerContext;\n };\n\nexport const legacyTokenHandler: LegacyTokenHandlerOverloaded = {\n type: 'legacy',\n initialize(ctx: {\n options: Config;\n legacy?: true;\n }): LegacyTokenHandlerContext {\n const secret = ctx.options.getString('secret');\n const subject = ctx.legacy\n ? 'external:backstage-plugin'\n : ctx.options.getString('subject');\n\n if (!secret.match(/^\\S+$/)) {\n throw new Error('Illegal secret, must be a valid base64 string');\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n }\n\n try {\n return {\n key: base64url.decode(secret),\n subject,\n };\n } catch {\n throw new Error('Illegal secret, must be a valid base64 string');\n }\n },\n\n async verifyToken(token: string, context: LegacyTokenHandlerContext) {\n // First do a duck typing check to see if it remotely looks like a legacy token\n try {\n // We do a fair amount of checking upfront here. Since we aren't certain\n // that it's even the right type of key that we're looking at, we can't\n // defer eg the alg check to jwtVerify, because it won't be possible to\n // discern different reasons for key verification failures from each other\n // easily\n const { alg } = decodeProtectedHeader(token);\n if (alg !== 'HS256') {\n return undefined;\n }\n const { sub, aud } = decodeJwt(token);\n if (sub !== 'backstage-server' || aud) {\n return undefined;\n }\n } catch (e) {\n // Doesn't look like a jwt at all\n return undefined;\n }\n\n try {\n await jwtVerify(token, context.key);\n return {\n subject: context.subject,\n };\n } catch (error) {\n if (error.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {\n throw error;\n }\n }\n\n // None of the signing keys matched\n return undefined;\n },\n};\n"],"names":["base64url","decodeProtectedHeader","decodeJwt","jwtVerify"],"mappings":";;;;AAwCO,MAAM,kBAAA,GAAmD;AAAA,EAC9D,IAAA,EAAM,QAAA;AAAA,EACN,WAAW,GAAA,EAGmB;AAC5B,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,QAAQ,CAAA;AAC7C,IAAA,MAAM,UAAU,GAAA,CAAI,MAAA,GAChB,8BACA,GAAA,CAAI,OAAA,CAAQ,UAAU,SAAS,CAAA;AAEnC,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,EAAG;AAC1B,MAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,IACjE,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AAClC,MAAA,MAAM,IAAI,MAAM,wDAAwD,CAAA;AAAA,IAC1E;AAEA,IAAA,IAAI;AACF,MAAA,OAAO;AAAA,QACL,GAAA,EAAKA,cAAA,CAAU,MAAA,CAAO,MAAM,CAAA;AAAA,QAC5B;AAAA,OACF;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,IACjE;AAAA,EACF,CAAA;AAAA,EAEA,MAAM,WAAA,CAAY,KAAA,EAAe,OAAA,EAAoC;AAEnE,IAAA,IAAI;AAMF,MAAA,MAAM,EAAE,GAAA,EAAI,GAAIC,0BAAA,CAAsB,KAAK,CAAA;AAC3C,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,OAAO,KAAA,CAAA;AAAA,MACT;AACA,MAAA,MAAM,EAAE,GAAA,EAAK,GAAA,EAAI,GAAIC,eAAU,KAAK,CAAA;AACpC,MAAA,IAAI,GAAA,KAAQ,sBAAsB,GAAA,EAAK;AACrC,QAAA,OAAO,KAAA,CAAA;AAAA,MACT;AAAA,IACF,SAAS,CAAA,EAAG;AAEV,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI;AACF,MAAA,MAAMC,cAAA,CAAU,KAAA,EAAO,OAAA,CAAQ,GAAG,CAAA;AAClC,MAAA,OAAO;AAAA,QACL,SAAS,OAAA,CAAQ;AAAA,OACnB;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,KAAA,CAAM,SAAS,uCAAA,EAAyC;AAC1D,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF;AAGA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;"}
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
var helpers = require('./helpers.cjs.js');
|
|
4
4
|
|
|
5
5
|
const MIN_TOKEN_LENGTH = 8;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const token =
|
|
10
|
-
const subject =
|
|
11
|
-
const allAccessRestrictions = helpers.readAccessRestrictionsFromConfig(config);
|
|
6
|
+
const staticTokenHandler = helpers.createExternalTokenHandler({
|
|
7
|
+
type: "static",
|
|
8
|
+
initialize(ctx) {
|
|
9
|
+
const token = ctx.options.getString("token");
|
|
10
|
+
const subject = ctx.options.getString("subject");
|
|
12
11
|
if (!token.match(/^\S+$/)) {
|
|
13
12
|
throw new Error("Illegal token, must be a set of non-space characters");
|
|
14
13
|
} else if (token.length < MIN_TOKEN_LENGTH) {
|
|
@@ -17,17 +16,16 @@ class StaticTokenHandler {
|
|
|
17
16
|
);
|
|
18
17
|
} else if (!subject.match(/^\S+$/)) {
|
|
19
18
|
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
20
|
-
} else if (this.#entries.has(token)) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
"Static externalAccess token was declared more than once"
|
|
23
|
-
);
|
|
24
19
|
}
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
async verifyToken(token) {
|
|
28
|
-
|
|
20
|
+
return { token, subject };
|
|
21
|
+
},
|
|
22
|
+
async verifyToken(token, context) {
|
|
23
|
+
if (token === context.token) {
|
|
24
|
+
return { subject: context.subject };
|
|
25
|
+
}
|
|
26
|
+
return void 0;
|
|
29
27
|
}
|
|
30
|
-
}
|
|
28
|
+
});
|
|
31
29
|
|
|
32
|
-
exports.
|
|
30
|
+
exports.staticTokenHandler = staticTokenHandler;
|
|
33
31
|
//# sourceMappingURL=static.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.cjs.js","sources":["../../../../src/entrypoints/auth/external/static.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {
|
|
1
|
+
{"version":3,"file":"static.cjs.js","sources":["../../../../src/entrypoints/auth/external/static.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { createExternalTokenHandler } from './helpers';\n\nconst MIN_TOKEN_LENGTH = 8;\n\nexport const staticTokenHandler = createExternalTokenHandler<{\n token: string;\n subject: string;\n}>({\n type: 'static',\n initialize(ctx: { options: Config }): { token: string; subject: string } {\n const token = ctx.options.getString('token');\n const subject = ctx.options.getString('subject');\n\n if (!token.match(/^\\S+$/)) {\n throw new Error('Illegal token, must be a set of non-space characters');\n } else if (token.length < MIN_TOKEN_LENGTH) {\n throw new Error(\n `Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`,\n );\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n }\n\n return { token, subject };\n },\n async verifyToken(\n token: string,\n context: { token: string; subject: string },\n ) {\n if (token === context.token) {\n return { subject: context.subject };\n }\n return undefined;\n },\n});\n"],"names":["createExternalTokenHandler"],"mappings":";;;;AAmBA,MAAM,gBAAA,GAAmB,CAAA;AAElB,MAAM,qBAAqBA,kCAAA,CAG/B;AAAA,EACD,IAAA,EAAM,QAAA;AAAA,EACN,WAAW,GAAA,EAA8D;AACvE,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,OAAO,CAAA;AAC3C,IAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,SAAS,CAAA;AAE/C,IAAA,IAAI,CAAC,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA,EAAG;AACzB,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE,CAAA,MAAA,IAAW,KAAA,CAAM,MAAA,GAAS,gBAAA,EAAkB;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,mCAAmC,gBAAgB,CAAA,kBAAA;AAAA,OACrD;AAAA,IACF,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AAClC,MAAA,MAAM,IAAI,MAAM,wDAAwD,CAAA;AAAA,IAC1E;AAEA,IAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAAA,EAC1B,CAAA;AAAA,EACA,MAAM,WAAA,CACJ,KAAA,EACA,OAAA,EACA;AACA,IAAA,IAAI,KAAA,KAAU,QAAQ,KAAA,EAAO;AAC3B,MAAA,OAAO,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ;AAAA,IACpC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAC;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.io/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
|
package/dist/package.json.cjs.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/backend-defaults",
|
|
3
|
-
"version": "0.13.0-next.
|
|
3
|
+
"version": "0.13.0-next.2",
|
|
4
4
|
"description": "Backend defaults used by Backstage backend apps",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "node-library"
|
|
@@ -220,8 +220,8 @@
|
|
|
220
220
|
"@backstage/backend-dev-utils": "0.1.5",
|
|
221
221
|
"@backstage/backend-plugin-api": "1.4.4-next.0",
|
|
222
222
|
"@backstage/cli-node": "0.2.14",
|
|
223
|
-
"@backstage/config": "1.3.
|
|
224
|
-
"@backstage/config-loader": "1.10.
|
|
223
|
+
"@backstage/config": "1.3.5-next.0",
|
|
224
|
+
"@backstage/config-loader": "1.10.5-next.0",
|
|
225
225
|
"@backstage/errors": "1.2.7",
|
|
226
226
|
"@backstage/integration": "1.18.1-next.1",
|
|
227
227
|
"@backstage/integration-aws-node": "0.1.18-next.0",
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
"@aws-sdk/util-stream-node": "^3.350.0",
|
|
287
287
|
"@backstage/backend-plugin-api": "1.4.4-next.0",
|
|
288
288
|
"@backstage/backend-test-utils": "1.9.1-next.1",
|
|
289
|
-
"@backstage/cli": "0.34.4-next.
|
|
289
|
+
"@backstage/cli": "0.34.4-next.2",
|
|
290
290
|
"@google-cloud/cloud-sql-connector": "^1.4.0",
|
|
291
291
|
"@types/archiver": "^6.0.0",
|
|
292
292
|
"@types/base64-stream": "^1.0.2",
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var errors = require('@backstage/errors');
|
|
4
|
-
var legacy = require('./legacy.cjs.js');
|
|
5
|
-
var _static = require('./static.cjs.js');
|
|
6
|
-
var jwks = require('./jwks.cjs.js');
|
|
7
|
-
|
|
8
|
-
const NEW_CONFIG_KEY = "backend.auth.externalAccess";
|
|
9
|
-
const OLD_CONFIG_KEY = "backend.auth.keys";
|
|
10
|
-
let loggedDeprecationWarning = false;
|
|
11
|
-
class ExternalTokenHandler {
|
|
12
|
-
constructor(ownPluginId, handlers) {
|
|
13
|
-
this.ownPluginId = ownPluginId;
|
|
14
|
-
this.handlers = handlers;
|
|
15
|
-
}
|
|
16
|
-
static create(options) {
|
|
17
|
-
const { ownPluginId, config, logger } = options;
|
|
18
|
-
const staticHandler = new _static.StaticTokenHandler();
|
|
19
|
-
const legacyHandler = new legacy.LegacyTokenHandler();
|
|
20
|
-
const jwksHandler = new jwks.JWKSHandler();
|
|
21
|
-
const handlers = {
|
|
22
|
-
static: staticHandler,
|
|
23
|
-
legacy: legacyHandler,
|
|
24
|
-
jwks: jwksHandler
|
|
25
|
-
};
|
|
26
|
-
const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];
|
|
27
|
-
for (const handlerConfig of handlerConfigs) {
|
|
28
|
-
const type = handlerConfig.getString("type");
|
|
29
|
-
const handler = handlers[type];
|
|
30
|
-
if (!handler) {
|
|
31
|
-
const valid = Object.keys(handlers).map((k) => `'${k}'`).join(", ");
|
|
32
|
-
throw new Error(
|
|
33
|
-
`Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
handler.add(handlerConfig);
|
|
37
|
-
}
|
|
38
|
-
const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];
|
|
39
|
-
if (legacyConfigs.length && !loggedDeprecationWarning) {
|
|
40
|
-
loggedDeprecationWarning = true;
|
|
41
|
-
logger.warn(
|
|
42
|
-
`DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
for (const handlerConfig of legacyConfigs) {
|
|
46
|
-
legacyHandler.addOld(handlerConfig);
|
|
47
|
-
}
|
|
48
|
-
return new ExternalTokenHandler(ownPluginId, Object.values(handlers));
|
|
49
|
-
}
|
|
50
|
-
async verifyToken(token) {
|
|
51
|
-
for (const handler of this.handlers) {
|
|
52
|
-
const result = await handler.verifyToken(token);
|
|
53
|
-
if (result) {
|
|
54
|
-
const { allAccessRestrictions, ...rest } = result;
|
|
55
|
-
if (allAccessRestrictions) {
|
|
56
|
-
const accessRestrictions = allAccessRestrictions.get(
|
|
57
|
-
this.ownPluginId
|
|
58
|
-
);
|
|
59
|
-
if (!accessRestrictions) {
|
|
60
|
-
const valid = [...allAccessRestrictions.keys()].map((k) => `'${k}'`).join(", ");
|
|
61
|
-
throw new errors.NotAllowedError(
|
|
62
|
-
`This token's access is restricted to plugin(s) ${valid}`
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
...rest,
|
|
67
|
-
accessRestrictions
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
return rest;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return void 0;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
exports.ExternalTokenHandler = ExternalTokenHandler;
|
|
78
|
-
//# sourceMappingURL=ExternalTokenHandler.cjs.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExternalTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/external/ExternalTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstagePrincipalAccessRestrictions,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport { LegacyTokenHandler } from './legacy';\nimport { StaticTokenHandler } from './static';\nimport { JWKSHandler } from './jwks';\nimport { TokenHandler } from './types';\n\nconst NEW_CONFIG_KEY = 'backend.auth.externalAccess';\nconst OLD_CONFIG_KEY = 'backend.auth.keys';\nlet loggedDeprecationWarning = false;\n\n/**\n * Handles all types of external caller token types (i.e. not Backstage user\n * tokens, nor Backstage backend plugin tokens).\n *\n * @internal\n */\nexport class ExternalTokenHandler {\n static create(options: {\n ownPluginId: string;\n config: RootConfigService;\n logger: LoggerService;\n }): ExternalTokenHandler {\n const { ownPluginId, config, logger } = options;\n\n const staticHandler = new StaticTokenHandler();\n const legacyHandler = new LegacyTokenHandler();\n const jwksHandler = new JWKSHandler();\n const handlers: Record<string, TokenHandler> = {\n static: staticHandler,\n legacy: legacyHandler,\n jwks: jwksHandler,\n };\n\n // Load the new-style handlers\n const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];\n for (const handlerConfig of handlerConfigs) {\n const type = handlerConfig.getString('type');\n const handler = handlers[type];\n if (!handler) {\n const valid = Object.keys(handlers)\n .map(k => `'${k}'`)\n .join(', ');\n throw new Error(\n `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,\n );\n }\n handler.add(handlerConfig);\n }\n\n // Load the old keys too\n const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];\n if (legacyConfigs.length && !loggedDeprecationWarning) {\n loggedDeprecationWarning = true;\n logger.warn(\n `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`,\n );\n }\n for (const handlerConfig of legacyConfigs) {\n legacyHandler.addOld(handlerConfig);\n }\n\n return new ExternalTokenHandler(ownPluginId, Object.values(handlers));\n }\n\n constructor(\n private readonly ownPluginId: string,\n private readonly handlers: TokenHandler[],\n ) {}\n\n async verifyToken(token: string): Promise<\n | {\n subject: string;\n accessRestrictions?: BackstagePrincipalAccessRestrictions;\n }\n | undefined\n > {\n for (const handler of this.handlers) {\n const result = await handler.verifyToken(token);\n if (result) {\n const { allAccessRestrictions, ...rest } = result;\n if (allAccessRestrictions) {\n const accessRestrictions = allAccessRestrictions.get(\n this.ownPluginId,\n );\n if (!accessRestrictions) {\n const valid = [...allAccessRestrictions.keys()]\n .map(k => `'${k}'`)\n .join(', ');\n throw new NotAllowedError(\n `This token's access is restricted to plugin(s) ${valid}`,\n );\n }\n\n return {\n ...rest,\n accessRestrictions,\n };\n }\n\n return rest;\n }\n }\n\n return undefined;\n }\n}\n"],"names":["StaticTokenHandler","LegacyTokenHandler","JWKSHandler","NotAllowedError"],"mappings":"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"}
|