@backstage/plugin-auth-backend 0.24.5 → 0.25.0-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 (84) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/config.d.ts +9 -58
  3. package/dist/authPlugin.cjs.js +4 -8
  4. package/dist/authPlugin.cjs.js.map +1 -1
  5. package/dist/database/AuthDatabase.cjs.js +0 -16
  6. package/dist/database/AuthDatabase.cjs.js.map +1 -1
  7. package/dist/identity/StaticTokenIssuer.cjs.js +14 -21
  8. package/dist/identity/StaticTokenIssuer.cjs.js.map +1 -1
  9. package/dist/identity/TokenFactory.cjs.js +11 -76
  10. package/dist/identity/TokenFactory.cjs.js.map +1 -1
  11. package/dist/identity/issueUserToken.cjs.js +98 -0
  12. package/dist/identity/issueUserToken.cjs.js.map +1 -0
  13. package/dist/index.cjs.js +0 -26
  14. package/dist/index.cjs.js.map +1 -1
  15. package/dist/index.d.ts +1 -850
  16. package/dist/lib/catalog/CatalogIdentityClient.cjs.js +11 -20
  17. package/dist/lib/catalog/CatalogIdentityClient.cjs.js.map +1 -1
  18. package/dist/lib/resolvers/CatalogAuthResolverContext.cjs.js +16 -21
  19. package/dist/lib/resolvers/CatalogAuthResolverContext.cjs.js.map +1 -1
  20. package/dist/providers/router.cjs.js +2 -9
  21. package/dist/providers/router.cjs.js.map +1 -1
  22. package/dist/service/router.cjs.js +13 -18
  23. package/dist/service/router.cjs.js.map +1 -1
  24. package/package.json +16 -58
  25. package/dist/lib/flow/authFlowHelpers.cjs.js +0 -43
  26. package/dist/lib/flow/authFlowHelpers.cjs.js.map +0 -1
  27. package/dist/lib/legacy/adaptLegacyOAuthHandler.cjs.js +0 -20
  28. package/dist/lib/legacy/adaptLegacyOAuthHandler.cjs.js.map +0 -1
  29. package/dist/lib/legacy/adaptLegacyOAuthSignInResolver.cjs.js +0 -24
  30. package/dist/lib/legacy/adaptLegacyOAuthSignInResolver.cjs.js.map +0 -1
  31. package/dist/lib/legacy/adaptOAuthSignInResolverToLegacy.cjs.js +0 -29
  32. package/dist/lib/legacy/adaptOAuthSignInResolverToLegacy.cjs.js.map +0 -1
  33. package/dist/lib/oauth/OAuthAdapter.cjs.js +0 -220
  34. package/dist/lib/oauth/OAuthAdapter.cjs.js.map +0 -1
  35. package/dist/lib/oauth/OAuthEnvironmentHandler.cjs.js +0 -8
  36. package/dist/lib/oauth/OAuthEnvironmentHandler.cjs.js.map +0 -1
  37. package/dist/lib/oauth/helpers.cjs.js +0 -40
  38. package/dist/lib/oauth/helpers.cjs.js.map +0 -1
  39. package/dist/lib/passport/PassportStrategyHelper.cjs.js +0 -49
  40. package/dist/lib/passport/PassportStrategyHelper.cjs.js.map +0 -1
  41. package/dist/providers/atlassian/provider.cjs.js +0 -20
  42. package/dist/providers/atlassian/provider.cjs.js.map +0 -1
  43. package/dist/providers/auth0/provider.cjs.js +0 -20
  44. package/dist/providers/auth0/provider.cjs.js.map +0 -1
  45. package/dist/providers/aws-alb/provider.cjs.js +0 -18
  46. package/dist/providers/aws-alb/provider.cjs.js.map +0 -1
  47. package/dist/providers/azure-easyauth/provider.cjs.js +0 -18
  48. package/dist/providers/azure-easyauth/provider.cjs.js.map +0 -1
  49. package/dist/providers/bitbucket/provider.cjs.js +0 -25
  50. package/dist/providers/bitbucket/provider.cjs.js.map +0 -1
  51. package/dist/providers/bitbucketServer/provider.cjs.js +0 -46
  52. package/dist/providers/bitbucketServer/provider.cjs.js.map +0 -1
  53. package/dist/providers/cloudflare-access/provider.cjs.js +0 -22
  54. package/dist/providers/cloudflare-access/provider.cjs.js.map +0 -1
  55. package/dist/providers/createAuthProviderIntegration.cjs.js +0 -11
  56. package/dist/providers/createAuthProviderIntegration.cjs.js.map +0 -1
  57. package/dist/providers/gcp-iap/provider.cjs.js +0 -18
  58. package/dist/providers/gcp-iap/provider.cjs.js.map +0 -1
  59. package/dist/providers/github/provider.cjs.js +0 -61
  60. package/dist/providers/github/provider.cjs.js.map +0 -1
  61. package/dist/providers/gitlab/provider.cjs.js +0 -20
  62. package/dist/providers/gitlab/provider.cjs.js.map +0 -1
  63. package/dist/providers/google/provider.cjs.js +0 -26
  64. package/dist/providers/google/provider.cjs.js.map +0 -1
  65. package/dist/providers/microsoft/provider.cjs.js +0 -27
  66. package/dist/providers/microsoft/provider.cjs.js.map +0 -1
  67. package/dist/providers/oauth2/provider.cjs.js +0 -20
  68. package/dist/providers/oauth2/provider.cjs.js.map +0 -1
  69. package/dist/providers/oauth2-proxy/provider.cjs.js +0 -18
  70. package/dist/providers/oauth2-proxy/provider.cjs.js.map +0 -1
  71. package/dist/providers/oidc/provider.cjs.js +0 -37
  72. package/dist/providers/oidc/provider.cjs.js.map +0 -1
  73. package/dist/providers/okta/provider.cjs.js +0 -47
  74. package/dist/providers/okta/provider.cjs.js.map +0 -1
  75. package/dist/providers/onelogin/provider.cjs.js +0 -20
  76. package/dist/providers/onelogin/provider.cjs.js.map +0 -1
  77. package/dist/providers/prepareBackstageIdentityResponse.cjs.js +0 -8
  78. package/dist/providers/prepareBackstageIdentityResponse.cjs.js.map +0 -1
  79. package/dist/providers/providers.cjs.js +0 -62
  80. package/dist/providers/providers.cjs.js.map +0 -1
  81. package/dist/providers/resolvers.cjs.js +0 -27
  82. package/dist/providers/resolvers.cjs.js.map +0 -1
  83. package/dist/providers/saml/provider.cjs.js +0 -121
  84. package/dist/providers/saml/provider.cjs.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.25.0-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0d606ac: Added the configuration flag `auth.omitIdentityTokenOwnershipClaim` that causes issued user tokens to no longer contain the `ent` claim that represents the ownership references of the user.
8
+
9
+ The benefit of this new flag is that issued user tokens will be much smaller in
10
+ size, but they will no longer be self-contained. This means that any consumers
11
+ of the token that require access to the ownership claims now need to call the
12
+ `/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is
13
+ done automatically, as clients will still receive the full set of claims during
14
+ authentication, while plugin backends will need to use the `UserInfoService`
15
+ which already calls the user info endpoint if necessary.
16
+
17
+ When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work:
18
+
19
+ ```ts
20
+ const { token } = await ctx.issueToken({
21
+ claims: { sub: entityRef, ent: [entityRef] },
22
+ });
23
+ return { token }; // WARNING: This will not work with the flag enabled
24
+ ```
25
+
26
+ Instead, the sign-in resolver should directly return the result:
27
+
28
+ ```ts
29
+ return ctx.issueToken({
30
+ claims: { sub: entityRef, ent: [entityRef] },
31
+ });
32
+ ```
33
+
34
+ - 72d019d: Removed various typos
35
+ - b128ed9: The `static` key store now issues tokens with the same structure as other key stores. Tokens now include the `typ` field in the header and the `uip` (user identity proof) in the payload.
36
+ - Updated dependencies
37
+ - @backstage/plugin-catalog-node@1.17.0-next.1
38
+ - @backstage/plugin-auth-node@0.6.3-next.1
39
+ - @backstage/backend-plugin-api@1.3.1-next.1
40
+ - @backstage/catalog-model@1.7.3
41
+ - @backstage/config@1.3.2
42
+ - @backstage/errors@1.2.7
43
+ - @backstage/types@1.2.1
44
+
45
+ ## 0.25.0-next.0
46
+
47
+ ### Minor Changes
48
+
49
+ - 57221d9: **BREAKING**: Removed support for the old backend system, and removed all deprecated exports.
50
+
51
+ If you were using one of the deprecated imports from this package, you will have to follow the instructions in their respective deprecation notices before upgrading. Most of the general utilities are available from `@backstage/plugin-auth-node`, and the specific auth providers are available from dedicated packages such as for example `@backstage/plugin-auth-backend-module-github-provider`. See [the auth docs](https://backstage.io/docs/auth/) for specific instructions.
52
+
53
+ ### Patch Changes
54
+
55
+ - Updated dependencies
56
+ - @backstage/plugin-catalog-node@1.17.0-next.0
57
+ - @backstage/backend-plugin-api@1.3.1-next.0
58
+ - @backstage/plugin-auth-node@0.6.3-next.0
59
+ - @backstage/catalog-model@1.7.3
60
+ - @backstage/config@1.3.2
61
+ - @backstage/errors@1.2.7
62
+ - @backstage/types@1.2.1
63
+
3
64
  ## 0.24.5
4
65
 
5
66
  ### Patch Changes
package/config.d.ts CHANGED
@@ -43,6 +43,15 @@ export interface Config {
43
43
  */
44
44
  identityTokenAlgorithm?: string;
45
45
 
46
+ /**
47
+ * Whether to omit the entity ownership references (`ent`) claim from the
48
+ * identity token. If this is enabled the `ent` claim will only be available
49
+ * via the user info endpoint and the `UserInfoService`.
50
+ *
51
+ * Defaults to `false`.
52
+ */
53
+ omitIdentityTokenOwnershipClaim?: boolean;
54
+
46
55
  /** To control how to store JWK data in auth-backend */
47
56
  keyStore?: {
48
57
  provider?: 'database' | 'memory' | 'firestore' | 'static';
@@ -84,64 +93,6 @@ export interface Config {
84
93
  };
85
94
  };
86
95
 
87
- /**
88
- * The available auth-provider options and attributes
89
- * @additionalProperties true
90
- */
91
- providers?: {
92
- /** @visibility frontend */
93
- saml?: {
94
- entryPoint: string;
95
- logoutUrl?: string;
96
- issuer: string;
97
- /**
98
- * @visibility secret
99
- */
100
- cert: string;
101
- audience?: string;
102
- /**
103
- * @visibility secret
104
- */
105
- privateKey?: string;
106
- authnContext?: string[];
107
- identifierFormat?: string;
108
- /**
109
- * @visibility secret
110
- */
111
- decryptionPvk?: string;
112
- signatureAlgorithm?: 'sha256' | 'sha512';
113
- digestAlgorithm?: string;
114
- acceptedClockSkewMs?: number;
115
- };
116
- /** @visibility frontend */
117
- auth0?: {
118
- [authEnv: string]: {
119
- clientId: string;
120
- /**
121
- * @visibility secret
122
- */
123
- clientSecret: string;
124
- domain: string;
125
- callbackUrl?: string;
126
- audience?: string;
127
- connection?: string;
128
- connectionScope?: string;
129
- };
130
- };
131
- /** @visibility frontend */
132
- onelogin?: {
133
- [authEnv: string]: {
134
- clientId: string;
135
- /**
136
- * @visibility secret
137
- */
138
- clientSecret: string;
139
- issuer: string;
140
- callbackUrl?: string;
141
- };
142
- };
143
- };
144
-
145
96
  /**
146
97
  * The backstage token expiration.
147
98
  */
@@ -2,7 +2,7 @@
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
  var pluginAuthNode = require('@backstage/plugin-auth-node');
5
- var alpha = require('@backstage/plugin-catalog-node/alpha');
5
+ var pluginCatalogNode = require('@backstage/plugin-catalog-node');
6
6
  var router = require('./service/router.cjs.js');
7
7
 
8
8
  const authPlugin = backendPluginApi.createBackendPlugin({
@@ -36,8 +36,7 @@ const authPlugin = backendPluginApi.createBackendPlugin({
36
36
  database: backendPluginApi.coreServices.database,
37
37
  discovery: backendPluginApi.coreServices.discovery,
38
38
  auth: backendPluginApi.coreServices.auth,
39
- httpAuth: backendPluginApi.coreServices.httpAuth,
40
- catalogApi: alpha.catalogServiceRef
39
+ catalog: pluginCatalogNode.catalogServiceRef
41
40
  },
42
41
  async init({
43
42
  httpRouter,
@@ -46,8 +45,7 @@ const authPlugin = backendPluginApi.createBackendPlugin({
46
45
  database,
47
46
  discovery,
48
47
  auth,
49
- httpAuth,
50
- catalogApi
48
+ catalog
51
49
  }) {
52
50
  const router$1 = await router.createRouter({
53
51
  logger,
@@ -55,10 +53,8 @@ const authPlugin = backendPluginApi.createBackendPlugin({
55
53
  database,
56
54
  discovery,
57
55
  auth,
58
- httpAuth,
59
- catalogApi,
56
+ catalog,
60
57
  providerFactories: Object.fromEntries(providers),
61
- disableDefaultProviderFactories: true,
62
58
  ownershipResolver
63
59
  });
64
60
  httpRouter.addAuthPolicy({
@@ -1 +1 @@
1
- {"version":3,"file":"authPlugin.cjs.js","sources":["../src/authPlugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport {\n authOwnershipResolutionExtensionPoint,\n AuthOwnershipResolver,\n AuthProviderFactory,\n authProvidersExtensionPoint,\n} from '@backstage/plugin-auth-node';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';\nimport { createRouter } from './service/router';\n\n/**\n * Auth plugin\n *\n * @public\n */\nexport const authPlugin = createBackendPlugin({\n pluginId: 'auth',\n register(reg) {\n const providers = new Map<string, AuthProviderFactory>();\n let ownershipResolver: AuthOwnershipResolver | undefined = undefined;\n\n reg.registerExtensionPoint(authProvidersExtensionPoint, {\n registerProvider({ providerId, factory }) {\n if (providers.has(providerId)) {\n throw new Error(\n `Auth provider '${providerId}' was already registered`,\n );\n }\n providers.set(providerId, factory);\n },\n });\n\n reg.registerExtensionPoint(authOwnershipResolutionExtensionPoint, {\n setAuthOwnershipResolver(resolver) {\n if (ownershipResolver) {\n throw new Error('Auth ownership resolver is already set');\n }\n ownershipResolver = resolver;\n },\n });\n\n reg.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n database: coreServices.database,\n discovery: coreServices.discovery,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n catalogApi: catalogServiceRef,\n },\n async init({\n httpRouter,\n logger,\n config,\n database,\n discovery,\n auth,\n httpAuth,\n catalogApi,\n }) {\n const router = await createRouter({\n logger,\n config,\n database,\n discovery,\n auth,\n httpAuth,\n catalogApi,\n providerFactories: Object.fromEntries(providers),\n disableDefaultProviderFactories: true,\n ownershipResolver,\n });\n httpRouter.addAuthPolicy({\n path: '/',\n allow: 'unauthenticated',\n });\n httpRouter.use(router);\n },\n });\n },\n});\n"],"names":["createBackendPlugin","authProvidersExtensionPoint","authOwnershipResolutionExtensionPoint","coreServices","catalogServiceRef","router","createRouter"],"mappings":";;;;;;;AAkCO,MAAM,aAAaA,oCAAoB,CAAA;AAAA,EAC5C,QAAU,EAAA,MAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,SAAA,uBAAgB,GAAiC,EAAA;AACvD,IAAA,IAAI,iBAAuD,GAAA,KAAA,CAAA;AAE3D,IAAA,GAAA,CAAI,uBAAuBC,0CAA6B,EAAA;AAAA,MACtD,gBAAiB,CAAA,EAAE,UAAY,EAAA,OAAA,EAAW,EAAA;AACxC,QAAI,IAAA,SAAA,CAAU,GAAI,CAAA,UAAU,CAAG,EAAA;AAC7B,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,kBAAkB,UAAU,CAAA,wBAAA;AAAA,WAC9B;AAAA;AAEF,QAAU,SAAA,CAAA,GAAA,CAAI,YAAY,OAAO,CAAA;AAAA;AACnC,KACD,CAAA;AAED,IAAA,GAAA,CAAI,uBAAuBC,oDAAuC,EAAA;AAAA,MAChE,yBAAyB,QAAU,EAAA;AACjC,QAAA,IAAI,iBAAmB,EAAA;AACrB,UAAM,MAAA,IAAI,MAAM,wCAAwC,CAAA;AAAA;AAE1D,QAAoB,iBAAA,GAAA,QAAA;AAAA;AACtB,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,YAAYC,6BAAa,CAAA,UAAA;AAAA,QACzB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,UAAY,EAAAC;AAAA,OACd;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,UAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,SAAA;AAAA,QACA,IAAA;AAAA,QACA,QAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAM,MAAAC,QAAA,GAAS,MAAMC,mBAAa,CAAA;AAAA,UAChC,MAAA;AAAA,UACA,MAAA;AAAA,UACA,QAAA;AAAA,UACA,SAAA;AAAA,UACA,IAAA;AAAA,UACA,QAAA;AAAA,UACA,UAAA;AAAA,UACA,iBAAA,EAAmB,MAAO,CAAA,WAAA,CAAY,SAAS,CAAA;AAAA,UAC/C,+BAAiC,EAAA,IAAA;AAAA,UACjC;AAAA,SACD,CAAA;AACD,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,IAAM,EAAA,GAAA;AAAA,UACN,KAAO,EAAA;AAAA,SACR,CAAA;AACD,QAAA,UAAA,CAAW,IAAID,QAAM,CAAA;AAAA;AACvB,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"authPlugin.cjs.js","sources":["../src/authPlugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport {\n authOwnershipResolutionExtensionPoint,\n AuthOwnershipResolver,\n AuthProviderFactory,\n authProvidersExtensionPoint,\n} from '@backstage/plugin-auth-node';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createRouter } from './service/router';\n\n/**\n * Auth plugin\n *\n * @public\n */\nexport const authPlugin = createBackendPlugin({\n pluginId: 'auth',\n register(reg) {\n const providers = new Map<string, AuthProviderFactory>();\n let ownershipResolver: AuthOwnershipResolver | undefined = undefined;\n\n reg.registerExtensionPoint(authProvidersExtensionPoint, {\n registerProvider({ providerId, factory }) {\n if (providers.has(providerId)) {\n throw new Error(\n `Auth provider '${providerId}' was already registered`,\n );\n }\n providers.set(providerId, factory);\n },\n });\n\n reg.registerExtensionPoint(authOwnershipResolutionExtensionPoint, {\n setAuthOwnershipResolver(resolver) {\n if (ownershipResolver) {\n throw new Error('Auth ownership resolver is already set');\n }\n ownershipResolver = resolver;\n },\n });\n\n reg.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n database: coreServices.database,\n discovery: coreServices.discovery,\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n },\n async init({\n httpRouter,\n logger,\n config,\n database,\n discovery,\n auth,\n catalog,\n }) {\n const router = await createRouter({\n logger,\n config,\n database,\n discovery,\n auth,\n catalog,\n providerFactories: Object.fromEntries(providers),\n ownershipResolver,\n });\n httpRouter.addAuthPolicy({\n path: '/',\n allow: 'unauthenticated',\n });\n httpRouter.use(router);\n },\n });\n },\n});\n"],"names":["createBackendPlugin","authProvidersExtensionPoint","authOwnershipResolutionExtensionPoint","coreServices","catalogServiceRef","router","createRouter"],"mappings":";;;;;;;AAkCO,MAAM,aAAaA,oCAAoB,CAAA;AAAA,EAC5C,QAAU,EAAA,MAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,SAAA,uBAAgB,GAAiC,EAAA;AACvD,IAAA,IAAI,iBAAuD,GAAA,KAAA,CAAA;AAE3D,IAAA,GAAA,CAAI,uBAAuBC,0CAA6B,EAAA;AAAA,MACtD,gBAAiB,CAAA,EAAE,UAAY,EAAA,OAAA,EAAW,EAAA;AACxC,QAAI,IAAA,SAAA,CAAU,GAAI,CAAA,UAAU,CAAG,EAAA;AAC7B,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,kBAAkB,UAAU,CAAA,wBAAA;AAAA,WAC9B;AAAA;AAEF,QAAU,SAAA,CAAA,GAAA,CAAI,YAAY,OAAO,CAAA;AAAA;AACnC,KACD,CAAA;AAED,IAAA,GAAA,CAAI,uBAAuBC,oDAAuC,EAAA;AAAA,MAChE,yBAAyB,QAAU,EAAA;AACjC,QAAA,IAAI,iBAAmB,EAAA;AACrB,UAAM,MAAA,IAAI,MAAM,wCAAwC,CAAA;AAAA;AAE1D,QAAoB,iBAAA,GAAA,QAAA;AAAA;AACtB,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,YAAYC,6BAAa,CAAA,UAAA;AAAA,QACzB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,OAAS,EAAAC;AAAA,OACX;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,UAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,SAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAM,MAAAC,QAAA,GAAS,MAAMC,mBAAa,CAAA;AAAA,UAChC,MAAA;AAAA,UACA,MAAA;AAAA,UACA,QAAA;AAAA,UACA,SAAA;AAAA,UACA,IAAA;AAAA,UACA,OAAA;AAAA,UACA,iBAAA,EAAmB,MAAO,CAAA,WAAA,CAAY,SAAS,CAAA;AAAA,UAC/C;AAAA,SACD,CAAA;AACD,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,IAAM,EAAA,GAAA;AAAA,UACN,KAAO,EAAA;AAAA,SACR,CAAA;AACD,QAAA,UAAA,CAAW,IAAID,QAAM,CAAA;AAAA;AACvB,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -1,8 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var backendCommon = require('@backstage/backend-common');
4
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
5
- var config = require('@backstage/config');
6
4
 
7
5
  const migrationsDir = backendPluginApi.resolvePackagePath(
8
6
  "@backstage/plugin-auth-backend",
@@ -14,20 +12,6 @@ class AuthDatabase {
14
12
  static create(database) {
15
13
  return new AuthDatabase(database);
16
14
  }
17
- /** @internal */
18
- static forTesting() {
19
- const config$1 = new config.ConfigReader({
20
- backend: {
21
- database: {
22
- client: "better-sqlite3",
23
- connection: ":memory:",
24
- useNullAsDefault: true
25
- }
26
- }
27
- });
28
- const database = backendCommon.DatabaseManager.fromConfig(config$1).forPlugin("auth");
29
- return new AuthDatabase(database);
30
- }
31
15
  static async runMigrations(knex) {
32
16
  await knex.migrate.latest({
33
17
  directory: migrationsDir
@@ -1 +1 @@
1
- {"version":3,"file":"AuthDatabase.cjs.js","sources":["../../src/database/AuthDatabase.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 { DatabaseManager } from '@backstage/backend-common';\nimport {\n DatabaseService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\nimport { ConfigReader } from '@backstage/config';\nimport { Knex } from 'knex';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage/plugin-auth-backend',\n 'migrations',\n);\n\n/**\n * Ensures that a database connection is established exactly once and only when\n * asked for, and runs migrations.\n */\nexport class AuthDatabase {\n readonly #database: DatabaseService;\n #promise: Promise<Knex> | undefined;\n\n static create(database: DatabaseService): AuthDatabase {\n return new AuthDatabase(database);\n }\n\n /** @internal */\n static forTesting(): AuthDatabase {\n const config = new ConfigReader({\n backend: {\n database: {\n client: 'better-sqlite3',\n connection: ':memory:',\n useNullAsDefault: true,\n },\n },\n });\n const database = DatabaseManager.fromConfig(config).forPlugin('auth');\n return new AuthDatabase(database);\n }\n\n static async runMigrations(knex: Knex): Promise<void> {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n private constructor(database: DatabaseService) {\n this.#database = database;\n }\n\n get(): Promise<Knex> {\n this.#promise ??= this.#database.getClient().then(async client => {\n if (!this.#database.migrations?.skip) {\n await AuthDatabase.runMigrations(client);\n }\n return client;\n });\n\n return this.#promise;\n }\n}\n"],"names":["resolvePackagePath","config","ConfigReader","DatabaseManager"],"mappings":";;;;;;AAwBA,MAAM,aAAgB,GAAAA,mCAAA;AAAA,EACpB,gCAAA;AAAA,EACA;AACF,CAAA;AAMO,MAAM,YAAa,CAAA;AAAA,EACf,SAAA;AAAA,EACT,QAAA;AAAA,EAEA,OAAO,OAAO,QAAyC,EAAA;AACrD,IAAO,OAAA,IAAI,aAAa,QAAQ,CAAA;AAAA;AAClC;AAAA,EAGA,OAAO,UAA2B,GAAA;AAChC,IAAM,MAAAC,QAAA,GAAS,IAAIC,mBAAa,CAAA;AAAA,MAC9B,OAAS,EAAA;AAAA,QACP,QAAU,EAAA;AAAA,UACR,MAAQ,EAAA,gBAAA;AAAA,UACR,UAAY,EAAA,UAAA;AAAA,UACZ,gBAAkB,EAAA;AAAA;AACpB;AACF,KACD,CAAA;AACD,IAAA,MAAM,WAAWC,6BAAgB,CAAA,UAAA,CAAWF,QAAM,CAAA,CAAE,UAAU,MAAM,CAAA;AACpE,IAAO,OAAA,IAAI,aAAa,QAAQ,CAAA;AAAA;AAClC,EAEA,aAAa,cAAc,IAA2B,EAAA;AACpD,IAAM,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA;AAAA,MACxB,SAAW,EAAA;AAAA,KACZ,CAAA;AAAA;AACH,EAEQ,YAAY,QAA2B,EAAA;AAC7C,IAAA,IAAA,CAAK,SAAY,GAAA,QAAA;AAAA;AACnB,EAEA,GAAqB,GAAA;AACnB,IAAA,IAAA,CAAK,aAAa,IAAK,CAAA,SAAA,CAAU,WAAY,CAAA,IAAA,CAAK,OAAM,MAAU,KAAA;AAChE,MAAA,IAAI,CAAC,IAAA,CAAK,SAAU,CAAA,UAAA,EAAY,IAAM,EAAA;AACpC,QAAM,MAAA,YAAA,CAAa,cAAc,MAAM,CAAA;AAAA;AAEzC,MAAO,OAAA,MAAA;AAAA,KACR,CAAA;AAED,IAAA,OAAO,IAAK,CAAA,QAAA;AAAA;AAEhB;;;;"}
1
+ {"version":3,"file":"AuthDatabase.cjs.js","sources":["../../src/database/AuthDatabase.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 DatabaseService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\nimport { Knex } from 'knex';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage/plugin-auth-backend',\n 'migrations',\n);\n\n/**\n * Ensures that a database connection is established exactly once and only when\n * asked for, and runs migrations.\n */\nexport class AuthDatabase {\n readonly #database: DatabaseService;\n #promise: Promise<Knex> | undefined;\n\n static create(database: DatabaseService): AuthDatabase {\n return new AuthDatabase(database);\n }\n\n static async runMigrations(knex: Knex): Promise<void> {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n private constructor(database: DatabaseService) {\n this.#database = database;\n }\n\n get(): Promise<Knex> {\n this.#promise ??= this.#database.getClient().then(async client => {\n if (!this.#database.migrations?.skip) {\n await AuthDatabase.runMigrations(client);\n }\n return client;\n });\n\n return this.#promise;\n }\n}\n"],"names":["resolvePackagePath"],"mappings":";;;;AAsBA,MAAM,aAAgB,GAAAA,mCAAA;AAAA,EACpB,gCAAA;AAAA,EACA;AACF,CAAA;AAMO,MAAM,YAAa,CAAA;AAAA,EACf,SAAA;AAAA,EACT,QAAA;AAAA,EAEA,OAAO,OAAO,QAAyC,EAAA;AACrD,IAAO,OAAA,IAAI,aAAa,QAAQ,CAAA;AAAA;AAClC,EAEA,aAAa,cAAc,IAA2B,EAAA;AACpD,IAAM,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA;AAAA,MACxB,SAAW,EAAA;AAAA,KACZ,CAAA;AAAA;AACH,EAEQ,YAAY,QAA2B,EAAA;AAC7C,IAAA,IAAA,CAAK,SAAY,GAAA,QAAA;AAAA;AACnB,EAEA,GAAqB,GAAA;AACnB,IAAA,IAAA,CAAK,aAAa,IAAK,CAAA,SAAA,CAAU,WAAY,CAAA,IAAA,CAAK,OAAM,MAAU,KAAA;AAChE,MAAA,IAAI,CAAC,IAAA,CAAK,SAAU,CAAA,UAAA,EAAY,IAAM,EAAA;AACpC,QAAM,MAAA,YAAA,CAAa,cAAc,MAAM,CAAA;AAAA;AAEzC,MAAO,OAAA,MAAA;AAAA,KACR,CAAA;AAED,IAAA,OAAO,IAAK,CAAA,QAAA;AAAA;AAEhB;;;;"}
@@ -1,40 +1,33 @@
1
1
  'use strict';
2
2
 
3
- var jose = require('jose');
4
- var catalogModel = require('@backstage/catalog-model');
5
- var errors = require('@backstage/errors');
3
+ var issueUserToken = require('./issueUserToken.cjs.js');
6
4
 
7
- const MS_IN_S = 1e3;
8
5
  class StaticTokenIssuer {
9
6
  issuer;
10
7
  logger;
11
8
  keyStore;
12
9
  sessionExpirationSeconds;
10
+ omitClaimsFromToken;
11
+ userInfoDatabaseHandler;
13
12
  constructor(options, keyStore) {
14
13
  this.issuer = options.issuer;
15
14
  this.logger = options.logger;
16
15
  this.sessionExpirationSeconds = options.sessionExpirationSeconds;
17
16
  this.keyStore = keyStore;
17
+ this.omitClaimsFromToken = options.omitClaimsFromToken;
18
+ this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;
18
19
  }
19
20
  async issueToken(params) {
20
21
  const key = await this.getSigningKey();
21
- const iss = this.issuer;
22
- const { sub, ent, ...additionalClaims } = params.claims;
23
- const aud = "backstage";
24
- const iat = Math.floor(Date.now() / MS_IN_S);
25
- const exp = iat + this.sessionExpirationSeconds;
26
- try {
27
- catalogModel.parseEntityRef(sub);
28
- } catch (error) {
29
- throw new Error(
30
- '"sub" claim provided by the auth resolver is not a valid EntityRef.'
31
- );
32
- }
33
- this.logger.info(`Issuing token for ${sub}, with entities ${ent ?? []}`);
34
- if (!key.alg) {
35
- throw new errors.AuthenticationError("No algorithm was provided in the key");
36
- }
37
- return new jose.SignJWT({ ...additionalClaims, iss, sub, ent, aud, iat, exp }).setProtectedHeader({ alg: key.alg, kid: key.kid }).setIssuer(iss).setAudience(aud).setSubject(sub).setIssuedAt(iat).setExpirationTime(exp).sign(await jose.importJWK(key));
22
+ return issueUserToken.issueUserToken({
23
+ issuer: this.issuer,
24
+ key,
25
+ keyDurationSeconds: this.sessionExpirationSeconds,
26
+ logger: this.logger,
27
+ omitClaimsFromToken: this.omitClaimsFromToken,
28
+ params,
29
+ userInfoDatabaseHandler: this.userInfoDatabaseHandler
30
+ });
38
31
  }
39
32
  async getSigningKey() {
40
33
  const { items: keys } = await this.keyStore.listKeys();
@@ -1 +1 @@
1
- {"version":3,"file":"StaticTokenIssuer.cjs.js","sources":["../../src/identity/StaticTokenIssuer.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 { AnyJWK, TokenIssuer } from './types';\nimport { SignJWT, importJWK, JWK } from 'jose';\nimport { parseEntityRef } from '@backstage/catalog-model';\nimport { AuthenticationError } from '@backstage/errors';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { StaticKeyStore } from './StaticKeyStore';\nimport { TokenParams } from '@backstage/plugin-auth-node';\n\nconst MS_IN_S = 1000;\n\nexport type Config = {\n publicKeyFile: string;\n privateKeyFile: string;\n keyId: string;\n algorithm?: string;\n};\n\nexport type Options = {\n logger: LoggerService;\n /** Value of the issuer claim in issued tokens */\n issuer: string;\n /** Expiration time of the JWT in seconds */\n sessionExpirationSeconds: number;\n};\n\n/**\n * A token issuer that issues tokens from predefined\n * public/private key pair stored in the static key store.\n */\nexport class StaticTokenIssuer implements TokenIssuer {\n private readonly issuer: string;\n private readonly logger: LoggerService;\n private readonly keyStore: StaticKeyStore;\n private readonly sessionExpirationSeconds: number;\n\n public constructor(options: Options, keyStore: StaticKeyStore) {\n this.issuer = options.issuer;\n this.logger = options.logger;\n this.sessionExpirationSeconds = options.sessionExpirationSeconds;\n this.keyStore = keyStore;\n }\n\n public async issueToken(params: TokenParams): Promise<string> {\n const key = await this.getSigningKey();\n\n // TODO: code shared with TokenFactory.ts\n const iss = this.issuer;\n const { sub, ent, ...additionalClaims } = params.claims;\n const aud = 'backstage';\n const iat = Math.floor(Date.now() / MS_IN_S);\n const exp = iat + this.sessionExpirationSeconds;\n\n // Validate that the subject claim is a valid EntityRef\n try {\n parseEntityRef(sub);\n } catch (error) {\n throw new Error(\n '\"sub\" claim provided by the auth resolver is not a valid EntityRef.',\n );\n }\n\n this.logger.info(`Issuing token for ${sub}, with entities ${ent ?? []}`);\n\n if (!key.alg) {\n throw new AuthenticationError('No algorithm was provided in the key');\n }\n\n return new SignJWT({ ...additionalClaims, iss, sub, ent, aud, iat, exp })\n .setProtectedHeader({ alg: key.alg, kid: key.kid })\n .setIssuer(iss)\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n }\n\n private async getSigningKey(): Promise<JWK> {\n const { items: keys } = await this.keyStore.listKeys();\n if (keys.length >= 1) {\n return this.keyStore.getPrivateKey(keys[0].key.kid);\n }\n throw new Error('Keystore should hold at least 1 key');\n }\n\n public async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {\n const { items: keys } = await this.keyStore.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n}\n"],"names":["parseEntityRef","AuthenticationError","SignJWT","importJWK"],"mappings":";;;;;;AAwBA,MAAM,OAAU,GAAA,GAAA;AAqBT,MAAM,iBAAyC,CAAA;AAAA,EACnC,MAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,wBAAA;AAAA,EAEV,WAAA,CAAY,SAAkB,QAA0B,EAAA;AAC7D,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,2BAA2B,OAAQ,CAAA,wBAAA;AACxC,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAAA;AAClB,EAEA,MAAa,WAAW,MAAsC,EAAA;AAC5D,IAAM,MAAA,GAAA,GAAM,MAAM,IAAA,CAAK,aAAc,EAAA;AAGrC,IAAA,MAAM,MAAM,IAAK,CAAA,MAAA;AACjB,IAAA,MAAM,EAAE,GAAK,EAAA,GAAA,EAAK,GAAG,gBAAA,KAAqB,MAAO,CAAA,MAAA;AACjD,IAAA,MAAM,GAAM,GAAA,WAAA;AACZ,IAAA,MAAM,MAAM,IAAK,CAAA,KAAA,CAAM,IAAK,CAAA,GAAA,KAAQ,OAAO,CAAA;AAC3C,IAAM,MAAA,GAAA,GAAM,MAAM,IAAK,CAAA,wBAAA;AAGvB,IAAI,IAAA;AACF,MAAAA,2BAAA,CAAe,GAAG,CAAA;AAAA,aACX,KAAO,EAAA;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA;AAGF,IAAK,IAAA,CAAA,MAAA,CAAO,KAAK,CAAqB,kBAAA,EAAA,GAAG,mBAAmB,GAAO,IAAA,EAAE,CAAE,CAAA,CAAA;AAEvE,IAAI,IAAA,CAAC,IAAI,GAAK,EAAA;AACZ,MAAM,MAAA,IAAIC,2BAAoB,sCAAsC,CAAA;AAAA;AAGtE,IAAA,OAAO,IAAIC,YAAQ,CAAA,EAAE,GAAG,gBAAkB,EAAA,GAAA,EAAK,KAAK,GAAK,EAAA,GAAA,EAAK,KAAK,GAAI,EAAC,EACrE,kBAAmB,CAAA,EAAE,KAAK,GAAI,CAAA,GAAA,EAAK,KAAK,GAAI,CAAA,GAAA,EAAK,CAAA,CACjD,UAAU,GAAG,CAAA,CACb,YAAY,GAAG,CAAA,CACf,WAAW,GAAG,CAAA,CACd,YAAY,GAAG,CAAA,CACf,kBAAkB,GAAG,CAAA,CACrB,KAAK,MAAMC,cAAA,CAAU,GAAG,CAAC,CAAA;AAAA;AAC9B,EAEA,MAAc,aAA8B,GAAA;AAC1C,IAAA,MAAM,EAAE,KAAO,EAAA,IAAA,KAAS,MAAM,IAAA,CAAK,SAAS,QAAS,EAAA;AACrD,IAAI,IAAA,IAAA,CAAK,UAAU,CAAG,EAAA;AACpB,MAAA,OAAO,KAAK,QAAS,CAAA,aAAA,CAAc,KAAK,CAAC,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA;AAEpD,IAAM,MAAA,IAAI,MAAM,qCAAqC,CAAA;AAAA;AACvD,EAEA,MAAa,cAA8C,GAAA;AACzD,IAAA,MAAM,EAAE,KAAO,EAAA,IAAA,KAAS,MAAM,IAAA,CAAK,SAAS,QAAS,EAAA;AACrD,IAAO,OAAA,EAAE,MAAM,IAAK,CAAA,GAAA,CAAI,CAAC,EAAE,GAAA,EAAU,KAAA,GAAG,CAAE,EAAA;AAAA;AAE9C;;;;"}
1
+ {"version":3,"file":"StaticTokenIssuer.cjs.js","sources":["../../src/identity/StaticTokenIssuer.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 { AnyJWK, TokenIssuer } from './types';\nimport { JWK } from 'jose';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { StaticKeyStore } from './StaticKeyStore';\nimport {\n BackstageSignInResult,\n TokenParams,\n} from '@backstage/plugin-auth-node';\nimport { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler';\nimport { issueUserToken } from './issueUserToken';\n\nexport type Config = {\n publicKeyFile: string;\n privateKeyFile: string;\n keyId: string;\n algorithm?: string;\n};\n\nexport type Options = {\n logger: LoggerService;\n /** Value of the issuer claim in issued tokens */\n issuer: string;\n /** Expiration time of the JWT in seconds */\n sessionExpirationSeconds: number;\n /**\n * A list of claims to omit from issued tokens and only store in the user info database\n */\n omitClaimsFromToken?: string[];\n userInfoDatabaseHandler: UserInfoDatabaseHandler;\n};\n\n/**\n * A token issuer that issues tokens from predefined\n * public/private key pair stored in the static key store.\n */\nexport class StaticTokenIssuer implements TokenIssuer {\n private readonly issuer: string;\n private readonly logger: LoggerService;\n private readonly keyStore: StaticKeyStore;\n private readonly sessionExpirationSeconds: number;\n private readonly omitClaimsFromToken?: string[];\n private readonly userInfoDatabaseHandler: UserInfoDatabaseHandler;\n\n public constructor(options: Options, keyStore: StaticKeyStore) {\n this.issuer = options.issuer;\n this.logger = options.logger;\n this.sessionExpirationSeconds = options.sessionExpirationSeconds;\n this.keyStore = keyStore;\n this.omitClaimsFromToken = options.omitClaimsFromToken;\n this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;\n }\n\n public async issueToken(params: TokenParams): Promise<BackstageSignInResult> {\n const key = await this.getSigningKey();\n\n return issueUserToken({\n issuer: this.issuer,\n key,\n keyDurationSeconds: this.sessionExpirationSeconds,\n logger: this.logger,\n omitClaimsFromToken: this.omitClaimsFromToken,\n params,\n userInfoDatabaseHandler: this.userInfoDatabaseHandler,\n });\n }\n\n private async getSigningKey(): Promise<JWK> {\n const { items: keys } = await this.keyStore.listKeys();\n if (keys.length >= 1) {\n return this.keyStore.getPrivateKey(keys[0].key.kid);\n }\n throw new Error('Keystore should hold at least 1 key');\n }\n\n public async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {\n const { items: keys } = await this.keyStore.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n}\n"],"names":["issueUserToken"],"mappings":";;;;AAmDO,MAAM,iBAAyC,CAAA;AAAA,EACnC,MAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,wBAAA;AAAA,EACA,mBAAA;AAAA,EACA,uBAAA;AAAA,EAEV,WAAA,CAAY,SAAkB,QAA0B,EAAA;AAC7D,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,2BAA2B,OAAQ,CAAA,wBAAA;AACxC,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAChB,IAAA,IAAA,CAAK,sBAAsB,OAAQ,CAAA,mBAAA;AACnC,IAAA,IAAA,CAAK,0BAA0B,OAAQ,CAAA,uBAAA;AAAA;AACzC,EAEA,MAAa,WAAW,MAAqD,EAAA;AAC3E,IAAM,MAAA,GAAA,GAAM,MAAM,IAAA,CAAK,aAAc,EAAA;AAErC,IAAA,OAAOA,6BAAe,CAAA;AAAA,MACpB,QAAQ,IAAK,CAAA,MAAA;AAAA,MACb,GAAA;AAAA,MACA,oBAAoB,IAAK,CAAA,wBAAA;AAAA,MACzB,QAAQ,IAAK,CAAA,MAAA;AAAA,MACb,qBAAqB,IAAK,CAAA,mBAAA;AAAA,MAC1B,MAAA;AAAA,MACA,yBAAyB,IAAK,CAAA;AAAA,KAC/B,CAAA;AAAA;AACH,EAEA,MAAc,aAA8B,GAAA;AAC1C,IAAA,MAAM,EAAE,KAAO,EAAA,IAAA,KAAS,MAAM,IAAA,CAAK,SAAS,QAAS,EAAA;AACrD,IAAI,IAAA,IAAA,CAAK,UAAU,CAAG,EAAA;AACpB,MAAA,OAAO,KAAK,QAAS,CAAA,aAAA,CAAc,KAAK,CAAC,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA;AAEpD,IAAM,MAAA,IAAI,MAAM,qCAAqC,CAAA;AAAA;AACvD,EAEA,MAAa,cAA8C,GAAA;AACzD,IAAA,MAAM,EAAE,KAAO,EAAA,IAAA,KAAS,MAAM,IAAA,CAAK,SAAS,QAAS,EAAA;AACrD,IAAO,OAAA,EAAE,MAAM,IAAK,CAAA,GAAA,CAAI,CAAC,EAAE,GAAA,EAAU,KAAA,GAAG,CAAE,EAAA;AAAA;AAE9C;;;;"}
@@ -1,21 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var catalogModel = require('@backstage/catalog-model');
4
- var errors = require('@backstage/errors');
5
3
  var jose = require('jose');
6
- var lodash = require('lodash');
7
4
  var luxon = require('luxon');
8
5
  var uuid = require('uuid');
9
- var pluginAuthNode = require('@backstage/plugin-auth-node');
6
+ var issueUserToken = require('./issueUserToken.cjs.js');
10
7
 
11
- const MS_IN_S = 1e3;
12
- const MAX_TOKEN_LENGTH = 32768;
13
8
  class TokenFactory {
14
9
  issuer;
15
10
  logger;
16
11
  keyStore;
17
12
  keyDurationSeconds;
18
13
  algorithm;
14
+ omitClaimsFromToken;
19
15
  userInfoDatabaseHandler;
20
16
  keyExpiry;
21
17
  privateKeyPromise;
@@ -25,62 +21,20 @@ class TokenFactory {
25
21
  this.keyStore = options.keyStore;
26
22
  this.keyDurationSeconds = options.keyDurationSeconds;
27
23
  this.algorithm = options.algorithm ?? "ES256";
24
+ this.omitClaimsFromToken = options.omitClaimsFromToken;
28
25
  this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;
29
26
  }
30
27
  async issueToken(params) {
31
28
  const key = await this.getKey();
32
- const iss = this.issuer;
33
- const { sub, ent = [sub], ...additionalClaims } = params.claims;
34
- const aud = pluginAuthNode.tokenTypes.user.audClaim;
35
- const iat = Math.floor(Date.now() / MS_IN_S);
36
- const exp = iat + this.keyDurationSeconds;
37
- try {
38
- catalogModel.parseEntityRef(sub);
39
- } catch (error) {
40
- throw new Error(
41
- '"sub" claim provided by the auth resolver is not a valid EntityRef.'
42
- );
43
- }
44
- if (!key.alg) {
45
- throw new errors.AuthenticationError("No algorithm was provided in the key");
46
- }
47
- this.logger.info(`Issuing token for ${sub}, with entities ${ent}`);
48
- const signingKey = await jose.importJWK(key);
49
- const uip = await this.createUserIdentityClaim({
50
- header: {
51
- typ: pluginAuthNode.tokenTypes.limitedUser.typParam,
52
- alg: key.alg,
53
- kid: key.kid
54
- },
55
- payload: { sub, iat, exp },
56
- key: signingKey
29
+ return issueUserToken.issueUserToken({
30
+ issuer: this.issuer,
31
+ key,
32
+ keyDurationSeconds: this.keyDurationSeconds,
33
+ logger: this.logger,
34
+ omitClaimsFromToken: this.omitClaimsFromToken,
35
+ params,
36
+ userInfoDatabaseHandler: this.userInfoDatabaseHandler
57
37
  });
58
- const claims = {
59
- ...additionalClaims,
60
- iss,
61
- sub,
62
- ent,
63
- aud,
64
- iat,
65
- exp,
66
- uip
67
- };
68
- const token = await new jose.SignJWT(claims).setProtectedHeader({
69
- typ: pluginAuthNode.tokenTypes.user.typParam,
70
- alg: key.alg,
71
- kid: key.kid
72
- }).sign(signingKey);
73
- if (token.length > MAX_TOKEN_LENGTH) {
74
- throw new Error(
75
- `Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims. You likely have a bug either in the sign-in resolver or catalog data. The following claims were requested: '${JSON.stringify(
76
- claims
77
- )}'`
78
- );
79
- }
80
- await this.userInfoDatabaseHandler.addUserInfo({
81
- claims: lodash.omit(claims, ["aud", "iat", "iss", "uip"])
82
- });
83
- return token;
84
38
  }
85
39
  // This will be called by other services that want to verify ID tokens.
86
40
  // It is important that it returns a list of all public keys that could
@@ -139,25 +93,6 @@ class TokenFactory {
139
93
  }
140
94
  return promise;
141
95
  }
142
- // Creates a string claim that can be used as part of reconstructing a limited
143
- // user token. The output of this function is only the signature part of a
144
- // JWS.
145
- async createUserIdentityClaim(options) {
146
- const header = {
147
- typ: options.header.typ,
148
- alg: options.header.alg,
149
- ...options.header.kid ? { kid: options.header.kid } : {}
150
- };
151
- const payload = {
152
- sub: options.payload.sub,
153
- iat: options.payload.iat,
154
- exp: options.payload.exp
155
- };
156
- const jws = await new jose.GeneralSign(
157
- new TextEncoder().encode(JSON.stringify(payload))
158
- ).addSignature(options.key).setProtectedHeader(header).done().sign();
159
- return jws.signatures[0].signature;
160
- }
161
96
  }
162
97
 
163
98
  exports.TokenFactory = TokenFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"TokenFactory.cjs.js","sources":["../../src/identity/TokenFactory.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 { parseEntityRef } from '@backstage/catalog-model';\nimport { AuthenticationError } from '@backstage/errors';\nimport {\n exportJWK,\n generateKeyPair,\n importJWK,\n JWK,\n SignJWT,\n GeneralSign,\n KeyLike,\n} from 'jose';\nimport { omit } from 'lodash';\nimport { DateTime } from 'luxon';\nimport { v4 as uuid } from 'uuid';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { TokenParams, tokenTypes } from '@backstage/plugin-auth-node';\nimport { AnyJWK, KeyStore, TokenIssuer } from './types';\nimport { JsonValue } from '@backstage/types';\nimport { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler';\n\nconst MS_IN_S = 1000;\nconst MAX_TOKEN_LENGTH = 32768; // At 64 bytes per entity ref this still leaves room for about 500 entities\n\n/**\n * The payload contents of a valid Backstage JWT token\n */\nexport interface BackstageTokenPayload {\n /**\n * The issuer of the token, currently the discovery URL of the auth backend\n */\n iss: string;\n\n /**\n * The entity ref of the user\n */\n sub: string;\n\n /**\n * The entity refs that the user claims ownership througg\n */\n ent: string[];\n\n /**\n * A hard coded audience string\n */\n aud: typeof tokenTypes.user.audClaim;\n\n /**\n * Standard expiry in epoch seconds\n */\n exp: number;\n\n /**\n * Standard issue time in epoch seconds\n */\n iat: number;\n\n /**\n * A separate user identity proof that the auth service can convert to a limited user token\n */\n uip: string;\n\n /**\n * Any other custom claims that the adopter may have added\n */\n [claim: string]: JsonValue;\n}\n\n/**\n * The payload contents of a valid Backstage user identity claim token\n *\n * @internal\n */\ninterface BackstageUserIdentityProofPayload {\n /**\n * The entity ref of the user\n */\n sub: string;\n\n /**\n * Standard expiry in epoch seconds\n */\n exp: number;\n\n /**\n * Standard issue time in epoch seconds\n */\n iat: number;\n}\n\ntype Options = {\n logger: LoggerService;\n /** Value of the issuer claim in issued tokens */\n issuer: string;\n /** Key store used for storing signing keys */\n keyStore: KeyStore;\n /** Expiration time of signing keys in seconds */\n keyDurationSeconds: number;\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 algorithm?: string;\n userInfoDatabaseHandler: UserInfoDatabaseHandler;\n};\n\n/**\n * A token issuer that is able to issue tokens in a distributed system\n * backed by a single database. Tokens are issued using lazily generated\n * signing keys, where each running instance of the auth service uses its own\n * signing key.\n *\n * The public parts of the keys are all stored in the shared key storage,\n * and any of the instances of the auth service will return the full list\n * of public keys that are currently in storage.\n *\n * Signing keys are automatically rotated at the same interval as the token\n * duration. Expired keys are kept in storage until there are no valid tokens\n * in circulation that could have been signed by that key.\n */\nexport class TokenFactory implements TokenIssuer {\n private readonly issuer: string;\n private readonly logger: LoggerService;\n private readonly keyStore: KeyStore;\n private readonly keyDurationSeconds: number;\n private readonly algorithm: string;\n private readonly userInfoDatabaseHandler: UserInfoDatabaseHandler;\n\n private keyExpiry?: Date;\n private privateKeyPromise?: Promise<JWK>;\n\n constructor(options: Options) {\n this.issuer = options.issuer;\n this.logger = options.logger;\n this.keyStore = options.keyStore;\n this.keyDurationSeconds = options.keyDurationSeconds;\n this.algorithm = options.algorithm ?? 'ES256';\n this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;\n }\n\n async issueToken(params: TokenParams): Promise<string> {\n const key = await this.getKey();\n\n const iss = this.issuer;\n const { sub, ent = [sub], ...additionalClaims } = params.claims;\n const aud = tokenTypes.user.audClaim;\n const iat = Math.floor(Date.now() / MS_IN_S);\n const exp = iat + this.keyDurationSeconds;\n\n try {\n // The subject must be a valid entity ref\n parseEntityRef(sub);\n } catch (error) {\n throw new Error(\n '\"sub\" claim provided by the auth resolver is not a valid EntityRef.',\n );\n }\n\n if (!key.alg) {\n throw new AuthenticationError('No algorithm was provided in the key');\n }\n\n this.logger.info(`Issuing token for ${sub}, with entities ${ent}`);\n\n const signingKey = await importJWK(key);\n\n const uip = await this.createUserIdentityClaim({\n header: {\n typ: tokenTypes.limitedUser.typParam,\n alg: key.alg,\n kid: key.kid,\n },\n payload: { sub, iat, exp },\n key: signingKey,\n });\n\n const claims: BackstageTokenPayload = {\n ...additionalClaims,\n iss,\n sub,\n ent,\n aud,\n iat,\n exp,\n uip,\n };\n\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.user.typParam,\n alg: key.alg,\n kid: key.kid,\n })\n .sign(signingKey);\n\n if (token.length > MAX_TOKEN_LENGTH) {\n throw new Error(\n `Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims. You likely have a bug either in the sign-in resolver or catalog data. The following claims were requested: '${JSON.stringify(\n claims,\n )}'`,\n );\n }\n\n // Store the user info in the database upon successful token\n // issuance so that it can be retrieved later by limited user tokens\n await this.userInfoDatabaseHandler.addUserInfo({\n claims: omit(claims, ['aud', 'iat', 'iss', 'uip']),\n });\n\n return token;\n }\n\n // This will be called by other services that want to verify ID tokens.\n // It is important that it returns a list of all public keys that could\n // have been used to sign tokens that have not yet expired.\n async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {\n const { items: keys } = await this.keyStore.listKeys();\n\n const validKeys = [];\n const expiredKeys = [];\n\n for (const key of keys) {\n // Allow for a grace period of another full key duration before we remove the keys from the database\n const expireAt = DateTime.fromJSDate(key.createdAt).plus({\n seconds: 3 * this.keyDurationSeconds,\n });\n if (expireAt < DateTime.local()) {\n expiredKeys.push(key);\n } else {\n validKeys.push(key);\n }\n }\n\n // Lazily prune expired keys. This may cause duplicate removals if we have concurrent callers, but w/e\n if (expiredKeys.length > 0) {\n const kids = expiredKeys.map(({ key }) => key.kid);\n\n this.logger.info(`Removing expired signing keys, '${kids.join(\"', '\")}'`);\n\n // We don't await this, just let it run in the background\n this.keyStore.removeKeys(kids).catch(error => {\n this.logger.error(`Failed to remove expired keys, ${error}`);\n });\n }\n\n // NOTE: we're currently only storing public keys, but if we start storing private keys we'd have to convert here\n return { keys: validKeys.map(({ key }) => key) };\n }\n\n private async getKey(): Promise<JWK> {\n // Make sure that we only generate one key at a time\n if (this.privateKeyPromise) {\n if (\n this.keyExpiry &&\n DateTime.fromJSDate(this.keyExpiry) > DateTime.local()\n ) {\n return this.privateKeyPromise;\n }\n this.logger.info(`Signing key has expired, generating new key`);\n delete this.privateKeyPromise;\n }\n\n this.keyExpiry = DateTime.utc()\n .plus({\n seconds: this.keyDurationSeconds,\n })\n .toJSDate();\n const promise = (async () => {\n // This generates a new signing key to be used to sign tokens until the next key rotation\n const key = await generateKeyPair(this.algorithm);\n const publicKey = await exportJWK(key.publicKey);\n const privateKey = await exportJWK(key.privateKey);\n publicKey.kid = privateKey.kid = uuid();\n publicKey.alg = privateKey.alg = this.algorithm;\n\n // We're not allowed to use the key until it has been successfully stored\n // TODO: some token verification implementations aggressively cache the list of keys, and\n // don't attempt to fetch new ones even if they encounter an unknown kid. Therefore we\n // may want to keep using the existing key for some period of time until we switch to\n // the new one. This also needs to be implemented cross-service though, meaning new services\n // that boot up need to be able to grab an existing key to use for signing.\n this.logger.info(`Created new signing key ${publicKey.kid}`);\n await this.keyStore.addKey(publicKey as AnyJWK);\n\n // At this point we are allowed to start using the new key\n return privateKey;\n })();\n\n this.privateKeyPromise = promise;\n\n try {\n // If we fail to generate a new key, we need to clear the state so that\n // the next caller will try to generate another key.\n await promise;\n } catch (error) {\n this.logger.error(`Failed to generate new signing key, ${error}`);\n delete this.keyExpiry;\n delete this.privateKeyPromise;\n }\n\n return promise;\n }\n\n // Creates a string claim that can be used as part of reconstructing a limited\n // user token. The output of this function is only the signature part of a\n // JWS.\n private async createUserIdentityClaim(options: {\n header: {\n typ: string;\n alg: string;\n kid?: string;\n };\n payload: BackstageUserIdentityProofPayload;\n key: KeyLike | Uint8Array;\n }): Promise<string> {\n // NOTE: We reconstruct the header and payload structures carefully to\n // perfectly guarantee ordering. The reason for this is that we store only\n // the signature part of these to reduce duplication within the Backstage\n // token. Anyone who wants to make an actual JWT based on all this must be\n // able to do the EXACT reconstruction of the header and payload parts, to\n // then append the signature.\n\n const header = {\n typ: options.header.typ,\n alg: options.header.alg,\n ...(options.header.kid ? { kid: options.header.kid } : {}),\n };\n\n const payload = {\n sub: options.payload.sub,\n iat: options.payload.iat,\n exp: options.payload.exp,\n };\n\n const jws = await new GeneralSign(\n new TextEncoder().encode(JSON.stringify(payload)),\n )\n .addSignature(options.key)\n .setProtectedHeader(header)\n .done()\n .sign();\n\n return jws.signatures[0].signature;\n }\n}\n"],"names":["tokenTypes","parseEntityRef","AuthenticationError","importJWK","SignJWT","omit","DateTime","generateKeyPair","exportJWK","uuid","GeneralSign"],"mappings":"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"}
1
+ {"version":3,"file":"TokenFactory.cjs.js","sources":["../../src/identity/TokenFactory.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 { exportJWK, generateKeyPair, JWK } from 'jose';\nimport { DateTime } from 'luxon';\nimport { v4 as uuid } from 'uuid';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n BackstageSignInResult,\n TokenParams,\n tokenTypes,\n} from '@backstage/plugin-auth-node';\nimport { AnyJWK, KeyStore, TokenIssuer } from './types';\nimport { JsonValue } from '@backstage/types';\nimport { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler';\nimport { issueUserToken } from './issueUserToken';\n\n/**\n * The payload contents of a valid Backstage JWT token\n */\nexport interface BackstageTokenPayload {\n /**\n * The issuer of the token, currently the discovery URL of the auth backend\n */\n iss: string;\n\n /**\n * The entity ref of the user\n */\n sub: string;\n\n /**\n * The entity refs that the user claims ownership through\n */\n ent: string[];\n\n /**\n * A hard coded audience string\n */\n aud: typeof tokenTypes.user.audClaim;\n\n /**\n * Standard expiry in epoch seconds\n */\n exp: number;\n\n /**\n * Standard issue time in epoch seconds\n */\n iat: number;\n\n /**\n * A separate user identity proof that the auth service can convert to a limited user token\n */\n uip: string;\n\n /**\n * Any other custom claims that the adopter may have added\n */\n [claim: string]: JsonValue;\n}\n\ntype Options = {\n logger: LoggerService;\n /** Value of the issuer claim in issued tokens */\n issuer: string;\n /** Key store used for storing signing keys */\n keyStore: KeyStore;\n /** Expiration time of signing keys in seconds */\n keyDurationSeconds: number;\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 algorithm?: string;\n /**\n * A list of claims to omit from issued tokens and only store in the user info database\n */\n omitClaimsFromToken?: string[];\n userInfoDatabaseHandler: UserInfoDatabaseHandler;\n};\n\n/**\n * A token issuer that is able to issue tokens in a distributed system\n * backed by a single database. Tokens are issued using lazily generated\n * signing keys, where each running instance of the auth service uses its own\n * signing key.\n *\n * The public parts of the keys are all stored in the shared key storage,\n * and any of the instances of the auth service will return the full list\n * of public keys that are currently in storage.\n *\n * Signing keys are automatically rotated at the same interval as the token\n * duration. Expired keys are kept in storage until there are no valid tokens\n * in circulation that could have been signed by that key.\n */\nexport class TokenFactory implements TokenIssuer {\n private readonly issuer: string;\n private readonly logger: LoggerService;\n private readonly keyStore: KeyStore;\n private readonly keyDurationSeconds: number;\n private readonly algorithm: string;\n private readonly omitClaimsFromToken?: string[];\n private readonly userInfoDatabaseHandler: UserInfoDatabaseHandler;\n\n private keyExpiry?: Date;\n private privateKeyPromise?: Promise<JWK>;\n\n constructor(options: Options) {\n this.issuer = options.issuer;\n this.logger = options.logger;\n this.keyStore = options.keyStore;\n this.keyDurationSeconds = options.keyDurationSeconds;\n this.algorithm = options.algorithm ?? 'ES256';\n this.omitClaimsFromToken = options.omitClaimsFromToken;\n this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;\n }\n\n async issueToken(params: TokenParams): Promise<BackstageSignInResult> {\n const key = await this.getKey();\n\n return issueUserToken({\n issuer: this.issuer,\n key,\n keyDurationSeconds: this.keyDurationSeconds,\n logger: this.logger,\n omitClaimsFromToken: this.omitClaimsFromToken,\n params,\n userInfoDatabaseHandler: this.userInfoDatabaseHandler,\n });\n }\n\n // This will be called by other services that want to verify ID tokens.\n // It is important that it returns a list of all public keys that could\n // have been used to sign tokens that have not yet expired.\n async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {\n const { items: keys } = await this.keyStore.listKeys();\n\n const validKeys = [];\n const expiredKeys = [];\n\n for (const key of keys) {\n // Allow for a grace period of another full key duration before we remove the keys from the database\n const expireAt = DateTime.fromJSDate(key.createdAt).plus({\n seconds: 3 * this.keyDurationSeconds,\n });\n if (expireAt < DateTime.local()) {\n expiredKeys.push(key);\n } else {\n validKeys.push(key);\n }\n }\n\n // Lazily prune expired keys. This may cause duplicate removals if we have concurrent callers, but w/e\n if (expiredKeys.length > 0) {\n const kids = expiredKeys.map(({ key }) => key.kid);\n\n this.logger.info(`Removing expired signing keys, '${kids.join(\"', '\")}'`);\n\n // We don't await this, just let it run in the background\n this.keyStore.removeKeys(kids).catch(error => {\n this.logger.error(`Failed to remove expired keys, ${error}`);\n });\n }\n\n // NOTE: we're currently only storing public keys, but if we start storing private keys we'd have to convert here\n return { keys: validKeys.map(({ key }) => key) };\n }\n\n private async getKey(): Promise<JWK> {\n // Make sure that we only generate one key at a time\n if (this.privateKeyPromise) {\n if (\n this.keyExpiry &&\n DateTime.fromJSDate(this.keyExpiry) > DateTime.local()\n ) {\n return this.privateKeyPromise;\n }\n this.logger.info(`Signing key has expired, generating new key`);\n delete this.privateKeyPromise;\n }\n\n this.keyExpiry = DateTime.utc()\n .plus({\n seconds: this.keyDurationSeconds,\n })\n .toJSDate();\n const promise = (async () => {\n // This generates a new signing key to be used to sign tokens until the next key rotation\n const key = await generateKeyPair(this.algorithm);\n const publicKey = await exportJWK(key.publicKey);\n const privateKey = await exportJWK(key.privateKey);\n publicKey.kid = privateKey.kid = uuid();\n publicKey.alg = privateKey.alg = this.algorithm;\n\n // We're not allowed to use the key until it has been successfully stored\n // TODO: some token verification implementations aggressively cache the list of keys, and\n // don't attempt to fetch new ones even if they encounter an unknown kid. Therefore we\n // may want to keep using the existing key for some period of time until we switch to\n // the new one. This also needs to be implemented cross-service though, meaning new services\n // that boot up need to be able to grab an existing key to use for signing.\n this.logger.info(`Created new signing key ${publicKey.kid}`);\n await this.keyStore.addKey(publicKey as AnyJWK);\n\n // At this point we are allowed to start using the new key\n return privateKey;\n })();\n\n this.privateKeyPromise = promise;\n\n try {\n // If we fail to generate a new key, we need to clear the state so that\n // the next caller will try to generate another key.\n await promise;\n } catch (error) {\n this.logger.error(`Failed to generate new signing key, ${error}`);\n delete this.keyExpiry;\n delete this.privateKeyPromise;\n }\n\n return promise;\n }\n}\n"],"names":["issueUserToken","DateTime","generateKeyPair","exportJWK","uuid"],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ var catalogModel = require('@backstage/catalog-model');
4
+ var errors = require('@backstage/errors');
5
+ var pluginAuthNode = require('@backstage/plugin-auth-node');
6
+ var lodash = require('lodash');
7
+ var jose = require('jose');
8
+
9
+ const MS_IN_S = 1e3;
10
+ const MAX_TOKEN_LENGTH = 32768;
11
+ async function issueUserToken({
12
+ issuer,
13
+ key,
14
+ keyDurationSeconds,
15
+ logger,
16
+ omitClaimsFromToken,
17
+ params,
18
+ userInfoDatabaseHandler
19
+ }) {
20
+ const { sub, ent = [sub], ...additionalClaims } = params.claims;
21
+ const aud = pluginAuthNode.tokenTypes.user.audClaim;
22
+ const iat = Math.floor(Date.now() / MS_IN_S);
23
+ const exp = iat + keyDurationSeconds;
24
+ try {
25
+ catalogModel.parseEntityRef(sub);
26
+ } catch (error) {
27
+ throw new Error(
28
+ '"sub" claim provided by the auth resolver is not a valid EntityRef.'
29
+ );
30
+ }
31
+ if (!key.alg) {
32
+ throw new errors.AuthenticationError("No algorithm was provided in the key");
33
+ }
34
+ logger.info(`Issuing token for ${sub}, with entities ${ent}`);
35
+ const signingKey = await jose.importJWK(key);
36
+ const uip = await createUserIdentityClaim({
37
+ header: {
38
+ typ: pluginAuthNode.tokenTypes.limitedUser.typParam,
39
+ alg: key.alg,
40
+ kid: key.kid
41
+ },
42
+ payload: { sub, iat, exp },
43
+ key: signingKey
44
+ });
45
+ const claims = {
46
+ ...additionalClaims,
47
+ iss: issuer,
48
+ sub,
49
+ ent,
50
+ aud,
51
+ iat,
52
+ exp,
53
+ uip
54
+ };
55
+ const tokenClaims = omitClaimsFromToken ? lodash.omit(claims, omitClaimsFromToken) : claims;
56
+ const token = await new jose.SignJWT(tokenClaims).setProtectedHeader({
57
+ typ: pluginAuthNode.tokenTypes.user.typParam,
58
+ alg: key.alg,
59
+ kid: key.kid
60
+ }).sign(signingKey);
61
+ if (token.length > MAX_TOKEN_LENGTH) {
62
+ throw new Error(
63
+ `Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims. You likely have a bug either in the sign-in resolver or catalog data. The following claims were requested: '${JSON.stringify(
64
+ tokenClaims
65
+ )}'`
66
+ );
67
+ }
68
+ await userInfoDatabaseHandler.addUserInfo({
69
+ claims: lodash.omit(claims, ["aud", "iat", "iss", "uip"])
70
+ });
71
+ return {
72
+ token,
73
+ identity: {
74
+ type: "user",
75
+ userEntityRef: sub,
76
+ ownershipEntityRefs: ent
77
+ }
78
+ };
79
+ }
80
+ async function createUserIdentityClaim(options) {
81
+ const header = {
82
+ typ: options.header.typ,
83
+ alg: options.header.alg,
84
+ ...options.header.kid ? { kid: options.header.kid } : {}
85
+ };
86
+ const payload = {
87
+ sub: options.payload.sub,
88
+ iat: options.payload.iat,
89
+ exp: options.payload.exp
90
+ };
91
+ const jws = await new jose.GeneralSign(
92
+ new TextEncoder().encode(JSON.stringify(payload))
93
+ ).addSignature(options.key).setProtectedHeader(header).done().sign();
94
+ return jws.signatures[0].signature;
95
+ }
96
+
97
+ exports.issueUserToken = issueUserToken;
98
+ //# sourceMappingURL=issueUserToken.cjs.js.map