@backstage/plugin-auth-backend 0.23.1-next.0 → 0.23.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 (96) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/authPlugin.cjs.js +75 -0
  3. package/dist/authPlugin.cjs.js.map +1 -0
  4. package/dist/database/AuthDatabase.cjs.js +51 -0
  5. package/dist/database/AuthDatabase.cjs.js.map +1 -0
  6. package/dist/identity/DatabaseKeyStore.cjs.js +40 -0
  7. package/dist/identity/DatabaseKeyStore.cjs.js.map +1 -0
  8. package/dist/identity/FirestoreKeyStore.cjs.js +90 -0
  9. package/dist/identity/FirestoreKeyStore.cjs.js.map +1 -0
  10. package/dist/identity/KeyStores.cjs.js +54 -0
  11. package/dist/identity/KeyStores.cjs.js.map +1 -0
  12. package/dist/identity/MemoryKeyStore.cjs.js +29 -0
  13. package/dist/identity/MemoryKeyStore.cjs.js.map +1 -0
  14. package/dist/identity/StaticKeyStore.cjs.js +91 -0
  15. package/dist/identity/StaticKeyStore.cjs.js.map +1 -0
  16. package/dist/identity/StaticTokenIssuer.cjs.js +53 -0
  17. package/dist/identity/StaticTokenIssuer.cjs.js.map +1 -0
  18. package/dist/identity/TokenFactory.cjs.js +164 -0
  19. package/dist/identity/TokenFactory.cjs.js.map +1 -0
  20. package/dist/identity/UserInfoDatabaseHandler.cjs.js +30 -0
  21. package/dist/identity/UserInfoDatabaseHandler.cjs.js.map +1 -0
  22. package/dist/identity/router.cjs.js +77 -0
  23. package/dist/identity/router.cjs.js.map +1 -0
  24. package/dist/index.cjs.js +31 -1981
  25. package/dist/index.cjs.js.map +1 -1
  26. package/dist/lib/catalog/CatalogIdentityClient.cjs.js +94 -0
  27. package/dist/lib/catalog/CatalogIdentityClient.cjs.js.map +1 -0
  28. package/dist/lib/flow/authFlowHelpers.cjs.js +43 -0
  29. package/dist/lib/flow/authFlowHelpers.cjs.js.map +1 -0
  30. package/dist/lib/legacy/adaptLegacyOAuthHandler.cjs.js +20 -0
  31. package/dist/lib/legacy/adaptLegacyOAuthHandler.cjs.js.map +1 -0
  32. package/dist/lib/legacy/adaptLegacyOAuthSignInResolver.cjs.js +24 -0
  33. package/dist/lib/legacy/adaptLegacyOAuthSignInResolver.cjs.js.map +1 -0
  34. package/dist/lib/legacy/adaptOAuthSignInResolverToLegacy.cjs.js +29 -0
  35. package/dist/lib/legacy/adaptOAuthSignInResolverToLegacy.cjs.js.map +1 -0
  36. package/dist/lib/oauth/OAuthAdapter.cjs.js +220 -0
  37. package/dist/lib/oauth/OAuthAdapter.cjs.js.map +1 -0
  38. package/dist/lib/oauth/OAuthEnvironmentHandler.cjs.js +8 -0
  39. package/dist/lib/oauth/OAuthEnvironmentHandler.cjs.js.map +1 -0
  40. package/dist/lib/oauth/helpers.cjs.js +40 -0
  41. package/dist/lib/oauth/helpers.cjs.js.map +1 -0
  42. package/dist/lib/passport/PassportStrategyHelper.cjs.js +48 -0
  43. package/dist/lib/passport/PassportStrategyHelper.cjs.js.map +1 -0
  44. package/dist/lib/resolvers/CatalogAuthResolverContext.cjs.js +116 -0
  45. package/dist/lib/resolvers/CatalogAuthResolverContext.cjs.js.map +1 -0
  46. package/dist/providers/atlassian/provider.cjs.js +20 -0
  47. package/dist/providers/atlassian/provider.cjs.js.map +1 -0
  48. package/dist/providers/auth0/provider.cjs.js +20 -0
  49. package/dist/providers/auth0/provider.cjs.js.map +1 -0
  50. package/dist/providers/aws-alb/provider.cjs.js +18 -0
  51. package/dist/providers/aws-alb/provider.cjs.js.map +1 -0
  52. package/dist/providers/azure-easyauth/provider.cjs.js +18 -0
  53. package/dist/providers/azure-easyauth/provider.cjs.js.map +1 -0
  54. package/dist/providers/bitbucket/provider.cjs.js +25 -0
  55. package/dist/providers/bitbucket/provider.cjs.js.map +1 -0
  56. package/dist/providers/bitbucketServer/provider.cjs.js +46 -0
  57. package/dist/providers/bitbucketServer/provider.cjs.js.map +1 -0
  58. package/dist/providers/cloudflare-access/provider.cjs.js +22 -0
  59. package/dist/providers/cloudflare-access/provider.cjs.js.map +1 -0
  60. package/dist/providers/createAuthProviderIntegration.cjs.js +11 -0
  61. package/dist/providers/createAuthProviderIntegration.cjs.js.map +1 -0
  62. package/dist/providers/gcp-iap/provider.cjs.js +18 -0
  63. package/dist/providers/gcp-iap/provider.cjs.js.map +1 -0
  64. package/dist/providers/github/provider.cjs.js +61 -0
  65. package/dist/providers/github/provider.cjs.js.map +1 -0
  66. package/dist/providers/gitlab/provider.cjs.js +20 -0
  67. package/dist/providers/gitlab/provider.cjs.js.map +1 -0
  68. package/dist/providers/google/provider.cjs.js +26 -0
  69. package/dist/providers/google/provider.cjs.js.map +1 -0
  70. package/dist/providers/microsoft/provider.cjs.js +27 -0
  71. package/dist/providers/microsoft/provider.cjs.js.map +1 -0
  72. package/dist/providers/oauth2/provider.cjs.js +20 -0
  73. package/dist/providers/oauth2/provider.cjs.js.map +1 -0
  74. package/dist/providers/oauth2-proxy/provider.cjs.js +18 -0
  75. package/dist/providers/oauth2-proxy/provider.cjs.js.map +1 -0
  76. package/dist/providers/oidc/provider.cjs.js +37 -0
  77. package/dist/providers/oidc/provider.cjs.js.map +1 -0
  78. package/dist/providers/okta/provider.cjs.js +47 -0
  79. package/dist/providers/okta/provider.cjs.js.map +1 -0
  80. package/dist/providers/onelogin/provider.cjs.js +20 -0
  81. package/dist/providers/onelogin/provider.cjs.js.map +1 -0
  82. package/dist/providers/prepareBackstageIdentityResponse.cjs.js +8 -0
  83. package/dist/providers/prepareBackstageIdentityResponse.cjs.js.map +1 -0
  84. package/dist/providers/providers.cjs.js +62 -0
  85. package/dist/providers/providers.cjs.js.map +1 -0
  86. package/dist/providers/resolvers.cjs.js +27 -0
  87. package/dist/providers/resolvers.cjs.js.map +1 -0
  88. package/dist/providers/router.cjs.js +111 -0
  89. package/dist/providers/router.cjs.js.map +1 -0
  90. package/dist/providers/saml/provider.cjs.js +121 -0
  91. package/dist/providers/saml/provider.cjs.js.map +1 -0
  92. package/dist/service/readBackstageTokenExpiration.cjs.js +27 -0
  93. package/dist/service/readBackstageTokenExpiration.cjs.js.map +1 -0
  94. package/dist/service/router.cjs.js +127 -0
  95. package/dist/service/router.cjs.js.map +1 -0
  96. package/package.json +25 -25
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var catalogModel = require('@backstage/catalog-model');
5
+ var backendCommon = require('@backstage/backend-common');
6
+
7
+ class CatalogIdentityClient {
8
+ catalogApi;
9
+ auth;
10
+ constructor(options) {
11
+ this.catalogApi = options.catalogApi;
12
+ const { auth } = backendCommon.createLegacyAuthAdapters({
13
+ auth: options.auth,
14
+ httpAuth: options.httpAuth,
15
+ discovery: options.discovery,
16
+ tokenManager: options.tokenManager
17
+ });
18
+ this.auth = auth;
19
+ }
20
+ /**
21
+ * Looks up a single user using a query.
22
+ *
23
+ * Throws a NotFoundError or ConflictError if 0 or multiple users are found.
24
+ */
25
+ async findUser(query) {
26
+ const filter = {
27
+ kind: "user"
28
+ };
29
+ for (const [key, value] of Object.entries(query.annotations)) {
30
+ filter[`metadata.annotations.${key}`] = value;
31
+ }
32
+ const { token } = await this.auth.getPluginRequestToken({
33
+ onBehalfOf: await this.auth.getOwnServiceCredentials(),
34
+ targetPluginId: "catalog"
35
+ });
36
+ const { items } = await this.catalogApi.getEntities({ filter }, { token });
37
+ if (items.length !== 1) {
38
+ if (items.length > 1) {
39
+ throw new errors.ConflictError("User lookup resulted in multiple matches");
40
+ } else {
41
+ throw new errors.NotFoundError("User not found");
42
+ }
43
+ }
44
+ return items[0];
45
+ }
46
+ /**
47
+ * Resolve additional entity claims from the catalog, using the passed-in entity names. Designed
48
+ * to be used within a `signInResolver` where additional entity claims might be provided, but
49
+ * group membership and transient group membership lean on imported catalog relations.
50
+ *
51
+ * Returns a superset of the entity names that can be passed directly to `issueToken` as `ent`.
52
+ */
53
+ async resolveCatalogMembership(query) {
54
+ const { entityRefs, logger } = query;
55
+ const resolvedEntityRefs = entityRefs.map((ref) => {
56
+ try {
57
+ const parsedRef = catalogModel.parseEntityRef(ref.toLocaleLowerCase("en-US"), {
58
+ defaultKind: "user",
59
+ defaultNamespace: "default"
60
+ });
61
+ return parsedRef;
62
+ } catch {
63
+ logger?.warn(`Failed to parse entityRef from ${ref}, ignoring`);
64
+ return null;
65
+ }
66
+ }).filter((ref) => ref !== null);
67
+ const filter = resolvedEntityRefs.map((ref) => ({
68
+ kind: ref.kind,
69
+ "metadata.namespace": ref.namespace,
70
+ "metadata.name": ref.name
71
+ }));
72
+ const { token } = await this.auth.getPluginRequestToken({
73
+ onBehalfOf: await this.auth.getOwnServiceCredentials(),
74
+ targetPluginId: "catalog"
75
+ });
76
+ const entities = await this.catalogApi.getEntities({ filter }, { token }).then((r) => r.items);
77
+ if (entityRefs.length !== entities.length) {
78
+ const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
79
+ const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
80
+ logger?.debug(`Entities not found for refs ${missingEntityNames.join()}`);
81
+ }
82
+ const memberOf = entities.flatMap(
83
+ (e) => e.relations?.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.targetRef) ?? []
84
+ );
85
+ const newEntityRefs = [
86
+ ...new Set(resolvedEntityRefs.map(catalogModel.stringifyEntityRef).concat(memberOf))
87
+ ];
88
+ logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`);
89
+ return newEntityRefs;
90
+ }
91
+ }
92
+
93
+ exports.CatalogIdentityClient = CatalogIdentityClient;
94
+ //# sourceMappingURL=CatalogIdentityClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CatalogIdentityClient.cjs.js","sources":["../../../src/lib/catalog/CatalogIdentityClient.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 AuthService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport { CatalogApi } from '@backstage/catalog-client';\nimport {\n CompoundEntityRef,\n parseEntityRef,\n RELATION_MEMBER_OF,\n stringifyEntityRef,\n UserEntity,\n} from '@backstage/catalog-model';\nimport {\n TokenManager,\n createLegacyAuthAdapters,\n} from '@backstage/backend-common';\n\n/**\n * A catalog client tailored for reading out identity data from the catalog.\n *\n * @public\n */\nexport class CatalogIdentityClient {\n private readonly catalogApi: CatalogApi;\n private readonly auth: AuthService;\n\n constructor(options: {\n catalogApi: CatalogApi;\n tokenManager?: TokenManager;\n discovery: DiscoveryService;\n auth?: AuthService;\n httpAuth?: HttpAuthService;\n }) {\n this.catalogApi = options.catalogApi;\n\n const { auth } = createLegacyAuthAdapters({\n auth: options.auth,\n httpAuth: options.httpAuth,\n discovery: options.discovery,\n tokenManager: options.tokenManager,\n });\n\n this.auth = auth;\n }\n\n /**\n * Looks up a single user using a query.\n *\n * Throws a NotFoundError or ConflictError if 0 or multiple users are found.\n */\n async findUser(query: {\n annotations: Record<string, string>;\n }): Promise<UserEntity> {\n const filter: Record<string, string> = {\n kind: 'user',\n };\n for (const [key, value] of Object.entries(query.annotations)) {\n filter[`metadata.annotations.${key}`] = value;\n }\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n\n const { items } = await this.catalogApi.getEntities({ filter }, { token });\n\n if (items.length !== 1) {\n if (items.length > 1) {\n throw new ConflictError('User lookup resulted in multiple matches');\n } else {\n throw new NotFoundError('User not found');\n }\n }\n\n return items[0] as UserEntity;\n }\n\n /**\n * Resolve additional entity claims from the catalog, using the passed-in entity names. Designed\n * to be used within a `signInResolver` where additional entity claims might be provided, but\n * group membership and transient group membership lean on imported catalog relations.\n *\n * Returns a superset of the entity names that can be passed directly to `issueToken` as `ent`.\n */\n async resolveCatalogMembership(query: {\n entityRefs: string[];\n logger?: LoggerService;\n }): Promise<string[]> {\n const { entityRefs, logger } = query;\n const resolvedEntityRefs = entityRefs\n .map((ref: string) => {\n try {\n const parsedRef = parseEntityRef(ref.toLocaleLowerCase('en-US'), {\n defaultKind: 'user',\n defaultNamespace: 'default',\n });\n return parsedRef;\n } catch {\n logger?.warn(`Failed to parse entityRef from ${ref}, ignoring`);\n return null;\n }\n })\n .filter((ref): ref is CompoundEntityRef => ref !== null);\n\n const filter = resolvedEntityRefs.map(ref => ({\n kind: ref.kind,\n 'metadata.namespace': ref.namespace,\n 'metadata.name': ref.name,\n }));\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n\n const entities = await this.catalogApi\n .getEntities({ filter }, { token })\n .then(r => r.items);\n\n if (entityRefs.length !== entities.length) {\n const foundEntityNames = entities.map(stringifyEntityRef);\n const missingEntityNames = resolvedEntityRefs\n .map(stringifyEntityRef)\n .filter(s => !foundEntityNames.includes(s));\n logger?.debug(`Entities not found for refs ${missingEntityNames.join()}`);\n }\n\n const memberOf = entities.flatMap(\n e =>\n e!.relations\n ?.filter(r => r.type === RELATION_MEMBER_OF)\n .map(r => r.targetRef) ?? [],\n );\n\n const newEntityRefs = [\n ...new Set(resolvedEntityRefs.map(stringifyEntityRef).concat(memberOf)),\n ];\n\n logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`);\n return newEntityRefs;\n }\n}\n"],"names":["createLegacyAuthAdapters","ConflictError","NotFoundError","parseEntityRef","stringifyEntityRef","RELATION_MEMBER_OF"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
6
+
7
+ var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
8
+
9
+ const safelyEncodeURIComponent = (value) => {
10
+ return encodeURIComponent(value).replace(/'/g, "%27");
11
+ };
12
+ const postMessageResponse = (res, appOrigin, response) => {
13
+ const jsonData = JSON.stringify(response);
14
+ const base64Data = safelyEncodeURIComponent(jsonData);
15
+ const base64Origin = safelyEncodeURIComponent(appOrigin);
16
+ const script = `
17
+ var authResponse = decodeURIComponent('${base64Data}');
18
+ var origin = decodeURIComponent('${base64Origin}');
19
+ var originInfo = {'type': 'config_info', 'targetOrigin': origin};
20
+ (window.opener || window.parent).postMessage(originInfo, '*');
21
+ (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin);
22
+ setTimeout(() => {
23
+ window.close();
24
+ }, 100); // same as the interval of the core-app-api lib/loginPopup.ts (to address race conditions)
25
+ `;
26
+ const hash = crypto__default.default.createHash("sha256").update(script).digest("base64");
27
+ res.setHeader("Content-Type", "text/html");
28
+ res.setHeader("X-Frame-Options", "sameorigin");
29
+ res.setHeader("Content-Security-Policy", `script-src 'sha256-${hash}'`);
30
+ res.end(`<html><body><script>${script}<\/script></body></html>`);
31
+ };
32
+ const ensuresXRequestedWith = (req) => {
33
+ const requiredHeader = req.header("X-Requested-With");
34
+ if (!requiredHeader || requiredHeader !== "XMLHttpRequest") {
35
+ return false;
36
+ }
37
+ return true;
38
+ };
39
+
40
+ exports.ensuresXRequestedWith = ensuresXRequestedWith;
41
+ exports.postMessageResponse = postMessageResponse;
42
+ exports.safelyEncodeURIComponent = safelyEncodeURIComponent;
43
+ //# sourceMappingURL=authFlowHelpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authFlowHelpers.cjs.js","sources":["../../../src/lib/flow/authFlowHelpers.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 express from 'express';\nimport crypto from 'crypto';\nimport { WebMessageResponse } from './types';\n\nexport const safelyEncodeURIComponent = (value: 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/**\n * @public\n * @deprecated Use `sendWebMessageResponse` from `@backstage/plugin-auth-node` instead\n */\nexport const postMessageResponse = (\n res: express.Response,\n appOrigin: string,\n response: WebMessageResponse,\n) => {\n const jsonData = JSON.stringify(response);\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\n/**\n * @public\n * @deprecated Use inline logic to check that the `X-Requested-With` header is set to `'XMLHttpRequest'` instead.\n */\nexport const ensuresXRequestedWith = (req: express.Request) => {\n const requiredHeader = req.header('X-Requested-With');\n if (!requiredHeader || requiredHeader !== 'XMLHttpRequest') {\n return false;\n }\n return true;\n};\n"],"names":["crypto"],"mappings":";;;;;;;;AAoBa,MAAA,wBAAA,GAA2B,CAAC,KAAkB,KAAA;AAGzD,EAAA,OAAO,kBAAmB,CAAA,KAAK,CAAE,CAAA,OAAA,CAAQ,MAAM,KAAK,CAAA,CAAA;AACtD,EAAA;AAMO,MAAM,mBAAsB,GAAA,CACjC,GACA,EAAA,SAAA,EACA,QACG,KAAA;AACH,EAAM,MAAA,QAAA,GAAW,IAAK,CAAA,SAAA,CAAU,QAAQ,CAAA,CAAA;AACxC,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,GAAOA,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,EAAA;AAMa,MAAA,qBAAA,GAAwB,CAAC,GAAyB,KAAA;AAC7D,EAAM,MAAA,cAAA,GAAiB,GAAI,CAAA,MAAA,CAAO,kBAAkB,CAAA,CAAA;AACpD,EAAI,IAAA,CAAC,cAAkB,IAAA,cAAA,KAAmB,gBAAkB,EAAA;AAC1D,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AACA,EAAO,OAAA,IAAA,CAAA;AACT;;;;;;"}
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ function adaptLegacyOAuthHandler(authHandler) {
4
+ return authHandler && (async (result, ctx) => authHandler(
5
+ {
6
+ fullProfile: result.fullProfile,
7
+ accessToken: result.session.accessToken,
8
+ params: {
9
+ scope: result.session.scope,
10
+ id_token: result.session.idToken,
11
+ token_type: result.session.tokenType,
12
+ expires_in: result.session.expiresInSeconds
13
+ }
14
+ },
15
+ ctx
16
+ ));
17
+ }
18
+
19
+ exports.adaptLegacyOAuthHandler = adaptLegacyOAuthHandler;
20
+ //# sourceMappingURL=adaptLegacyOAuthHandler.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptLegacyOAuthHandler.cjs.js","sources":["../../../src/lib/legacy/adaptLegacyOAuthHandler.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 {\n OAuthAuthenticatorResult,\n ProfileTransform,\n} from '@backstage/plugin-auth-node';\nimport { AuthHandler } from '../../providers';\nimport { OAuthResult } from '../oauth';\nimport { PassportProfile } from '../passport/types';\n\n/** @internal */\nexport function adaptLegacyOAuthHandler(\n authHandler?: AuthHandler<OAuthResult>,\n): ProfileTransform<OAuthAuthenticatorResult<PassportProfile>> | undefined {\n return (\n authHandler &&\n (async (result, ctx) =>\n authHandler(\n {\n fullProfile: result.fullProfile,\n accessToken: result.session.accessToken,\n params: {\n scope: result.session.scope,\n id_token: result.session.idToken,\n token_type: result.session.tokenType,\n expires_in: result.session.expiresInSeconds!,\n },\n },\n ctx,\n ))\n );\n}\n"],"names":[],"mappings":";;AAyBO,SAAS,wBACd,WACyE,EAAA;AACzE,EACE,OAAA,WAAA,KACC,OAAO,MAAA,EAAQ,GACd,KAAA,WAAA;AAAA,IACE;AAAA,MACE,aAAa,MAAO,CAAA,WAAA;AAAA,MACpB,WAAA,EAAa,OAAO,OAAQ,CAAA,WAAA;AAAA,MAC5B,MAAQ,EAAA;AAAA,QACN,KAAA,EAAO,OAAO,OAAQ,CAAA,KAAA;AAAA,QACtB,QAAA,EAAU,OAAO,OAAQ,CAAA,OAAA;AAAA,QACzB,UAAA,EAAY,OAAO,OAAQ,CAAA,SAAA;AAAA,QAC3B,UAAA,EAAY,OAAO,OAAQ,CAAA,gBAAA;AAAA,OAC7B;AAAA,KACF;AAAA,IACA,GAAA;AAAA,GACF,CAAA,CAAA;AAEN;;;;"}
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ function adaptLegacyOAuthSignInResolver(signInResolver) {
4
+ return signInResolver && (async (input, ctx) => signInResolver(
5
+ {
6
+ profile: input.profile,
7
+ result: {
8
+ fullProfile: input.result.fullProfile,
9
+ accessToken: input.result.session.accessToken,
10
+ refreshToken: input.result.session.refreshToken,
11
+ params: {
12
+ scope: input.result.session.scope,
13
+ id_token: input.result.session.idToken,
14
+ token_type: input.result.session.tokenType,
15
+ expires_in: input.result.session.expiresInSeconds
16
+ }
17
+ }
18
+ },
19
+ ctx
20
+ ));
21
+ }
22
+
23
+ exports.adaptLegacyOAuthSignInResolver = adaptLegacyOAuthSignInResolver;
24
+ //# sourceMappingURL=adaptLegacyOAuthSignInResolver.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptLegacyOAuthSignInResolver.cjs.js","sources":["../../../src/lib/legacy/adaptLegacyOAuthSignInResolver.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 {\n OAuthAuthenticatorResult,\n PassportProfile,\n SignInResolver,\n} from '@backstage/plugin-auth-node';\nimport { OAuthResult } from '../oauth';\n\n/** @internal */\nexport function adaptLegacyOAuthSignInResolver(\n signInResolver?: SignInResolver<OAuthResult>,\n): SignInResolver<OAuthAuthenticatorResult<PassportProfile>> | undefined {\n return (\n signInResolver &&\n (async (input, ctx) =>\n signInResolver(\n {\n profile: input.profile,\n result: {\n fullProfile: input.result.fullProfile,\n accessToken: input.result.session.accessToken,\n refreshToken: input.result.session.refreshToken,\n params: {\n scope: input.result.session.scope,\n id_token: input.result.session.idToken,\n token_type: input.result.session.tokenType,\n expires_in: input.result.session.expiresInSeconds!,\n },\n },\n },\n ctx,\n ))\n );\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,+BACd,cACuE,EAAA;AACvE,EACE,OAAA,cAAA,KACC,OAAO,KAAA,EAAO,GACb,KAAA,cAAA;AAAA,IACE;AAAA,MACE,SAAS,KAAM,CAAA,OAAA;AAAA,MACf,MAAQ,EAAA;AAAA,QACN,WAAA,EAAa,MAAM,MAAO,CAAA,WAAA;AAAA,QAC1B,WAAA,EAAa,KAAM,CAAA,MAAA,CAAO,OAAQ,CAAA,WAAA;AAAA,QAClC,YAAA,EAAc,KAAM,CAAA,MAAA,CAAO,OAAQ,CAAA,YAAA;AAAA,QACnC,MAAQ,EAAA;AAAA,UACN,KAAA,EAAO,KAAM,CAAA,MAAA,CAAO,OAAQ,CAAA,KAAA;AAAA,UAC5B,QAAA,EAAU,KAAM,CAAA,MAAA,CAAO,OAAQ,CAAA,OAAA;AAAA,UAC/B,UAAA,EAAY,KAAM,CAAA,MAAA,CAAO,OAAQ,CAAA,SAAA;AAAA,UACjC,UAAA,EAAY,KAAM,CAAA,MAAA,CAAO,OAAQ,CAAA,gBAAA;AAAA,SACnC;AAAA,OACF;AAAA,KACF;AAAA,IACA,GAAA;AAAA,GACF,CAAA,CAAA;AAEN;;;;"}
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ function adaptOAuthSignInResolverToLegacy(resolvers) {
4
+ const legacyResolvers = {};
5
+ for (const name of Object.keys(resolvers)) {
6
+ const resolver = resolvers[name];
7
+ legacyResolvers[name] = () => async (input, ctx) => resolver(
8
+ {
9
+ profile: input.profile,
10
+ result: {
11
+ fullProfile: input.result.fullProfile,
12
+ session: {
13
+ accessToken: input.result.accessToken,
14
+ expiresInSeconds: input.result.params.expires_in,
15
+ scope: input.result.params.scope,
16
+ idToken: input.result.params.id_token,
17
+ tokenType: input.result.params.token_type ?? "bearer",
18
+ refreshToken: input.result.refreshToken
19
+ }
20
+ }
21
+ },
22
+ ctx
23
+ );
24
+ }
25
+ return legacyResolvers;
26
+ }
27
+
28
+ exports.adaptOAuthSignInResolverToLegacy = adaptOAuthSignInResolverToLegacy;
29
+ //# sourceMappingURL=adaptOAuthSignInResolverToLegacy.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptOAuthSignInResolverToLegacy.cjs.js","sources":["../../../src/lib/legacy/adaptOAuthSignInResolverToLegacy.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 {\n OAuthAuthenticatorResult,\n PassportProfile,\n SignInResolver,\n} from '@backstage/plugin-auth-node';\nimport { OAuthResult } from '../oauth';\n\n/** @internal */\nexport function adaptOAuthSignInResolverToLegacy<\n TKeys extends string,\n>(resolvers: {\n [key in TKeys]: SignInResolver<OAuthAuthenticatorResult<PassportProfile>>;\n}): { [key in TKeys]: () => SignInResolver<OAuthResult> } {\n const legacyResolvers = {} as {\n [key in TKeys]: () => SignInResolver<OAuthResult>;\n };\n for (const name of Object.keys(resolvers) as TKeys[]) {\n const resolver = resolvers[name];\n legacyResolvers[name] = () => async (input, ctx) =>\n resolver(\n {\n profile: input.profile,\n result: {\n fullProfile: input.result.fullProfile,\n session: {\n accessToken: input.result.accessToken,\n expiresInSeconds: input.result.params.expires_in,\n scope: input.result.params.scope,\n idToken: input.result.params.id_token,\n tokenType: input.result.params.token_type ?? 'bearer',\n refreshToken: input.result.refreshToken,\n },\n },\n },\n ctx,\n );\n }\n return legacyResolvers;\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,iCAEd,SAEwD,EAAA;AACxD,EAAA,MAAM,kBAAkB,EAAC,CAAA;AAGzB,EAAA,KAAA,MAAW,IAAQ,IAAA,MAAA,CAAO,IAAK,CAAA,SAAS,CAAc,EAAA;AACpD,IAAM,MAAA,QAAA,GAAW,UAAU,IAAI,CAAA,CAAA;AAC/B,IAAA,eAAA,CAAgB,IAAI,CAAA,GAAI,MAAM,OAAO,OAAO,GAC1C,KAAA,QAAA;AAAA,MACE;AAAA,QACE,SAAS,KAAM,CAAA,OAAA;AAAA,QACf,MAAQ,EAAA;AAAA,UACN,WAAA,EAAa,MAAM,MAAO,CAAA,WAAA;AAAA,UAC1B,OAAS,EAAA;AAAA,YACP,WAAA,EAAa,MAAM,MAAO,CAAA,WAAA;AAAA,YAC1B,gBAAA,EAAkB,KAAM,CAAA,MAAA,CAAO,MAAO,CAAA,UAAA;AAAA,YACtC,KAAA,EAAO,KAAM,CAAA,MAAA,CAAO,MAAO,CAAA,KAAA;AAAA,YAC3B,OAAA,EAAS,KAAM,CAAA,MAAA,CAAO,MAAO,CAAA,QAAA;AAAA,YAC7B,SAAW,EAAA,KAAA,CAAM,MAAO,CAAA,MAAA,CAAO,UAAc,IAAA,QAAA;AAAA,YAC7C,YAAA,EAAc,MAAM,MAAO,CAAA,YAAA;AAAA,WAC7B;AAAA,SACF;AAAA,OACF;AAAA,MACA,GAAA;AAAA,KACF,CAAA;AAAA,GACJ;AACA,EAAO,OAAA,eAAA,CAAA;AACT;;;;"}
@@ -0,0 +1,220 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var url = require('url');
5
+ var errors = require('@backstage/errors');
6
+ var helpers = require('./helpers.cjs.js');
7
+ var authFlowHelpers = require('../flow/authFlowHelpers.cjs.js');
8
+ var prepareBackstageIdentityResponse = require('../../providers/prepareBackstageIdentityResponse.cjs.js');
9
+
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
+
12
+ var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
13
+
14
+ const THOUSAND_DAYS_MS = 1e3 * 24 * 60 * 60 * 1e3;
15
+ const TEN_MINUTES_MS = 600 * 1e3;
16
+ class OAuthAdapter {
17
+ constructor(handlers, options) {
18
+ this.handlers = handlers;
19
+ this.options = options;
20
+ this.baseCookieOptions = {
21
+ httpOnly: true,
22
+ sameSite: "lax"
23
+ };
24
+ }
25
+ static fromConfig(config, handlers, options) {
26
+ const { appUrl, baseUrl, isOriginAllowed } = config;
27
+ const { origin: appOrigin } = new url.URL(appUrl);
28
+ const cookieConfigurer = config.cookieConfigurer ?? helpers.defaultCookieConfigurer;
29
+ return new OAuthAdapter(handlers, {
30
+ ...options,
31
+ appOrigin,
32
+ baseUrl,
33
+ cookieConfigurer,
34
+ isOriginAllowed
35
+ });
36
+ }
37
+ baseCookieOptions;
38
+ async start(req, res) {
39
+ const scope = req.query.scope?.toString() ?? "";
40
+ const env = req.query.env?.toString();
41
+ const origin = req.query.origin?.toString();
42
+ const redirectUrl = req.query.redirectUrl?.toString();
43
+ const flow = req.query.flow?.toString();
44
+ if (!env) {
45
+ throw new errors.InputError("No env provided in request query parameters");
46
+ }
47
+ const cookieConfig = this.getCookieConfig(origin);
48
+ const nonce = crypto__default.default.randomBytes(16).toString("base64");
49
+ this.setNonceCookie(res, nonce, cookieConfig);
50
+ const state = { nonce, env, origin, redirectUrl, flow };
51
+ if (this.options.persistScopes) {
52
+ state.scope = scope;
53
+ }
54
+ const forwardReq = Object.assign(req, { scope, state });
55
+ const { url, status } = await this.handlers.start(
56
+ forwardReq
57
+ );
58
+ res.statusCode = status || 302;
59
+ res.setHeader("Location", url);
60
+ res.setHeader("Content-Length", "0");
61
+ res.end();
62
+ }
63
+ async frameHandler(req, res) {
64
+ let appOrigin = this.options.appOrigin;
65
+ try {
66
+ const state = helpers.readState(req.query.state?.toString() ?? "");
67
+ if (state.origin) {
68
+ try {
69
+ appOrigin = new url.URL(state.origin).origin;
70
+ } catch {
71
+ throw new errors.NotAllowedError("App origin is invalid, failed to parse");
72
+ }
73
+ if (!this.options.isOriginAllowed(appOrigin)) {
74
+ throw new errors.NotAllowedError(`Origin '${appOrigin}' is not allowed`);
75
+ }
76
+ }
77
+ helpers.verifyNonce(req, this.options.providerId);
78
+ const { response, refreshToken } = await this.handlers.handler(req);
79
+ const cookieConfig = this.getCookieConfig(appOrigin);
80
+ if (this.options.persistScopes && state.scope) {
81
+ this.setGrantedScopeCookie(res, state.scope, cookieConfig);
82
+ response.providerInfo.scope = state.scope;
83
+ }
84
+ if (refreshToken) {
85
+ this.setRefreshTokenCookie(res, refreshToken, cookieConfig);
86
+ }
87
+ const identity = await this.populateIdentity(response.backstageIdentity);
88
+ const responseObj = {
89
+ type: "authorization_response",
90
+ response: { ...response, backstageIdentity: identity }
91
+ };
92
+ if (state.flow === "redirect") {
93
+ if (!state.redirectUrl) {
94
+ throw new errors.InputError(
95
+ "No redirectUrl provided in request query parameters"
96
+ );
97
+ }
98
+ res.redirect(state.redirectUrl);
99
+ return void 0;
100
+ }
101
+ return authFlowHelpers.postMessageResponse(res, appOrigin, responseObj);
102
+ } catch (error) {
103
+ const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
104
+ return authFlowHelpers.postMessageResponse(res, appOrigin, {
105
+ type: "authorization_response",
106
+ error: { name, message }
107
+ });
108
+ }
109
+ }
110
+ async logout(req, res) {
111
+ if (!authFlowHelpers.ensuresXRequestedWith(req)) {
112
+ throw new errors.AuthenticationError("Invalid X-Requested-With header");
113
+ }
114
+ if (this.handlers.logout) {
115
+ const refreshToken = this.getRefreshTokenFromCookie(req);
116
+ const revokeRequest = Object.assign(req, {
117
+ refreshToken
118
+ });
119
+ await this.handlers.logout(revokeRequest);
120
+ }
121
+ const origin = req.get("origin");
122
+ const cookieConfig = this.getCookieConfig(origin);
123
+ this.removeRefreshTokenCookie(res, cookieConfig);
124
+ res.status(200).end();
125
+ }
126
+ async refresh(req, res) {
127
+ if (!authFlowHelpers.ensuresXRequestedWith(req)) {
128
+ throw new errors.AuthenticationError("Invalid X-Requested-With header");
129
+ }
130
+ if (!this.handlers.refresh) {
131
+ throw new errors.InputError(
132
+ `Refresh token is not supported for provider ${this.options.providerId}`
133
+ );
134
+ }
135
+ try {
136
+ const refreshToken = this.getRefreshTokenFromCookie(req);
137
+ if (!refreshToken) {
138
+ throw new errors.InputError("Missing session cookie");
139
+ }
140
+ let scope = req.query.scope?.toString() ?? "";
141
+ if (this.options.persistScopes) {
142
+ scope = this.getGrantedScopeFromCookie(req);
143
+ }
144
+ const forwardReq = Object.assign(req, { scope, refreshToken });
145
+ const { response, refreshToken: newRefreshToken } = await this.handlers.refresh(forwardReq);
146
+ const backstageIdentity = await this.populateIdentity(
147
+ response.backstageIdentity
148
+ );
149
+ if (newRefreshToken && newRefreshToken !== refreshToken) {
150
+ const origin = req.get("origin");
151
+ const cookieConfig = this.getCookieConfig(origin);
152
+ this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig);
153
+ }
154
+ res.status(200).json({ ...response, backstageIdentity });
155
+ } catch (error) {
156
+ throw new errors.AuthenticationError("Refresh failed", error);
157
+ }
158
+ }
159
+ /**
160
+ * If the response from the OAuth provider includes a Backstage identity, we
161
+ * make sure it's populated with all the information we can derive from the user ID.
162
+ */
163
+ async populateIdentity(identity) {
164
+ if (!identity) {
165
+ return void 0;
166
+ }
167
+ if (!identity.token) {
168
+ throw new errors.InputError(`Identity response must return a token`);
169
+ }
170
+ return prepareBackstageIdentityResponse.prepareBackstageIdentityResponse(identity);
171
+ }
172
+ setNonceCookie = (res, nonce, cookieConfig) => {
173
+ res.cookie(`${this.options.providerId}-nonce`, nonce, {
174
+ maxAge: TEN_MINUTES_MS,
175
+ ...this.baseCookieOptions,
176
+ ...cookieConfig,
177
+ path: `${cookieConfig.path}/handler`
178
+ });
179
+ };
180
+ setGrantedScopeCookie = (res, scope, cookieConfig) => {
181
+ res.cookie(`${this.options.providerId}-granted-scope`, scope, {
182
+ maxAge: THOUSAND_DAYS_MS,
183
+ ...this.baseCookieOptions,
184
+ ...cookieConfig
185
+ });
186
+ };
187
+ getRefreshTokenFromCookie = (req) => {
188
+ return req.cookies[`${this.options.providerId}-refresh-token`];
189
+ };
190
+ getGrantedScopeFromCookie = (req) => {
191
+ return req.cookies[`${this.options.providerId}-granted-scope`];
192
+ };
193
+ setRefreshTokenCookie = (res, refreshToken, cookieConfig) => {
194
+ res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
195
+ maxAge: THOUSAND_DAYS_MS,
196
+ ...this.baseCookieOptions,
197
+ ...cookieConfig
198
+ });
199
+ };
200
+ removeRefreshTokenCookie = (res, cookieConfig) => {
201
+ res.cookie(`${this.options.providerId}-refresh-token`, "", {
202
+ maxAge: 0,
203
+ ...this.baseCookieOptions,
204
+ ...cookieConfig
205
+ });
206
+ };
207
+ getCookieConfig = (origin) => {
208
+ return this.options.cookieConfigurer({
209
+ providerId: this.options.providerId,
210
+ baseUrl: this.options.baseUrl,
211
+ callbackUrl: this.options.callbackUrl,
212
+ appOrigin: origin ?? this.options.appOrigin
213
+ });
214
+ };
215
+ }
216
+
217
+ exports.OAuthAdapter = OAuthAdapter;
218
+ exports.TEN_MINUTES_MS = TEN_MINUTES_MS;
219
+ exports.THOUSAND_DAYS_MS = THOUSAND_DAYS_MS;
220
+ //# sourceMappingURL=OAuthAdapter.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthAdapter.cjs.js","sources":["../../../src/lib/oauth/OAuthAdapter.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 express, { CookieOptions } from 'express';\nimport crypto from 'crypto';\nimport { URL } from 'url';\nimport {\n AuthProviderConfig,\n AuthProviderRouteHandlers,\n BackstageIdentityResponse,\n BackstageSignInResult,\n CookieConfigurer,\n OAuthState,\n} from '@backstage/plugin-auth-node';\nimport {\n AuthenticationError,\n InputError,\n isError,\n NotAllowedError,\n} from '@backstage/errors';\nimport { defaultCookieConfigurer, readState, verifyNonce } from './helpers';\nimport {\n postMessageResponse,\n ensuresXRequestedWith,\n WebMessageResponse,\n} from '../flow';\nimport {\n OAuthHandlers,\n OAuthStartRequest,\n OAuthRefreshRequest,\n OAuthLogoutRequest,\n} from './types';\nimport { prepareBackstageIdentityResponse } from '../../providers/prepareBackstageIdentityResponse';\n\nexport const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;\nexport const TEN_MINUTES_MS = 600 * 1000;\n\n/**\n * @public\n * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead\n */\nexport type OAuthAdapterOptions = {\n providerId: string;\n persistScopes?: boolean;\n appOrigin: string;\n baseUrl: string;\n cookieConfigurer: CookieConfigurer;\n isOriginAllowed: (origin: string) => boolean;\n callbackUrl: string;\n};\n\n/**\n * @public\n * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead\n */\nexport class OAuthAdapter implements AuthProviderRouteHandlers {\n static fromConfig(\n config: AuthProviderConfig,\n handlers: OAuthHandlers,\n options: Pick<\n OAuthAdapterOptions,\n 'providerId' | 'persistScopes' | 'callbackUrl'\n >,\n ): OAuthAdapter {\n const { appUrl, baseUrl, isOriginAllowed } = config;\n const { origin: appOrigin } = new URL(appUrl);\n\n const cookieConfigurer = config.cookieConfigurer ?? defaultCookieConfigurer;\n\n return new OAuthAdapter(handlers, {\n ...options,\n appOrigin,\n baseUrl,\n cookieConfigurer,\n isOriginAllowed,\n });\n }\n\n private readonly baseCookieOptions: CookieOptions;\n\n constructor(\n private readonly handlers: OAuthHandlers,\n private readonly options: OAuthAdapterOptions,\n ) {\n this.baseCookieOptions = {\n httpOnly: true,\n sameSite: 'lax',\n };\n }\n\n async start(req: express.Request, res: express.Response): Promise<void> {\n // retrieve scopes from request\n const scope = req.query.scope?.toString() ?? '';\n const env = req.query.env?.toString();\n const origin = req.query.origin?.toString();\n const redirectUrl = req.query.redirectUrl?.toString();\n const flow = req.query.flow?.toString();\n\n if (!env) {\n throw new InputError('No env provided in request query parameters');\n }\n\n const cookieConfig = this.getCookieConfig(origin);\n\n const nonce = crypto.randomBytes(16).toString('base64');\n // set a nonce cookie before redirecting to oauth provider\n this.setNonceCookie(res, nonce, cookieConfig);\n\n const state: OAuthState = { nonce, env, origin, redirectUrl, flow };\n\n // If scopes are persisted then we pass them through the state so that we\n // can set the cookie on successful auth\n if (this.options.persistScopes) {\n state.scope = scope;\n }\n const forwardReq = Object.assign(req, { scope, state });\n\n const { url, status } = await this.handlers.start(\n forwardReq as OAuthStartRequest,\n );\n\n res.statusCode = status || 302;\n res.setHeader('Location', url);\n res.setHeader('Content-Length', '0');\n res.end();\n }\n\n async frameHandler(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n let appOrigin = this.options.appOrigin;\n\n try {\n const state: OAuthState = readState(req.query.state?.toString() ?? '');\n\n if (state.origin) {\n try {\n appOrigin = new URL(state.origin).origin;\n } catch {\n throw new NotAllowedError('App origin is invalid, failed to parse');\n }\n if (!this.options.isOriginAllowed(appOrigin)) {\n throw new NotAllowedError(`Origin '${appOrigin}' is not allowed`);\n }\n }\n\n // verify nonce cookie and state cookie on callback\n verifyNonce(req, this.options.providerId);\n\n const { response, refreshToken } = await this.handlers.handler(req);\n\n const cookieConfig = this.getCookieConfig(appOrigin);\n\n // Store the scope that we have been granted for this session. This is useful if\n // the provider does not return granted scopes on refresh or if they are normalized.\n if (this.options.persistScopes && state.scope) {\n this.setGrantedScopeCookie(res, state.scope, cookieConfig);\n response.providerInfo.scope = state.scope;\n }\n\n if (refreshToken) {\n // set new refresh token\n this.setRefreshTokenCookie(res, refreshToken, cookieConfig);\n }\n\n const identity = await this.populateIdentity(response.backstageIdentity);\n\n const responseObj: WebMessageResponse = {\n type: 'authorization_response',\n response: { ...response, backstageIdentity: identity },\n };\n\n if (state.flow === 'redirect') {\n if (!state.redirectUrl) {\n throw new InputError(\n 'No redirectUrl provided in request query parameters',\n );\n }\n res.redirect(state.redirectUrl);\n return undefined;\n }\n // post message back to popup if successful\n return postMessageResponse(res, appOrigin, responseObj);\n } catch (error) {\n const { name, message } = isError(error)\n ? error\n : new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value\n // post error message back to popup if failure\n return postMessageResponse(res, appOrigin, {\n type: 'authorization_response',\n error: { name, message },\n });\n }\n }\n\n async logout(req: express.Request, res: express.Response): Promise<void> {\n if (!ensuresXRequestedWith(req)) {\n throw new AuthenticationError('Invalid X-Requested-With header');\n }\n\n if (this.handlers.logout) {\n const refreshToken = this.getRefreshTokenFromCookie(req);\n const revokeRequest: OAuthLogoutRequest = Object.assign(req, {\n refreshToken,\n });\n await this.handlers.logout(revokeRequest);\n }\n\n // remove refresh token cookie if it is set\n const origin = req.get('origin');\n const cookieConfig = this.getCookieConfig(origin);\n this.removeRefreshTokenCookie(res, cookieConfig);\n\n res.status(200).end();\n }\n\n async refresh(req: express.Request, res: express.Response): Promise<void> {\n if (!ensuresXRequestedWith(req)) {\n throw new AuthenticationError('Invalid X-Requested-With header');\n }\n\n if (!this.handlers.refresh) {\n throw new InputError(\n `Refresh token is not supported for provider ${this.options.providerId}`,\n );\n }\n\n try {\n const refreshToken = this.getRefreshTokenFromCookie(req);\n\n // throw error if refresh token is missing in the request\n if (!refreshToken) {\n throw new InputError('Missing session cookie');\n }\n\n let scope = req.query.scope?.toString() ?? '';\n if (this.options.persistScopes) {\n scope = this.getGrantedScopeFromCookie(req);\n }\n const forwardReq = Object.assign(req, { scope, refreshToken });\n\n // get new access_token\n const { response, refreshToken: newRefreshToken } =\n await this.handlers.refresh(forwardReq as OAuthRefreshRequest);\n\n const backstageIdentity = await this.populateIdentity(\n response.backstageIdentity,\n );\n\n if (newRefreshToken && newRefreshToken !== refreshToken) {\n const origin = req.get('origin');\n const cookieConfig = this.getCookieConfig(origin);\n this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig);\n }\n\n res.status(200).json({ ...response, backstageIdentity });\n } catch (error) {\n throw new AuthenticationError('Refresh failed', error);\n }\n }\n\n /**\n * If the response from the OAuth provider includes a Backstage identity, we\n * make sure it's populated with all the information we can derive from the user ID.\n */\n private async populateIdentity(\n identity?: BackstageSignInResult,\n ): Promise<BackstageIdentityResponse | undefined> {\n if (!identity) {\n return undefined;\n }\n if (!identity.token) {\n throw new InputError(`Identity response must return a token`);\n }\n\n return prepareBackstageIdentityResponse(identity);\n }\n\n private setNonceCookie = (\n res: express.Response,\n nonce: string,\n cookieConfig: ReturnType<CookieConfigurer>,\n ) => {\n res.cookie(`${this.options.providerId}-nonce`, nonce, {\n maxAge: TEN_MINUTES_MS,\n ...this.baseCookieOptions,\n ...cookieConfig,\n path: `${cookieConfig.path}/handler`,\n });\n };\n\n private setGrantedScopeCookie = (\n res: express.Response,\n scope: string,\n cookieConfig: ReturnType<CookieConfigurer>,\n ) => {\n res.cookie(`${this.options.providerId}-granted-scope`, scope, {\n maxAge: THOUSAND_DAYS_MS,\n ...this.baseCookieOptions,\n ...cookieConfig,\n });\n };\n\n private getRefreshTokenFromCookie = (req: express.Request) => {\n return req.cookies[`${this.options.providerId}-refresh-token`];\n };\n\n private getGrantedScopeFromCookie = (req: express.Request) => {\n return req.cookies[`${this.options.providerId}-granted-scope`];\n };\n\n private setRefreshTokenCookie = (\n res: express.Response,\n refreshToken: string,\n cookieConfig: ReturnType<CookieConfigurer>,\n ) => {\n res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {\n maxAge: THOUSAND_DAYS_MS,\n ...this.baseCookieOptions,\n ...cookieConfig,\n });\n };\n\n private removeRefreshTokenCookie = (\n res: express.Response,\n cookieConfig: ReturnType<CookieConfigurer>,\n ) => {\n res.cookie(`${this.options.providerId}-refresh-token`, '', {\n maxAge: 0,\n ...this.baseCookieOptions,\n ...cookieConfig,\n });\n };\n\n private getCookieConfig = (origin?: string) => {\n return this.options.cookieConfigurer({\n providerId: this.options.providerId,\n baseUrl: this.options.baseUrl,\n callbackUrl: this.options.callbackUrl,\n appOrigin: origin ?? this.options.appOrigin,\n });\n };\n}\n"],"names":["URL","defaultCookieConfigurer","InputError","crypto","readState","NotAllowedError","verifyNonce","postMessageResponse","isError","ensuresXRequestedWith","AuthenticationError","prepareBackstageIdentityResponse"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ var pluginAuthNode = require('@backstage/plugin-auth-node');
4
+
5
+ const OAuthEnvironmentHandler = pluginAuthNode.OAuthEnvironmentHandler;
6
+
7
+ exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
8
+ //# sourceMappingURL=OAuthEnvironmentHandler.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthEnvironmentHandler.cjs.js","sources":["../../../src/lib/oauth/OAuthEnvironmentHandler.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 { OAuthEnvironmentHandler as _OAuthEnvironmentHandler } from '@backstage/plugin-auth-node';\n\n/**\n * @public\n * @deprecated import from `@backstage/plugin-auth-node` instead\n */\nexport const OAuthEnvironmentHandler = _OAuthEnvironmentHandler;\n"],"names":["_OAuthEnvironmentHandler"],"mappings":";;;;AAsBO,MAAM,uBAA0B,GAAAA;;;;"}
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var pluginAuthNode = require('@backstage/plugin-auth-node');
4
+
5
+ const readState = pluginAuthNode.decodeOAuthState;
6
+ const encodeState = pluginAuthNode.encodeOAuthState;
7
+ const verifyNonce = (req, providerId) => {
8
+ const cookieNonce = req.cookies[`${providerId}-nonce`];
9
+ const state = readState(req.query.state?.toString() ?? "");
10
+ const stateNonce = state.nonce;
11
+ if (!cookieNonce) {
12
+ throw new Error("Auth response is missing cookie nonce");
13
+ }
14
+ if (stateNonce.length === 0) {
15
+ throw new Error("Auth response is missing state nonce");
16
+ }
17
+ if (cookieNonce !== stateNonce) {
18
+ throw new Error("Invalid nonce");
19
+ }
20
+ };
21
+ const defaultCookieConfigurer = ({
22
+ callbackUrl,
23
+ providerId,
24
+ appOrigin
25
+ }) => {
26
+ const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
27
+ const secure = protocol === "https:";
28
+ let sameSite = "lax";
29
+ if (new URL(appOrigin).hostname !== domain && secure) {
30
+ sameSite = "none";
31
+ }
32
+ const path = pathname.endsWith(`${providerId}/handler/frame`) ? pathname.slice(0, -"/handler/frame".length) : `${pathname}/${providerId}`;
33
+ return { domain, path, secure, sameSite };
34
+ };
35
+
36
+ exports.defaultCookieConfigurer = defaultCookieConfigurer;
37
+ exports.encodeState = encodeState;
38
+ exports.readState = readState;
39
+ exports.verifyNonce = verifyNonce;
40
+ //# sourceMappingURL=helpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../../src/lib/oauth/helpers.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 express from 'express';\nimport {\n CookieConfigurer,\n OAuthState,\n decodeOAuthState,\n encodeOAuthState,\n} from '@backstage/plugin-auth-node';\n\n/**\n * @public\n * @deprecated Use `decodeOAuthState` from `@backstage/plugin-auth-node` instead\n */\nexport const readState = decodeOAuthState;\n\n/**\n * @public\n * @deprecated Use `encodeOAuthState` from `@backstage/plugin-auth-node` instead\n */\nexport const encodeState = encodeOAuthState;\n\n/**\n * @public\n * @deprecated Use inline logic to make sure the session and state nonce matches instead.\n */\nexport const verifyNonce = (req: express.Request, providerId: string) => {\n const cookieNonce = req.cookies[`${providerId}-nonce`];\n const state: OAuthState = readState(req.query.state?.toString() ?? '');\n const stateNonce = state.nonce;\n\n if (!cookieNonce) {\n throw new Error('Auth response is missing cookie nonce');\n }\n if (stateNonce.length === 0) {\n throw new Error('Auth response is missing state nonce');\n }\n if (cookieNonce !== stateNonce) {\n throw new Error('Invalid nonce');\n }\n};\n\nexport const defaultCookieConfigurer: CookieConfigurer = ({\n callbackUrl,\n providerId,\n appOrigin,\n}) => {\n const { hostname: domain, pathname, protocol } = new URL(callbackUrl);\n const secure = protocol === 'https:';\n\n // For situations where the auth-backend is running on a\n // different domain than the app, we set the SameSite attribute\n // to 'none' to allow third-party access to the cookie, but\n // only if it's in a secure context (https).\n let sameSite: ReturnType<CookieConfigurer>['sameSite'] = 'lax';\n if (new URL(appOrigin).hostname !== domain && secure) {\n sameSite = 'none';\n }\n\n // If the provider supports callbackUrls, the pathname will\n // contain the complete path to the frame handler so we need\n // to slice off the trailing part of the path.\n const path = pathname.endsWith(`${providerId}/handler/frame`)\n ? pathname.slice(0, -'/handler/frame'.length)\n : `${pathname}/${providerId}`;\n\n return { domain, path, secure, sameSite };\n};\n"],"names":["decodeOAuthState","encodeOAuthState"],"mappings":";;;;AA4BO,MAAM,SAAY,GAAAA,gCAAA;AAMlB,MAAM,WAAc,GAAAC,gCAAA;AAMd,MAAA,WAAA,GAAc,CAAC,GAAA,EAAsB,UAAuB,KAAA;AACvE,EAAA,MAAM,WAAc,GAAA,GAAA,CAAI,OAAQ,CAAA,CAAA,EAAG,UAAU,CAAQ,MAAA,CAAA,CAAA,CAAA;AACrD,EAAA,MAAM,QAAoB,SAAU,CAAA,GAAA,CAAI,MAAM,KAAO,EAAA,QAAA,MAAc,EAAE,CAAA,CAAA;AACrE,EAAA,MAAM,aAAa,KAAM,CAAA,KAAA,CAAA;AAEzB,EAAA,IAAI,CAAC,WAAa,EAAA;AAChB,IAAM,MAAA,IAAI,MAAM,uCAAuC,CAAA,CAAA;AAAA,GACzD;AACA,EAAI,IAAA,UAAA,CAAW,WAAW,CAAG,EAAA;AAC3B,IAAM,MAAA,IAAI,MAAM,sCAAsC,CAAA,CAAA;AAAA,GACxD;AACA,EAAA,IAAI,gBAAgB,UAAY,EAAA;AAC9B,IAAM,MAAA,IAAI,MAAM,eAAe,CAAA,CAAA;AAAA,GACjC;AACF,EAAA;AAEO,MAAM,0BAA4C,CAAC;AAAA,EACxD,WAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AACF,CAAM,KAAA;AACJ,EAAM,MAAA,EAAE,UAAU,MAAQ,EAAA,QAAA,EAAU,UAAa,GAAA,IAAI,IAAI,WAAW,CAAA,CAAA;AACpE,EAAA,MAAM,SAAS,QAAa,KAAA,QAAA,CAAA;AAM5B,EAAA,IAAI,QAAqD,GAAA,KAAA,CAAA;AACzD,EAAA,IAAI,IAAI,GAAI,CAAA,SAAS,CAAE,CAAA,QAAA,KAAa,UAAU,MAAQ,EAAA;AACpD,IAAW,QAAA,GAAA,MAAA,CAAA;AAAA,GACb;AAKA,EAAA,MAAM,OAAO,QAAS,CAAA,QAAA,CAAS,CAAG,EAAA,UAAU,gBAAgB,CACxD,GAAA,QAAA,CAAS,KAAM,CAAA,CAAA,EAAG,CAAC,gBAAiB,CAAA,MAAM,IAC1C,CAAG,EAAA,QAAQ,IAAI,UAAU,CAAA,CAAA,CAAA;AAE7B,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAM,EAAA,MAAA,EAAQ,QAAS,EAAA,CAAA;AAC1C;;;;;;;"}