@backstage/plugin-auth-node 0.5.2 → 0.5.3-next.1

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/extensions/AuthOwnershipResolutionExtensionPoint.cjs.js +10 -0
  3. package/dist/extensions/AuthOwnershipResolutionExtensionPoint.cjs.js.map +1 -0
  4. package/dist/extensions/AuthProvidersExtensionPoint.cjs.js +10 -0
  5. package/dist/extensions/AuthProvidersExtensionPoint.cjs.js.map +1 -0
  6. package/dist/flow/sendWebMessageResponse.cjs.js +41 -0
  7. package/dist/flow/sendWebMessageResponse.cjs.js.map +1 -0
  8. package/dist/identity/DefaultIdentityClient.cjs.js +103 -0
  9. package/dist/identity/DefaultIdentityClient.cjs.js.map +1 -0
  10. package/dist/identity/IdentityClient.cjs.js +27 -0
  11. package/dist/identity/IdentityClient.cjs.js.map +1 -0
  12. package/dist/identity/getBearerTokenFromAuthorizationHeader.cjs.js +12 -0
  13. package/dist/identity/getBearerTokenFromAuthorizationHeader.cjs.js.map +1 -0
  14. package/dist/identity/prepareBackstageIdentityResponse.cjs.js +36 -0
  15. package/dist/identity/prepareBackstageIdentityResponse.cjs.js.map +1 -0
  16. package/dist/index.cjs.js +48 -1063
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.d.ts +6 -4
  19. package/dist/oauth/CookieScopeManager.cjs.js +90 -0
  20. package/dist/oauth/CookieScopeManager.cjs.js.map +1 -0
  21. package/dist/oauth/OAuthCookieManager.cjs.js +87 -0
  22. package/dist/oauth/OAuthCookieManager.cjs.js.map +1 -0
  23. package/dist/oauth/OAuthEnvironmentHandler.cjs.js +64 -0
  24. package/dist/oauth/OAuthEnvironmentHandler.cjs.js.map +1 -0
  25. package/dist/oauth/PassportOAuthAuthenticatorHelper.cjs.js +69 -0
  26. package/dist/oauth/PassportOAuthAuthenticatorHelper.cjs.js.map +1 -0
  27. package/dist/oauth/createOAuthProviderFactory.cjs.js +37 -0
  28. package/dist/oauth/createOAuthProviderFactory.cjs.js.map +1 -0
  29. package/dist/oauth/createOAuthRouteHandlers.cjs.js +208 -0
  30. package/dist/oauth/createOAuthRouteHandlers.cjs.js.map +1 -0
  31. package/dist/oauth/state.cjs.js +31 -0
  32. package/dist/oauth/state.cjs.js.map +1 -0
  33. package/dist/oauth/types.cjs.js +8 -0
  34. package/dist/oauth/types.cjs.js.map +1 -0
  35. package/dist/passport/PassportHelpers.cjs.js +144 -0
  36. package/dist/passport/PassportHelpers.cjs.js.map +1 -0
  37. package/dist/proxy/createProxyAuthProviderFactory.cjs.js +32 -0
  38. package/dist/proxy/createProxyAuthProviderFactory.cjs.js.map +1 -0
  39. package/dist/proxy/createProxyRouteHandlers.cjs.js +39 -0
  40. package/dist/proxy/createProxyRouteHandlers.cjs.js.map +1 -0
  41. package/dist/proxy/types.cjs.js +8 -0
  42. package/dist/proxy/types.cjs.js.map +1 -0
  43. package/dist/sign-in/commonSignInResolvers.cjs.js +70 -0
  44. package/dist/sign-in/commonSignInResolvers.cjs.js.map +1 -0
  45. package/dist/sign-in/createSignInResolverFactory.cjs.js +37 -0
  46. package/dist/sign-in/createSignInResolverFactory.cjs.js.map +1 -0
  47. package/dist/sign-in/readDeclarativeSignInResolver.cjs.js +38 -0
  48. package/dist/sign-in/readDeclarativeSignInResolver.cjs.js.map +1 -0
  49. package/dist/types.cjs.js +17 -0
  50. package/dist/types.cjs.js.map +1 -0
  51. package/package.json +11 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @backstage/plugin-auth-node
2
2
 
3
+ ## 0.5.3-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 217458a: Added a new `allowedDomains` option for the common `emailLocalPartMatchingUserEntityName` sign-in resolver.
8
+ - Updated dependencies
9
+ - @backstage/catalog-client@1.7.1-next.0
10
+ - @backstage/backend-plugin-api@1.0.1-next.1
11
+ - @backstage/catalog-model@1.7.0
12
+ - @backstage/config@1.2.0
13
+ - @backstage/errors@1.2.4
14
+ - @backstage/types@1.1.1
15
+
16
+ ## 0.5.3-next.0
17
+
18
+ ### Patch Changes
19
+
20
+ - 094eaa3: Remove references to in-repo backend-common
21
+ - Updated dependencies
22
+ - @backstage/backend-plugin-api@1.0.1-next.0
23
+ - @backstage/catalog-client@1.7.0
24
+ - @backstage/catalog-model@1.7.0
25
+ - @backstage/config@1.2.0
26
+ - @backstage/errors@1.2.4
27
+ - @backstage/types@1.1.1
28
+
3
29
  ## 0.5.2
4
30
 
5
31
  ### Patch Changes
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+
5
+ const authOwnershipResolutionExtensionPoint = backendPluginApi.createExtensionPoint({
6
+ id: "auth.ownershipResolution"
7
+ });
8
+
9
+ exports.authOwnershipResolutionExtensionPoint = authOwnershipResolutionExtensionPoint;
10
+ //# sourceMappingURL=AuthOwnershipResolutionExtensionPoint.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthOwnershipResolutionExtensionPoint.cjs.js","sources":["../../src/extensions/AuthOwnershipResolutionExtensionPoint.ts"],"sourcesContent":["/*\n * Copyright 2023 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 */\nimport { AuthOwnershipResolver } from '../types';\nimport { createExtensionPoint } from '@backstage/backend-plugin-api';\n\n/** @public */\nexport interface AuthOwnershipResolutionExtensionPoint {\n setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void;\n}\n\n/** @public */\nexport const authOwnershipResolutionExtensionPoint =\n createExtensionPoint<AuthOwnershipResolutionExtensionPoint>({\n id: 'auth.ownershipResolution',\n });\n"],"names":["createExtensionPoint"],"mappings":";;;;AAwBO,MAAM,wCACXA,qCAA4D,CAAA;AAAA,EAC1D,EAAI,EAAA,0BAAA;AACN,CAAC;;;;"}
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+
5
+ const authProvidersExtensionPoint = backendPluginApi.createExtensionPoint({
6
+ id: "auth.providers"
7
+ });
8
+
9
+ exports.authProvidersExtensionPoint = authProvidersExtensionPoint;
10
+ //# sourceMappingURL=AuthProvidersExtensionPoint.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthProvidersExtensionPoint.cjs.js","sources":["../../src/extensions/AuthProvidersExtensionPoint.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { createExtensionPoint } from '@backstage/backend-plugin-api';\nimport { AuthProviderFactory } from '../types';\n\n/** @public */\nexport interface AuthProviderRegistrationOptions {\n providerId: string;\n factory: AuthProviderFactory;\n}\n\n/** @public */\nexport interface AuthProvidersExtensionPoint {\n registerProvider(options: AuthProviderRegistrationOptions): void;\n}\n\n/** @public */\nexport const authProvidersExtensionPoint =\n createExtensionPoint<AuthProvidersExtensionPoint>({\n id: 'auth.providers',\n });\n"],"names":["createExtensionPoint"],"mappings":";;;;AA+BO,MAAM,8BACXA,qCAAkD,CAAA;AAAA,EAChD,EAAI,EAAA,gBAAA;AACN,CAAC;;;;"}
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var errors = require('@backstage/errors');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
+
8
+ var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
9
+
10
+ function safelyEncodeURIComponent(value) {
11
+ return encodeURIComponent(value).replace(/'/g, "%27");
12
+ }
13
+ function sendWebMessageResponse(res, appOrigin, response) {
14
+ const jsonData = JSON.stringify(response, (_, value) => {
15
+ if (value instanceof Error) {
16
+ return errors.serializeError(value);
17
+ }
18
+ return value;
19
+ });
20
+ const base64Data = safelyEncodeURIComponent(jsonData);
21
+ const base64Origin = safelyEncodeURIComponent(appOrigin);
22
+ const script = `
23
+ var authResponse = decodeURIComponent('${base64Data}');
24
+ var origin = decodeURIComponent('${base64Origin}');
25
+ var originInfo = {'type': 'config_info', 'targetOrigin': origin};
26
+ (window.opener || window.parent).postMessage(originInfo, '*');
27
+ (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin);
28
+ setTimeout(() => {
29
+ window.close();
30
+ }, 100); // same as the interval of the core-app-api lib/loginPopup.ts (to address race conditions)
31
+ `;
32
+ const hash = crypto__default.default.createHash("sha256").update(script).digest("base64");
33
+ res.setHeader("Content-Type", "text/html");
34
+ res.setHeader("X-Frame-Options", "sameorigin");
35
+ res.setHeader("Content-Security-Policy", `script-src 'sha256-${hash}'`);
36
+ res.end(`<html><body><script>${script}<\/script></body></html>`);
37
+ }
38
+
39
+ exports.safelyEncodeURIComponent = safelyEncodeURIComponent;
40
+ exports.sendWebMessageResponse = sendWebMessageResponse;
41
+ //# sourceMappingURL=sendWebMessageResponse.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendWebMessageResponse.cjs.js","sources":["../../src/flow/sendWebMessageResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { Response } from 'express';\nimport crypto from 'crypto';\nimport { ClientAuthResponse } from '../types';\nimport { serializeError } from '@backstage/errors';\n\n/**\n * Payload sent as a post message after the auth request is complete.\n * If successful then has a valid payload with Auth information else contains an error.\n *\n * @public\n */\nexport type WebMessageResponse =\n | {\n type: 'authorization_response';\n response: ClientAuthResponse<unknown>;\n }\n | {\n type: 'authorization_response';\n error: Error;\n };\n\n/** @internal */\nexport function safelyEncodeURIComponent(value: string): string {\n // Note the g at the end of the regex; all occurrences of single quotes must\n // be replaced, which encodeURIComponent does not do itself by default\n return encodeURIComponent(value).replace(/'/g, '%27');\n}\n\n/** @public */\nexport function sendWebMessageResponse(\n res: Response,\n appOrigin: string,\n response: WebMessageResponse,\n): void {\n const jsonData = JSON.stringify(response, (_, value) => {\n if (value instanceof Error) {\n return serializeError(value);\n }\n return value;\n });\n const base64Data = safelyEncodeURIComponent(jsonData);\n const base64Origin = safelyEncodeURIComponent(appOrigin);\n\n // NOTE: It is absolutely imperative that we use the safe encoder above, to\n // be sure that the js code below does not allow the injection of malicious\n // data.\n\n // TODO: Make target app origin configurable globally\n\n //\n // postMessage fails silently if the targetOrigin is disallowed.\n // So 2 postMessages are sent from the popup to the parent window.\n // First, the origin being used to post the actual authorization response is\n // shared with the parent window with a postMessage with targetOrigin '*'.\n // Second, the actual authorization response is sent with the app origin\n // as the targetOrigin.\n // If the first message was received but the actual auth response was\n // never received, the event listener can conclude that targetOrigin\n // was disallowed, indicating potential misconfiguration.\n //\n const script = `\n var authResponse = decodeURIComponent('${base64Data}');\n var origin = decodeURIComponent('${base64Origin}');\n var originInfo = {'type': 'config_info', 'targetOrigin': origin};\n (window.opener || window.parent).postMessage(originInfo, '*');\n (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin);\n setTimeout(() => {\n window.close();\n }, 100); // same as the interval of the core-app-api lib/loginPopup.ts (to address race conditions)\n `;\n const hash = crypto.createHash('sha256').update(script).digest('base64');\n\n res.setHeader('Content-Type', 'text/html');\n res.setHeader('X-Frame-Options', 'sameorigin');\n res.setHeader('Content-Security-Policy', `script-src 'sha256-${hash}'`);\n res.end(`<html><body><script>${script}</script></body></html>`);\n}\n"],"names":["serializeError","crypto"],"mappings":";;;;;;;;;AAsCO,SAAS,yBAAyB,KAAuB,EAAA;AAG9D,EAAA,OAAO,kBAAmB,CAAA,KAAK,CAAE,CAAA,OAAA,CAAQ,MAAM,KAAK,CAAA,CAAA;AACtD,CAAA;AAGgB,SAAA,sBAAA,CACd,GACA,EAAA,SAAA,EACA,QACM,EAAA;AACN,EAAA,MAAM,WAAW,IAAK,CAAA,SAAA,CAAU,QAAU,EAAA,CAAC,GAAG,KAAU,KAAA;AACtD,IAAA,IAAI,iBAAiB,KAAO,EAAA;AAC1B,MAAA,OAAOA,sBAAe,KAAK,CAAA,CAAA;AAAA,KAC7B;AACA,IAAO,OAAA,KAAA,CAAA;AAAA,GACR,CAAA,CAAA;AACD,EAAM,MAAA,UAAA,GAAa,yBAAyB,QAAQ,CAAA,CAAA;AACpD,EAAM,MAAA,YAAA,GAAe,yBAAyB,SAAS,CAAA,CAAA;AAmBvD,EAAA,MAAM,MAAS,GAAA,CAAA;AAAA,2CAAA,EAC4B,UAAU,CAAA;AAAA,qCAAA,EAChB,YAAY,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA,CAAA;AAQjD,EAAM,MAAA,IAAA,GAAOC,wBAAO,UAAW,CAAA,QAAQ,EAAE,MAAO,CAAA,MAAM,CAAE,CAAA,MAAA,CAAO,QAAQ,CAAA,CAAA;AAEvE,EAAI,GAAA,CAAA,SAAA,CAAU,gBAAgB,WAAW,CAAA,CAAA;AACzC,EAAI,GAAA,CAAA,SAAA,CAAU,mBAAmB,YAAY,CAAA,CAAA;AAC7C,EAAA,GAAA,CAAI,SAAU,CAAA,yBAAA,EAA2B,CAAsB,mBAAA,EAAA,IAAI,CAAG,CAAA,CAAA,CAAA,CAAA;AACtE,EAAI,GAAA,CAAA,GAAA,CAAI,CAAuB,oBAAA,EAAA,MAAM,CAAyB,wBAAA,CAAA,CAAA,CAAA;AAChE;;;;;"}
@@ -0,0 +1,103 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var jose = require('jose');
5
+ var getBearerTokenFromAuthorizationHeader = require('./getBearerTokenFromAuthorizationHeader.cjs.js');
6
+
7
+ const CLOCK_MARGIN_S = 10;
8
+ class DefaultIdentityClient {
9
+ discovery;
10
+ issuer;
11
+ algorithms;
12
+ keyStore;
13
+ keyStoreUpdated = 0;
14
+ /**
15
+ * Create a new {@link DefaultIdentityClient} instance.
16
+ */
17
+ static create(options) {
18
+ return new DefaultIdentityClient(options);
19
+ }
20
+ constructor(options) {
21
+ this.discovery = options.discovery;
22
+ this.issuer = options.issuer;
23
+ this.algorithms = options.hasOwnProperty("algorithms") ? options.algorithms : ["ES256"];
24
+ }
25
+ async getIdentity(options) {
26
+ const {
27
+ request: { headers }
28
+ } = options;
29
+ if (!headers.authorization) {
30
+ return void 0;
31
+ }
32
+ try {
33
+ return await this.authenticate(
34
+ getBearerTokenFromAuthorizationHeader.getBearerTokenFromAuthorizationHeader(headers.authorization)
35
+ );
36
+ } catch (e) {
37
+ throw new errors.AuthenticationError(e.message);
38
+ }
39
+ }
40
+ /**
41
+ * Verifies the given backstage identity token
42
+ * Returns a BackstageIdentity (user) matching the token.
43
+ * The method throws an error if verification fails.
44
+ *
45
+ * @deprecated You should start to use getIdentity instead of authenticate to retrieve the user
46
+ * identity.
47
+ */
48
+ async authenticate(token) {
49
+ if (!token) {
50
+ throw new errors.AuthenticationError("No token specified");
51
+ }
52
+ await this.refreshKeyStore(token);
53
+ if (!this.keyStore) {
54
+ throw new errors.AuthenticationError("No keystore exists");
55
+ }
56
+ const decoded = await jose.jwtVerify(token, this.keyStore, {
57
+ algorithms: this.algorithms,
58
+ audience: "backstage",
59
+ issuer: this.issuer
60
+ });
61
+ if (!decoded.payload.sub) {
62
+ throw new errors.AuthenticationError("No user sub found in token");
63
+ }
64
+ const user = {
65
+ token,
66
+ identity: {
67
+ type: "user",
68
+ userEntityRef: decoded.payload.sub,
69
+ ownershipEntityRefs: decoded.payload.ent ? decoded.payload.ent : []
70
+ }
71
+ };
72
+ return user;
73
+ }
74
+ /**
75
+ * If the last keystore refresh is stale, update the keystore URL to the latest
76
+ */
77
+ async refreshKeyStore(rawJwtToken) {
78
+ const payload = await jose.decodeJwt(rawJwtToken);
79
+ const header = await jose.decodeProtectedHeader(rawJwtToken);
80
+ let keyStoreHasKey;
81
+ try {
82
+ if (this.keyStore) {
83
+ const [_, rawPayload, rawSignature] = rawJwtToken.split(".");
84
+ keyStoreHasKey = await this.keyStore(header, {
85
+ payload: rawPayload,
86
+ signature: rawSignature
87
+ });
88
+ }
89
+ } catch (error) {
90
+ keyStoreHasKey = false;
91
+ }
92
+ const issuedAfterLastRefresh = payload?.iat && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;
93
+ if (!this.keyStore || !keyStoreHasKey && issuedAfterLastRefresh) {
94
+ const url = await this.discovery.getBaseUrl("auth");
95
+ const endpoint = new URL(`${url}/.well-known/jwks.json`);
96
+ this.keyStore = jose.createRemoteJWKSet(endpoint);
97
+ this.keyStoreUpdated = Date.now() / 1e3;
98
+ }
99
+ }
100
+ }
101
+
102
+ exports.DefaultIdentityClient = DefaultIdentityClient;
103
+ //# sourceMappingURL=DefaultIdentityClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DefaultIdentityClient.cjs.js","sources":["../../src/identity/DefaultIdentityClient.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { PluginEndpointDiscovery } from '@backstage/backend-common';\nimport { AuthenticationError } from '@backstage/errors';\nimport {\n createRemoteJWKSet,\n decodeJwt,\n decodeProtectedHeader,\n FlattenedJWSInput,\n JWSHeaderParameters,\n jwtVerify,\n} from 'jose';\nimport { GetKeyFunction } from 'jose/dist/types/types';\nimport { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader';\nimport { IdentityApi, IdentityApiGetIdentityRequest } from './IdentityApi';\nimport { BackstageIdentityResponse } from '../types';\n\nconst CLOCK_MARGIN_S = 10;\n\n/**\n * An identity client options object which allows extra configurations\n *\n * @experimental This is not a stable API yet\n * @public\n */\nexport type IdentityClientOptions = {\n discovery: PluginEndpointDiscovery;\n issuer?: string;\n\n /** JWS \"alg\" (Algorithm) Header Parameter values. Defaults to an array containing just ES256.\n * More info on supported algorithms: https://github.com/panva/jose */\n algorithms?: string[];\n};\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @experimental This is not a stable API yet\n * @public\n */\nexport class DefaultIdentityClient implements IdentityApi {\n private readonly discovery: PluginEndpointDiscovery;\n private readonly issuer?: string;\n private readonly algorithms?: string[];\n private keyStore?: GetKeyFunction<JWSHeaderParameters, FlattenedJWSInput>;\n private keyStoreUpdated: number = 0;\n\n /**\n * Create a new {@link DefaultIdentityClient} instance.\n */\n static create(options: IdentityClientOptions): DefaultIdentityClient {\n return new DefaultIdentityClient(options);\n }\n\n private constructor(options: IdentityClientOptions) {\n this.discovery = options.discovery;\n this.issuer = options.issuer;\n this.algorithms = options.hasOwnProperty('algorithms')\n ? options.algorithms\n : ['ES256'];\n }\n\n async getIdentity(options: IdentityApiGetIdentityRequest) {\n const {\n request: { headers },\n } = options;\n if (!headers.authorization) {\n return undefined;\n }\n try {\n return await this.authenticate(\n getBearerTokenFromAuthorizationHeader(headers.authorization),\n );\n } catch (e) {\n throw new AuthenticationError(e.message);\n }\n }\n\n /**\n * Verifies the given backstage identity token\n * Returns a BackstageIdentity (user) matching the token.\n * The method throws an error if verification fails.\n *\n * @deprecated You should start to use getIdentity instead of authenticate to retrieve the user\n * identity.\n */\n async authenticate(\n token: string | undefined,\n ): Promise<BackstageIdentityResponse> {\n // Extract token from header\n if (!token) {\n throw new AuthenticationError('No token specified');\n }\n\n // Verify token claims and signature\n // Note: Claims must match those set by TokenFactory when issuing tokens\n // Note: verify throws if verification fails\n // Check if the keystore needs to be updated\n await this.refreshKeyStore(token);\n if (!this.keyStore) {\n throw new AuthenticationError('No keystore exists');\n }\n const decoded = await jwtVerify(token, this.keyStore, {\n algorithms: this.algorithms,\n audience: 'backstage',\n issuer: this.issuer,\n });\n // Verified, return the matching user as BackstageIdentity\n // TODO: Settle internal user format/properties\n if (!decoded.payload.sub) {\n throw new AuthenticationError('No user sub found in token');\n }\n\n const user: BackstageIdentityResponse = {\n token,\n identity: {\n type: 'user',\n userEntityRef: decoded.payload.sub,\n ownershipEntityRefs: decoded.payload.ent\n ? (decoded.payload.ent as string[])\n : [],\n },\n };\n return user;\n }\n\n /**\n * If the last keystore refresh is stale, update the keystore URL to the latest\n */\n private async refreshKeyStore(rawJwtToken: string): Promise<void> {\n const payload = await decodeJwt(rawJwtToken);\n const header = await decodeProtectedHeader(rawJwtToken);\n\n // Refresh public keys if needed\n let keyStoreHasKey;\n try {\n if (this.keyStore) {\n // Check if the key is present in the keystore\n const [_, rawPayload, rawSignature] = rawJwtToken.split('.');\n keyStoreHasKey = await this.keyStore(header, {\n payload: rawPayload,\n signature: rawSignature,\n });\n }\n } catch (error) {\n keyStoreHasKey = false;\n }\n // Refresh public key URL if needed\n // Add a small margin in case clocks are out of sync\n const issuedAfterLastRefresh =\n payload?.iat && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;\n if (!this.keyStore || (!keyStoreHasKey && issuedAfterLastRefresh)) {\n const url = await this.discovery.getBaseUrl('auth');\n const endpoint = new URL(`${url}/.well-known/jwks.json`);\n this.keyStore = createRemoteJWKSet(endpoint);\n this.keyStoreUpdated = Date.now() / 1000;\n }\n }\n}\n"],"names":["getBearerTokenFromAuthorizationHeader","AuthenticationError","jwtVerify","decodeJwt","decodeProtectedHeader","createRemoteJWKSet"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ var DefaultIdentityClient = require('./DefaultIdentityClient.cjs.js');
4
+
5
+ class IdentityClient {
6
+ defaultIdentityClient;
7
+ static create(options) {
8
+ return new IdentityClient(DefaultIdentityClient.DefaultIdentityClient.create(options));
9
+ }
10
+ constructor(defaultIdentityClient) {
11
+ this.defaultIdentityClient = defaultIdentityClient;
12
+ }
13
+ /**
14
+ * Verifies the given backstage identity token
15
+ * Returns a BackstageIdentity (user) matching the token.
16
+ * The method throws an error if verification fails.
17
+ *
18
+ * @deprecated You should start to use IdentityApi#getIdentity instead of authenticate
19
+ * to retrieve the user identity.
20
+ */
21
+ async authenticate(token) {
22
+ return await this.defaultIdentityClient.authenticate(token);
23
+ }
24
+ }
25
+
26
+ exports.IdentityClient = IdentityClient;
27
+ //# sourceMappingURL=IdentityClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IdentityClient.cjs.js","sources":["../../src/identity/IdentityClient.ts"],"sourcesContent":["/*\n * Copyright 2020 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 DefaultIdentityClient,\n IdentityClientOptions,\n} from './DefaultIdentityClient';\nimport { BackstageIdentityResponse } from '../types';\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @public\n * @experimental This is not a stable API yet\n * @deprecated Please migrate to the DefaultIdentityClient.\n */\nexport class IdentityClient {\n private readonly defaultIdentityClient: DefaultIdentityClient;\n static create(options: IdentityClientOptions): IdentityClient {\n return new IdentityClient(DefaultIdentityClient.create(options));\n }\n\n private constructor(defaultIdentityClient: DefaultIdentityClient) {\n this.defaultIdentityClient = defaultIdentityClient;\n }\n\n /**\n * Verifies the given backstage identity token\n * Returns a BackstageIdentity (user) matching the token.\n * The method throws an error if verification fails.\n *\n * @deprecated You should start to use IdentityApi#getIdentity instead of authenticate\n * to retrieve the user identity.\n */\n async authenticate(\n token: string | undefined,\n ): Promise<BackstageIdentityResponse> {\n return await this.defaultIdentityClient.authenticate(token);\n }\n}\n"],"names":["DefaultIdentityClient"],"mappings":";;;;AA8BO,MAAM,cAAe,CAAA;AAAA,EACT,qBAAA,CAAA;AAAA,EACjB,OAAO,OAAO,OAAgD,EAAA;AAC5D,IAAA,OAAO,IAAI,cAAA,CAAeA,2CAAsB,CAAA,MAAA,CAAO,OAAO,CAAC,CAAA,CAAA;AAAA,GACjE;AAAA,EAEQ,YAAY,qBAA8C,EAAA;AAChE,IAAA,IAAA,CAAK,qBAAwB,GAAA,qBAAA,CAAA;AAAA,GAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,aACJ,KACoC,EAAA;AACpC,IAAA,OAAO,MAAM,IAAA,CAAK,qBAAsB,CAAA,YAAA,CAAa,KAAK,CAAA,CAAA;AAAA,GAC5D;AACF;;;;"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ function getBearerTokenFromAuthorizationHeader(authorizationHeader) {
4
+ if (typeof authorizationHeader !== "string") {
5
+ return void 0;
6
+ }
7
+ const matches = authorizationHeader.match(/^Bearer[ ]+(\S+)$/i);
8
+ return matches?.[1];
9
+ }
10
+
11
+ exports.getBearerTokenFromAuthorizationHeader = getBearerTokenFromAuthorizationHeader;
12
+ //# sourceMappingURL=getBearerTokenFromAuthorizationHeader.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBearerTokenFromAuthorizationHeader.cjs.js","sources":["../../src/identity/getBearerTokenFromAuthorizationHeader.ts"],"sourcesContent":["/*\n * Copyright 2022 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\n/**\n * Parses the given authorization header and returns the bearer token, or\n * undefined if no bearer token is given.\n *\n * @remarks\n *\n * This function is explicitly built to tolerate bad inputs safely, so you may\n * call it directly with e.g. the output of `req.header('authorization')`\n * without first checking that it exists.\n *\n * @deprecated Use the `credentials` method of `HttpAuthService` from `@backstage/backend-plugin-api` instead\n * @public\n */\nexport function getBearerTokenFromAuthorizationHeader(\n authorizationHeader: unknown,\n): string | undefined {\n if (typeof authorizationHeader !== 'string') {\n return undefined;\n }\n const matches = authorizationHeader.match(/^Bearer[ ]+(\\S+)$/i);\n return matches?.[1];\n}\n"],"names":[],"mappings":";;AA6BO,SAAS,sCACd,mBACoB,EAAA;AACpB,EAAI,IAAA,OAAO,wBAAwB,QAAU,EAAA;AAC3C,IAAO,OAAA,KAAA,CAAA,CAAA;AAAA,GACT;AACA,EAAM,MAAA,OAAA,GAAU,mBAAoB,CAAA,KAAA,CAAM,oBAAoB,CAAA,CAAA;AAC9D,EAAA,OAAO,UAAU,CAAC,CAAA,CAAA;AACpB;;;;"}
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ function parseJwtPayload(token) {
6
+ const [_header, payload, _signature] = token.split(".");
7
+ return JSON.parse(Buffer.from(payload, "base64").toString());
8
+ }
9
+ function prepareBackstageIdentityResponse(result) {
10
+ if (!result.token) {
11
+ throw new errors.InputError(`Identity response must return a token`);
12
+ }
13
+ const { sub, ent = [], exp: expStr } = parseJwtPayload(result.token);
14
+ if (!sub) {
15
+ throw new errors.InputError(
16
+ `Identity response must return a token with subject claim`
17
+ );
18
+ }
19
+ const expAt = Number(expStr);
20
+ const exp = expAt ? Math.round(expAt - Date.now() / 1e3) : void 0;
21
+ if (exp && exp < 0) {
22
+ throw new errors.InputError(`Identity response must not return an expired token`);
23
+ }
24
+ return {
25
+ ...result,
26
+ expiresInSeconds: exp,
27
+ identity: {
28
+ type: "user",
29
+ userEntityRef: sub,
30
+ ownershipEntityRefs: ent
31
+ }
32
+ };
33
+ }
34
+
35
+ exports.prepareBackstageIdentityResponse = prepareBackstageIdentityResponse;
36
+ //# sourceMappingURL=prepareBackstageIdentityResponse.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareBackstageIdentityResponse.cjs.js","sources":["../../src/identity/prepareBackstageIdentityResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { InputError } from '@backstage/errors';\nimport {\n BackstageIdentityResponse,\n BackstageSignInResult,\n} from '@backstage/plugin-auth-node';\n\nfunction parseJwtPayload(token: string) {\n const [_header, payload, _signature] = token.split('.');\n return JSON.parse(Buffer.from(payload, 'base64').toString());\n}\n\n/**\n * Parses a Backstage-issued token and decorates the\n * {@link @backstage/plugin-auth-node#BackstageIdentityResponse} with identity information sourced from the\n * token.\n *\n * @public\n */\nexport function prepareBackstageIdentityResponse(\n result: BackstageSignInResult,\n): BackstageIdentityResponse {\n if (!result.token) {\n throw new InputError(`Identity response must return a token`);\n }\n\n const { sub, ent = [], exp: expStr } = parseJwtPayload(result.token);\n if (!sub) {\n throw new InputError(\n `Identity response must return a token with subject claim`,\n );\n }\n\n const expAt = Number(expStr);\n\n // Default to 1 hour if no expiration is set, in particular to make testing simpler\n const exp = expAt ? Math.round(expAt - Date.now() / 1000) : undefined;\n if (exp && exp < 0) {\n throw new InputError(`Identity response must not return an expired token`);\n }\n\n return {\n ...result,\n expiresInSeconds: exp,\n identity: {\n type: 'user',\n userEntityRef: sub,\n ownershipEntityRefs: ent,\n },\n };\n}\n"],"names":["InputError"],"mappings":";;;;AAsBA,SAAS,gBAAgB,KAAe,EAAA;AACtC,EAAA,MAAM,CAAC,OAAS,EAAA,OAAA,EAAS,UAAU,CAAI,GAAA,KAAA,CAAM,MAAM,GAAG,CAAA,CAAA;AACtD,EAAO,OAAA,IAAA,CAAK,MAAM,MAAO,CAAA,IAAA,CAAK,SAAS,QAAQ,CAAA,CAAE,UAAU,CAAA,CAAA;AAC7D,CAAA;AASO,SAAS,iCACd,MAC2B,EAAA;AAC3B,EAAI,IAAA,CAAC,OAAO,KAAO,EAAA;AACjB,IAAM,MAAA,IAAIA,kBAAW,CAAuC,qCAAA,CAAA,CAAA,CAAA;AAAA,GAC9D;AAEA,EAAM,MAAA,EAAE,GAAK,EAAA,GAAA,GAAM,EAAC,EAAG,KAAK,MAAO,EAAA,GAAI,eAAgB,CAAA,MAAA,CAAO,KAAK,CAAA,CAAA;AACnE,EAAA,IAAI,CAAC,GAAK,EAAA;AACR,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,wDAAA,CAAA;AAAA,KACF,CAAA;AAAA,GACF;AAEA,EAAM,MAAA,KAAA,GAAQ,OAAO,MAAM,CAAA,CAAA;AAG3B,EAAM,MAAA,GAAA,GAAM,QAAQ,IAAK,CAAA,KAAA,CAAM,QAAQ,IAAK,CAAA,GAAA,EAAQ,GAAA,GAAI,CAAI,GAAA,KAAA,CAAA,CAAA;AAC5D,EAAI,IAAA,GAAA,IAAO,MAAM,CAAG,EAAA;AAClB,IAAM,MAAA,IAAIA,kBAAW,CAAoD,kDAAA,CAAA,CAAA,CAAA;AAAA,GAC3E;AAEA,EAAO,OAAA;AAAA,IACL,GAAG,MAAA;AAAA,IACH,gBAAkB,EAAA,GAAA;AAAA,IAClB,QAAU,EAAA;AAAA,MACR,IAAM,EAAA,MAAA;AAAA,MACN,aAAe,EAAA,GAAA;AAAA,MACf,mBAAqB,EAAA,GAAA;AAAA,KACvB;AAAA,GACF,CAAA;AACF;;;;"}