@backstage/plugin-auth-backend 0.23.1-next.0 → 0.23.1-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 (96) hide show
  1. package/CHANGELOG.md +31 -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 +29 -29
package/dist/index.cjs.js CHANGED
@@ -2,1985 +2,35 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var backendPluginApi = require('@backstage/backend-plugin-api');
6
- var pluginAuthNode = require('@backstage/plugin-auth-node');
7
- var alpha = require('@backstage/plugin-catalog-node/alpha');
8
- var express = require('express');
9
- var Router = require('express-promise-router');
10
- var cookieParser = require('cookie-parser');
11
- var pluginAuthBackendModuleAtlassianProvider = require('@backstage/plugin-auth-backend-module-atlassian-provider');
12
- var pluginAuthBackendModuleAuth0Provider = require('@backstage/plugin-auth-backend-module-auth0-provider');
13
- var pluginAuthBackendModuleAwsAlbProvider = require('@backstage/plugin-auth-backend-module-aws-alb-provider');
14
- var pluginAuthBackendModuleBitbucketProvider = require('@backstage/plugin-auth-backend-module-bitbucket-provider');
15
- var pluginAuthBackendModuleCloudflareAccessProvider = require('@backstage/plugin-auth-backend-module-cloudflare-access-provider');
16
- var pluginAuthBackendModuleGcpIapProvider = require('@backstage/plugin-auth-backend-module-gcp-iap-provider');
17
- var pluginAuthBackendModuleGithubProvider = require('@backstage/plugin-auth-backend-module-github-provider');
18
- var pluginAuthBackendModuleGitlabProvider = require('@backstage/plugin-auth-backend-module-gitlab-provider');
19
- var pluginAuthBackendModuleGoogleProvider = require('@backstage/plugin-auth-backend-module-google-provider');
20
- var pluginAuthBackendModuleMicrosoftProvider = require('@backstage/plugin-auth-backend-module-microsoft-provider');
21
- var pluginAuthBackendModuleOauth2Provider = require('@backstage/plugin-auth-backend-module-oauth2-provider');
22
- var pluginAuthBackendModuleOauth2ProxyProvider = require('@backstage/plugin-auth-backend-module-oauth2-proxy-provider');
23
- var pluginAuthBackendModuleOidcProvider = require('@backstage/plugin-auth-backend-module-oidc-provider');
24
- var pluginAuthBackendModuleOktaProvider = require('@backstage/plugin-auth-backend-module-okta-provider');
25
- var pluginAuthBackendModuleOneloginProvider = require('@backstage/plugin-auth-backend-module-onelogin-provider');
26
- var passportSaml = require('@node-saml/passport-saml');
27
- var jose = require('jose');
28
- var crypto = require('crypto');
29
- var errors = require('@backstage/errors');
30
- var pluginAuthBackendModuleBitbucketServerProvider = require('@backstage/plugin-auth-backend-module-bitbucket-server-provider');
31
- var pluginAuthBackendModuleAzureEasyauthProvider = require('@backstage/plugin-auth-backend-module-azure-easyauth-provider');
32
- var catalogClient = require('@backstage/catalog-client');
33
- var minimatch = require('minimatch');
34
- var catalogModel = require('@backstage/catalog-model');
35
- var backendCommon = require('@backstage/backend-common');
36
- var lodash = require('lodash');
37
- var luxon = require('luxon');
38
- var uuid = require('uuid');
39
- var firestore = require('@google-cloud/firestore');
40
- var fs = require('fs');
41
- var session = require('express-session');
42
- var connectSessionKnex = require('connect-session-knex');
43
- var passport = require('passport');
44
- var config = require('@backstage/config');
45
- var types = require('@backstage/types');
46
- var url = require('url');
47
-
48
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
49
-
50
- var express__default = /*#__PURE__*/_interopDefaultCompat(express);
51
- var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
52
- var cookieParser__default = /*#__PURE__*/_interopDefaultCompat(cookieParser);
53
- var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
54
- var session__default = /*#__PURE__*/_interopDefaultCompat(session);
55
- var connectSessionKnex__default = /*#__PURE__*/_interopDefaultCompat(connectSessionKnex);
56
- var passport__default = /*#__PURE__*/_interopDefaultCompat(passport);
57
-
58
- function adaptLegacyOAuthHandler(authHandler) {
59
- return authHandler && (async (result, ctx) => authHandler(
60
- {
61
- fullProfile: result.fullProfile,
62
- accessToken: result.session.accessToken,
63
- params: {
64
- scope: result.session.scope,
65
- id_token: result.session.idToken,
66
- token_type: result.session.tokenType,
67
- expires_in: result.session.expiresInSeconds
68
- }
69
- },
70
- ctx
71
- ));
72
- }
73
-
74
- function adaptLegacyOAuthSignInResolver(signInResolver) {
75
- return signInResolver && (async (input, ctx) => signInResolver(
76
- {
77
- profile: input.profile,
78
- result: {
79
- fullProfile: input.result.fullProfile,
80
- accessToken: input.result.session.accessToken,
81
- refreshToken: input.result.session.refreshToken,
82
- params: {
83
- scope: input.result.session.scope,
84
- id_token: input.result.session.idToken,
85
- token_type: input.result.session.tokenType,
86
- expires_in: input.result.session.expiresInSeconds
87
- }
88
- }
89
- },
90
- ctx
91
- ));
92
- }
93
-
94
- function adaptOAuthSignInResolverToLegacy(resolvers) {
95
- const legacyResolvers = {};
96
- for (const name of Object.keys(resolvers)) {
97
- const resolver = resolvers[name];
98
- legacyResolvers[name] = () => async (input, ctx) => resolver(
99
- {
100
- profile: input.profile,
101
- result: {
102
- fullProfile: input.result.fullProfile,
103
- session: {
104
- accessToken: input.result.accessToken,
105
- expiresInSeconds: input.result.params.expires_in,
106
- scope: input.result.params.scope,
107
- idToken: input.result.params.id_token,
108
- tokenType: input.result.params.token_type ?? "bearer",
109
- refreshToken: input.result.refreshToken
110
- }
111
- }
112
- },
113
- ctx
114
- );
115
- }
116
- return legacyResolvers;
117
- }
118
-
119
- function createAuthProviderIntegration(config) {
120
- return Object.freeze({
121
- ...config,
122
- resolvers: Object.freeze(config.resolvers ?? {})
123
- });
124
- }
125
-
126
- const atlassian = createAuthProviderIntegration({
127
- create(options) {
128
- return pluginAuthNode.createOAuthProviderFactory({
129
- authenticator: pluginAuthBackendModuleAtlassianProvider.atlassianAuthenticator,
130
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
131
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
132
- });
133
- }
134
- });
135
-
136
- const auth0 = createAuthProviderIntegration({
137
- create(options) {
138
- return pluginAuthNode.createOAuthProviderFactory({
139
- authenticator: pluginAuthBackendModuleAuth0Provider.auth0Authenticator,
140
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
141
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
142
- });
143
- }
144
- });
145
-
146
- const awsAlb = createAuthProviderIntegration({
147
- create(options) {
148
- return pluginAuthNode.createProxyAuthProviderFactory({
149
- authenticator: pluginAuthBackendModuleAwsAlbProvider.awsAlbAuthenticator,
150
- profileTransform: options?.authHandler,
151
- signInResolver: options?.signIn?.resolver
152
- });
153
- }
154
- });
155
-
156
- const bitbucket = createAuthProviderIntegration({
157
- create(options) {
158
- return pluginAuthNode.createOAuthProviderFactory({
159
- authenticator: pluginAuthBackendModuleBitbucketProvider.bitbucketAuthenticator,
160
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
161
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
162
- });
163
- },
164
- resolvers: adaptOAuthSignInResolverToLegacy({
165
- userIdMatchingUserEntityAnnotation: pluginAuthBackendModuleBitbucketProvider.bitbucketSignInResolvers.userIdMatchingUserEntityAnnotation(),
166
- usernameMatchingUserEntityAnnotation: pluginAuthBackendModuleBitbucketProvider.bitbucketSignInResolvers.usernameMatchingUserEntityAnnotation()
167
- })
168
- });
169
-
170
- const cfAccess = createAuthProviderIntegration({
171
- create(options) {
172
- return pluginAuthNode.createProxyAuthProviderFactory({
173
- authenticator: pluginAuthBackendModuleCloudflareAccessProvider.createCloudflareAccessAuthenticator({
174
- cache: options.cache
175
- }),
176
- profileTransform: options?.authHandler,
177
- signInResolver: options?.signIn?.resolver,
178
- signInResolverFactories: pluginAuthBackendModuleCloudflareAccessProvider.cloudflareAccessSignInResolvers
179
- });
180
- },
181
- resolvers: pluginAuthBackendModuleCloudflareAccessProvider.cloudflareAccessSignInResolvers
182
- });
183
-
184
- const gcpIap = createAuthProviderIntegration({
185
- create(options) {
186
- return pluginAuthNode.createProxyAuthProviderFactory({
187
- authenticator: pluginAuthBackendModuleGcpIapProvider.gcpIapAuthenticator,
188
- profileTransform: options?.authHandler,
189
- signInResolver: options?.signIn?.resolver
190
- });
191
- }
192
- });
193
-
194
- const github = createAuthProviderIntegration({
195
- create(options) {
196
- const authHandler = options?.authHandler;
197
- const signInResolver = options?.signIn?.resolver;
198
- return pluginAuthNode.createOAuthProviderFactory({
199
- authenticator: pluginAuthBackendModuleGithubProvider.githubAuthenticator,
200
- profileTransform: authHandler && (async (result, ctx) => authHandler(
201
- {
202
- fullProfile: result.fullProfile,
203
- accessToken: result.session.accessToken,
204
- params: {
205
- scope: result.session.scope,
206
- expires_in: result.session.expiresInSeconds ? String(result.session.expiresInSeconds) : "",
207
- refresh_token_expires_in: result.session.refreshTokenExpiresInSeconds ? String(result.session.refreshTokenExpiresInSeconds) : ""
208
- }
209
- },
210
- ctx
211
- )),
212
- signInResolver: signInResolver && (async ({ profile, result }, ctx) => signInResolver(
213
- {
214
- profile,
215
- result: {
216
- fullProfile: result.fullProfile,
217
- accessToken: result.session.accessToken,
218
- refreshToken: result.session.refreshToken,
219
- params: {
220
- scope: result.session.scope,
221
- expires_in: result.session.expiresInSeconds ? String(result.session.expiresInSeconds) : "",
222
- refresh_token_expires_in: result.session.refreshTokenExpiresInSeconds ? String(result.session.refreshTokenExpiresInSeconds) : ""
223
- }
224
- }
225
- },
226
- ctx
227
- ))
228
- });
229
- },
230
- resolvers: {
231
- /**
232
- * Looks up the user by matching their GitHub username to the entity name.
233
- */
234
- usernameMatchingUserEntityName: () => {
235
- return async (info, ctx) => {
236
- const { fullProfile } = info.result;
237
- const userId = fullProfile.username;
238
- if (!userId) {
239
- throw new Error(`GitHub user profile does not contain a username`);
240
- }
241
- return ctx.signInWithCatalogUser({ entityRef: { name: userId } });
242
- };
243
- }
244
- }
245
- });
246
-
247
- const gitlab = createAuthProviderIntegration({
248
- create(options) {
249
- return pluginAuthNode.createOAuthProviderFactory({
250
- authenticator: pluginAuthBackendModuleGitlabProvider.gitlabAuthenticator,
251
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
252
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
253
- });
254
- }
255
- });
256
-
257
- const google = createAuthProviderIntegration({
258
- create(options) {
259
- return pluginAuthNode.createOAuthProviderFactory({
260
- authenticator: pluginAuthBackendModuleGoogleProvider.googleAuthenticator,
261
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
262
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
263
- });
264
- },
265
- resolvers: adaptOAuthSignInResolverToLegacy({
266
- emailLocalPartMatchingUserEntityName: pluginAuthNode.commonSignInResolvers.emailLocalPartMatchingUserEntityName(),
267
- emailMatchingUserEntityProfileEmail: pluginAuthNode.commonSignInResolvers.emailMatchingUserEntityProfileEmail(),
268
- emailMatchingUserEntityAnnotation: pluginAuthBackendModuleGoogleProvider.googleSignInResolvers.emailMatchingUserEntityAnnotation()
269
- })
270
- });
271
-
272
- const microsoft = createAuthProviderIntegration({
273
- create(options) {
274
- return pluginAuthNode.createOAuthProviderFactory({
275
- authenticator: pluginAuthBackendModuleMicrosoftProvider.microsoftAuthenticator,
276
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
277
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
278
- });
279
- },
280
- resolvers: adaptOAuthSignInResolverToLegacy({
281
- emailLocalPartMatchingUserEntityName: pluginAuthNode.commonSignInResolvers.emailLocalPartMatchingUserEntityName(),
282
- emailMatchingUserEntityProfileEmail: pluginAuthNode.commonSignInResolvers.emailMatchingUserEntityProfileEmail(),
283
- emailMatchingUserEntityAnnotation: pluginAuthBackendModuleMicrosoftProvider.microsoftSignInResolvers.emailMatchingUserEntityAnnotation(),
284
- userIdMatchingUserEntityAnnotation: pluginAuthBackendModuleMicrosoftProvider.microsoftSignInResolvers.userIdMatchingUserEntityAnnotation()
285
- })
286
- });
287
-
288
- const oauth2 = createAuthProviderIntegration({
289
- create(options) {
290
- return pluginAuthNode.createOAuthProviderFactory({
291
- authenticator: pluginAuthBackendModuleOauth2Provider.oauth2Authenticator,
292
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
293
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
294
- });
295
- }
296
- });
297
-
298
- const oauth2Proxy = createAuthProviderIntegration({
299
- create(options) {
300
- return pluginAuthNode.createProxyAuthProviderFactory({
301
- authenticator: pluginAuthBackendModuleOauth2ProxyProvider.oauth2ProxyAuthenticator,
302
- profileTransform: options?.authHandler,
303
- signInResolver: options?.signIn?.resolver
304
- });
305
- }
306
- });
307
-
308
- const commonByEmailLocalPartResolver = async (info, ctx) => {
309
- const { profile } = info;
310
- if (!profile.email) {
311
- throw new Error("Login failed, user profile does not contain an email");
312
- }
313
- const [localPart] = profile.email.split("@");
314
- return ctx.signInWithCatalogUser({
315
- entityRef: { name: localPart }
316
- });
317
- };
318
- const commonByEmailResolver = async (info, ctx) => {
319
- const { profile } = info;
320
- if (!profile.email) {
321
- throw new Error("Login failed, user profile does not contain an email");
322
- }
323
- return ctx.signInWithCatalogUser({
324
- filter: {
325
- "spec.profile.email": profile.email
326
- }
327
- });
328
- };
329
-
330
- const oidc = createAuthProviderIntegration({
331
- create(options) {
332
- const authHandler = options?.authHandler;
333
- const signInResolver = options?.signIn?.resolver;
334
- return pluginAuthNode.createOAuthProviderFactory({
335
- authenticator: pluginAuthBackendModuleOidcProvider.oidcAuthenticator,
336
- profileTransform: authHandler && ((result, context) => authHandler(result.fullProfile, context)),
337
- signInResolver: signInResolver && ((info, context) => signInResolver(
338
- {
339
- result: info.result.fullProfile,
340
- profile: info.profile
341
- },
342
- context
343
- ))
344
- });
345
- },
346
- resolvers: {
347
- /**
348
- * Looks up the user by matching their email local part to the entity name.
349
- */
350
- emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver,
351
- /**
352
- * Looks up the user by matching their email to the entity email.
353
- */
354
- emailMatchingUserEntityProfileEmail: () => commonByEmailResolver
355
- }
356
- });
357
-
358
- const okta = createAuthProviderIntegration({
359
- create(options) {
360
- return pluginAuthNode.createOAuthProviderFactory({
361
- authenticator: pluginAuthBackendModuleOktaProvider.oktaAuthenticator,
362
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
363
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
364
- });
365
- },
366
- resolvers: {
367
- /**
368
- * Looks up the user by matching their email local part to the entity name.
369
- */
370
- emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver,
371
- /**
372
- * Looks up the user by matching their email to the entity email.
373
- */
374
- emailMatchingUserEntityProfileEmail: () => commonByEmailResolver,
375
- /**
376
- * Looks up the user by matching their email to the `okta.com/email` annotation.
377
- */
378
- emailMatchingUserEntityAnnotation() {
379
- return async (info, ctx) => {
380
- const { profile } = info;
381
- if (!profile.email) {
382
- throw new Error("Okta profile contained no email");
383
- }
384
- return ctx.signInWithCatalogUser({
385
- annotations: {
386
- "okta.com/email": profile.email
387
- }
388
- });
389
- };
390
- }
391
- }
392
- });
393
-
394
- const onelogin = createAuthProviderIntegration({
395
- create(options) {
396
- return pluginAuthNode.createOAuthProviderFactory({
397
- authenticator: pluginAuthBackendModuleOneloginProvider.oneLoginAuthenticator,
398
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
399
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
400
- });
401
- }
402
- });
403
-
404
- const executeRedirectStrategy = async (req, providerStrategy, options) => {
405
- return new Promise((resolve) => {
406
- const strategy = Object.create(providerStrategy);
407
- strategy.redirect = (url, status) => {
408
- resolve({ url, status: status ?? void 0 });
409
- };
410
- strategy.authenticate(req, { ...options });
411
- });
412
- };
413
- const executeFrameHandlerStrategy = async (req, providerStrategy, options) => {
414
- return new Promise(
415
- (resolve, reject) => {
416
- const strategy = Object.create(providerStrategy);
417
- strategy.success = (result, privateInfo) => {
418
- resolve({ result, privateInfo });
419
- };
420
- strategy.fail = (info) => {
421
- reject(new Error(`Authentication rejected, ${info.message ?? ""}`));
422
- };
423
- strategy.error = (error) => {
424
- let message = `Authentication failed, ${error.message}`;
425
- if (error.oauthError?.data) {
426
- try {
427
- const errorData = JSON.parse(error.oauthError.data);
428
- if (errorData.message) {
429
- message += ` - ${errorData.message}`;
430
- }
431
- } catch (parseError) {
432
- message += ` - ${error.oauthError}`;
433
- }
434
- }
435
- reject(new Error(message));
436
- };
437
- strategy.redirect = () => {
438
- reject(new Error("Unexpected redirect"));
439
- };
440
- strategy.authenticate(req, { ...{} });
441
- }
442
- );
443
- };
444
-
445
- const safelyEncodeURIComponent = (value) => {
446
- return encodeURIComponent(value).replace(/'/g, "%27");
447
- };
448
- const postMessageResponse = (res, appOrigin, response) => {
449
- const jsonData = JSON.stringify(response);
450
- const base64Data = safelyEncodeURIComponent(jsonData);
451
- const base64Origin = safelyEncodeURIComponent(appOrigin);
452
- const script = `
453
- var authResponse = decodeURIComponent('${base64Data}');
454
- var origin = decodeURIComponent('${base64Origin}');
455
- var originInfo = {'type': 'config_info', 'targetOrigin': origin};
456
- (window.opener || window.parent).postMessage(originInfo, '*');
457
- (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin);
458
- setTimeout(() => {
459
- window.close();
460
- }, 100); // same as the interval of the core-app-api lib/loginPopup.ts (to address race conditions)
461
- `;
462
- const hash = crypto__default.default.createHash("sha256").update(script).digest("base64");
463
- res.setHeader("Content-Type", "text/html");
464
- res.setHeader("X-Frame-Options", "sameorigin");
465
- res.setHeader("Content-Security-Policy", `script-src 'sha256-${hash}'`);
466
- res.end(`<html><body><script>${script}<\/script></body></html>`);
467
- };
468
- const ensuresXRequestedWith = (req) => {
469
- const requiredHeader = req.header("X-Requested-With");
470
- if (!requiredHeader || requiredHeader !== "XMLHttpRequest") {
471
- return false;
472
- }
473
- return true;
474
- };
475
-
476
- const prepareBackstageIdentityResponse = pluginAuthNode.prepareBackstageIdentityResponse;
477
-
478
- class SamlAuthProvider {
479
- strategy;
480
- signInResolver;
481
- authHandler;
482
- resolverContext;
483
- appUrl;
484
- constructor(options) {
485
- this.appUrl = options.appUrl;
486
- this.signInResolver = options.signInResolver;
487
- this.authHandler = options.authHandler;
488
- this.resolverContext = options.resolverContext;
489
- const verifier = (profile, done) => {
490
- done(null, { fullProfile: profile });
491
- };
492
- this.strategy = new passportSaml.Strategy(options, verifier, verifier);
493
- }
494
- async start(req, res) {
495
- const { url } = await executeRedirectStrategy(req, this.strategy, {});
496
- res.redirect(url);
497
- }
498
- async frameHandler(req, res) {
499
- try {
500
- const { result } = await executeFrameHandlerStrategy(
501
- req,
502
- this.strategy
503
- );
504
- const { profile } = await this.authHandler(result, this.resolverContext);
505
- const response = {
506
- profile,
507
- providerInfo: {}
508
- };
509
- if (this.signInResolver) {
510
- const signInResponse = await this.signInResolver(
511
- {
512
- result,
513
- profile
514
- },
515
- this.resolverContext
516
- );
517
- response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
518
- }
519
- return postMessageResponse(res, this.appUrl, {
520
- type: "authorization_response",
521
- response
522
- });
523
- } catch (error) {
524
- const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
525
- return postMessageResponse(res, this.appUrl, {
526
- type: "authorization_response",
527
- error: { name, message }
528
- });
529
- }
530
- }
531
- async logout(_req, res) {
532
- res.end();
533
- }
534
- }
535
- const saml = createAuthProviderIntegration({
536
- create(options) {
537
- return ({ providerId, globalConfig, config, resolverContext }) => {
538
- const authHandler = options?.authHandler ? options.authHandler : async ({ fullProfile }) => ({
539
- profile: {
540
- email: fullProfile.email,
541
- displayName: fullProfile.displayName
542
- }
543
- });
544
- return new SamlAuthProvider({
545
- callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
546
- entryPoint: config.getString("entryPoint"),
547
- logoutUrl: config.getOptionalString("logoutUrl"),
548
- audience: config.getString("audience"),
549
- issuer: config.getString("issuer"),
550
- idpCert: config.getString("cert"),
551
- privateKey: config.getOptionalString("privateKey"),
552
- authnContext: config.getOptionalStringArray("authnContext"),
553
- identifierFormat: config.getOptionalString("identifierFormat"),
554
- decryptionPvk: config.getOptionalString("decryptionPvk"),
555
- signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
556
- digestAlgorithm: config.getOptionalString("digestAlgorithm"),
557
- acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
558
- wantAuthnResponseSigned: config.getOptionalBoolean(
559
- "wantAuthnResponseSigned"
560
- ),
561
- wantAssertionsSigned: config.getOptionalBoolean("wantAssertionsSigned"),
562
- appUrl: globalConfig.appUrl,
563
- authHandler,
564
- signInResolver: options?.signIn?.resolver,
565
- resolverContext
566
- });
567
- };
568
- },
569
- resolvers: {
570
- /**
571
- * Looks up the user by matching their nameID to the entity name.
572
- */
573
- nameIdMatchingUserEntityName() {
574
- return async (info, ctx) => {
575
- const id = info.result.fullProfile.nameID;
576
- if (!id) {
577
- throw new errors.AuthenticationError("No nameID found in SAML response");
578
- }
579
- return ctx.signInWithCatalogUser({
580
- entityRef: { name: id }
581
- });
582
- };
583
- }
584
- }
585
- });
586
-
587
- const bitbucketServer = createAuthProviderIntegration({
588
- create(options) {
589
- return pluginAuthNode.createOAuthProviderFactory({
590
- authenticator: pluginAuthBackendModuleBitbucketServerProvider.bitbucketServerAuthenticator,
591
- profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
592
- signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver)
593
- });
594
- },
595
- resolvers: {
596
- /**
597
- * Looks up the user by matching their email to the entity email.
598
- */
599
- emailMatchingUserEntityProfileEmail: () => {
600
- const resolver = pluginAuthBackendModuleBitbucketServerProvider.bitbucketServerSignInResolvers.emailMatchingUserEntityProfileEmail();
601
- return async (info, ctx) => {
602
- return resolver(
603
- {
604
- profile: info.profile,
605
- result: {
606
- fullProfile: info.result.fullProfile,
607
- session: {
608
- accessToken: info.result.accessToken,
609
- tokenType: info.result.params.token_type ?? "bearer",
610
- scope: info.result.params.scope,
611
- expiresInSeconds: info.result.params.expires_in,
612
- refreshToken: info.result.refreshToken
613
- }
614
- }
615
- },
616
- ctx
617
- );
618
- };
619
- }
620
- }
621
- });
622
-
623
- const easyAuth = createAuthProviderIntegration({
624
- create(options) {
625
- return pluginAuthNode.createProxyAuthProviderFactory({
626
- authenticator: pluginAuthBackendModuleAzureEasyauthProvider.azureEasyAuthAuthenticator,
627
- profileTransform: options?.authHandler,
628
- signInResolver: options?.signIn?.resolver
629
- });
630
- }
631
- });
632
-
633
- const providers = Object.freeze({
634
- atlassian,
635
- auth0,
636
- awsAlb,
637
- bitbucket,
638
- bitbucketServer,
639
- cfAccess,
640
- gcpIap,
641
- github,
642
- gitlab,
643
- google,
644
- microsoft,
645
- oauth2,
646
- oauth2Proxy,
647
- oidc,
648
- okta,
649
- onelogin,
650
- saml,
651
- easyAuth
652
- });
653
- const defaultAuthProviderFactories = {
654
- google: google.create(),
655
- github: github.create(),
656
- gitlab: gitlab.create(),
657
- saml: saml.create(),
658
- okta: okta.create(),
659
- auth0: auth0.create(),
660
- microsoft: microsoft.create(),
661
- easyAuth: easyAuth.create(),
662
- oauth2: oauth2.create(),
663
- oidc: oidc.create(),
664
- onelogin: onelogin.create(),
665
- awsalb: awsAlb.create(),
666
- bitbucket: bitbucket.create(),
667
- bitbucketServer: bitbucketServer.create(),
668
- atlassian: atlassian.create()
669
- };
670
-
671
- class CatalogIdentityClient {
672
- catalogApi;
673
- auth;
674
- constructor(options) {
675
- this.catalogApi = options.catalogApi;
676
- const { auth } = backendCommon.createLegacyAuthAdapters({
677
- auth: options.auth,
678
- httpAuth: options.httpAuth,
679
- discovery: options.discovery,
680
- tokenManager: options.tokenManager
681
- });
682
- this.auth = auth;
683
- }
684
- /**
685
- * Looks up a single user using a query.
686
- *
687
- * Throws a NotFoundError or ConflictError if 0 or multiple users are found.
688
- */
689
- async findUser(query) {
690
- const filter = {
691
- kind: "user"
692
- };
693
- for (const [key, value] of Object.entries(query.annotations)) {
694
- filter[`metadata.annotations.${key}`] = value;
695
- }
696
- const { token } = await this.auth.getPluginRequestToken({
697
- onBehalfOf: await this.auth.getOwnServiceCredentials(),
698
- targetPluginId: "catalog"
699
- });
700
- const { items } = await this.catalogApi.getEntities({ filter }, { token });
701
- if (items.length !== 1) {
702
- if (items.length > 1) {
703
- throw new errors.ConflictError("User lookup resulted in multiple matches");
704
- } else {
705
- throw new errors.NotFoundError("User not found");
706
- }
707
- }
708
- return items[0];
709
- }
710
- /**
711
- * Resolve additional entity claims from the catalog, using the passed-in entity names. Designed
712
- * to be used within a `signInResolver` where additional entity claims might be provided, but
713
- * group membership and transient group membership lean on imported catalog relations.
714
- *
715
- * Returns a superset of the entity names that can be passed directly to `issueToken` as `ent`.
716
- */
717
- async resolveCatalogMembership(query) {
718
- const { entityRefs, logger } = query;
719
- const resolvedEntityRefs = entityRefs.map((ref) => {
720
- try {
721
- const parsedRef = catalogModel.parseEntityRef(ref.toLocaleLowerCase("en-US"), {
722
- defaultKind: "user",
723
- defaultNamespace: "default"
724
- });
725
- return parsedRef;
726
- } catch {
727
- logger?.warn(`Failed to parse entityRef from ${ref}, ignoring`);
728
- return null;
729
- }
730
- }).filter((ref) => ref !== null);
731
- const filter = resolvedEntityRefs.map((ref) => ({
732
- kind: ref.kind,
733
- "metadata.namespace": ref.namespace,
734
- "metadata.name": ref.name
735
- }));
736
- const { token } = await this.auth.getPluginRequestToken({
737
- onBehalfOf: await this.auth.getOwnServiceCredentials(),
738
- targetPluginId: "catalog"
739
- });
740
- const entities = await this.catalogApi.getEntities({ filter }, { token }).then((r) => r.items);
741
- if (entityRefs.length !== entities.length) {
742
- const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
743
- const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
744
- logger?.debug(`Entities not found for refs ${missingEntityNames.join()}`);
745
- }
746
- const memberOf = entities.flatMap(
747
- (e) => e.relations?.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.targetRef) ?? []
748
- );
749
- const newEntityRefs = [
750
- ...new Set(resolvedEntityRefs.map(catalogModel.stringifyEntityRef).concat(memberOf))
751
- ];
752
- logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`);
753
- return newEntityRefs;
754
- }
755
- }
756
-
757
- function getDefaultOwnershipEntityRefs(entity) {
758
- const membershipRefs = entity.relations?.filter(
759
- (r) => r.type === catalogModel.RELATION_MEMBER_OF && r.targetRef.startsWith("group:")
760
- ).map((r) => r.targetRef) ?? [];
761
- return Array.from(/* @__PURE__ */ new Set([catalogModel.stringifyEntityRef(entity), ...membershipRefs]));
762
- }
763
- class CatalogAuthResolverContext {
764
- constructor(logger, tokenIssuer, catalogIdentityClient, catalogApi, auth, ownershipResolver) {
765
- this.logger = logger;
766
- this.tokenIssuer = tokenIssuer;
767
- this.catalogIdentityClient = catalogIdentityClient;
768
- this.catalogApi = catalogApi;
769
- this.auth = auth;
770
- this.ownershipResolver = ownershipResolver;
771
- }
772
- static create(options) {
773
- const catalogIdentityClient = new CatalogIdentityClient({
774
- catalogApi: options.catalogApi,
775
- tokenManager: options.tokenManager,
776
- discovery: options.discovery,
777
- auth: options.auth,
778
- httpAuth: options.httpAuth
779
- });
780
- return new CatalogAuthResolverContext(
781
- options.logger,
782
- options.tokenIssuer,
783
- catalogIdentityClient,
784
- options.catalogApi,
785
- options.auth,
786
- options.ownershipResolver
787
- );
788
- }
789
- async issueToken(params) {
790
- const token = await this.tokenIssuer.issueToken(params);
791
- return { token };
792
- }
793
- async findCatalogUser(query) {
794
- let result = void 0;
795
- const { token } = await this.auth.getPluginRequestToken({
796
- onBehalfOf: await this.auth.getOwnServiceCredentials(),
797
- targetPluginId: "catalog"
798
- });
799
- if ("entityRef" in query) {
800
- const entityRef = catalogModel.parseEntityRef(query.entityRef, {
801
- defaultKind: "User",
802
- defaultNamespace: catalogModel.DEFAULT_NAMESPACE
803
- });
804
- result = await this.catalogApi.getEntityByRef(entityRef, { token });
805
- } else if ("annotations" in query) {
806
- const filter = {
807
- kind: "user"
808
- };
809
- for (const [key, value] of Object.entries(query.annotations)) {
810
- filter[`metadata.annotations.${key}`] = value;
811
- }
812
- const res = await this.catalogApi.getEntities({ filter }, { token });
813
- result = res.items;
814
- } else if ("filter" in query) {
815
- const filter = [query.filter].flat().map((value) => {
816
- if (!Object.keys(value).some(
817
- (key) => key.toLocaleLowerCase("en-US") === "kind"
818
- )) {
819
- return {
820
- ...value,
821
- kind: "user"
822
- };
823
- }
824
- return value;
825
- });
826
- const res = await this.catalogApi.getEntities(
827
- { filter },
828
- { token }
829
- );
830
- result = res.items;
831
- } else {
832
- throw new errors.InputError("Invalid user lookup query");
833
- }
834
- if (Array.isArray(result)) {
835
- if (result.length > 1) {
836
- throw new errors.ConflictError("User lookup resulted in multiple matches");
837
- }
838
- result = result[0];
839
- }
840
- if (!result) {
841
- throw new errors.NotFoundError("User not found");
842
- }
843
- return { entity: result };
844
- }
845
- async signInWithCatalogUser(query) {
846
- const { entity } = await this.findCatalogUser(query);
847
- let ent;
848
- if (this.ownershipResolver) {
849
- const { ownershipEntityRefs } = await this.ownershipResolver.resolveOwnershipEntityRefs(entity);
850
- ent = ownershipEntityRefs;
851
- } else {
852
- ent = getDefaultOwnershipEntityRefs(entity);
853
- }
854
- const token = await this.tokenIssuer.issueToken({
855
- claims: {
856
- sub: catalogModel.stringifyEntityRef(entity),
857
- ent
858
- }
859
- });
860
- return { token };
861
- }
862
- }
863
-
864
- function bindProviderRouters(targetRouter, options) {
865
- const {
866
- providers,
867
- appUrl,
868
- baseUrl,
869
- config,
870
- logger,
871
- discovery,
872
- auth,
873
- httpAuth,
874
- tokenManager,
875
- tokenIssuer,
876
- catalogApi,
877
- ownershipResolver
878
- } = options;
879
- const providersConfig = config.getOptionalConfig("auth.providers");
880
- const isOriginAllowed = createOriginFilter(config);
881
- for (const [providerId, providerFactory] of Object.entries(providers)) {
882
- if (providersConfig?.has(providerId)) {
883
- logger.info(`Configuring auth provider: ${providerId}`);
884
- try {
885
- const provider = providerFactory({
886
- providerId,
887
- appUrl,
888
- baseUrl,
889
- isOriginAllowed,
890
- globalConfig: {
891
- baseUrl,
892
- appUrl,
893
- isOriginAllowed
894
- },
895
- config: providersConfig.getConfig(providerId),
896
- logger,
897
- resolverContext: CatalogAuthResolverContext.create({
898
- logger,
899
- catalogApi: catalogApi ?? new catalogClient.CatalogClient({ discoveryApi: discovery }),
900
- tokenIssuer,
901
- tokenManager,
902
- discovery,
903
- auth,
904
- httpAuth,
905
- ownershipResolver
906
- })
907
- });
908
- const r = Router__default.default();
909
- r.get("/start", provider.start.bind(provider));
910
- r.get("/handler/frame", provider.frameHandler.bind(provider));
911
- r.post("/handler/frame", provider.frameHandler.bind(provider));
912
- if (provider.logout) {
913
- r.post("/logout", provider.logout.bind(provider));
914
- }
915
- if (provider.refresh) {
916
- r.get("/refresh", provider.refresh.bind(provider));
917
- r.post("/refresh", provider.refresh.bind(provider));
918
- }
919
- targetRouter.use(`/${providerId}`, r);
920
- } catch (e) {
921
- errors.assertError(e);
922
- if (process.env.NODE_ENV !== "development") {
923
- throw new Error(
924
- `Failed to initialize ${providerId} auth provider, ${e.message}`
925
- );
926
- }
927
- logger.warn(`Skipping ${providerId} auth provider, ${e.message}`);
928
- targetRouter.use(`/${providerId}`, () => {
929
- throw new errors.NotFoundError(
930
- `Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under auth.providers.${providerId} are missing or the environment variables used are not defined. Check the auth backend plugin logs when the backend starts to see more details.`
931
- );
932
- });
933
- }
934
- } else {
935
- targetRouter.use(`/${providerId}`, () => {
936
- throw new errors.NotFoundError(
937
- `No auth provider registered for '${providerId}'`
938
- );
939
- });
940
- }
941
- }
942
- }
943
- function createOriginFilter(config) {
944
- const appUrl = config.getString("app.baseUrl");
945
- const { origin: appOrigin } = new URL(appUrl);
946
- const allowedOrigins = config.getOptionalStringArray(
947
- "auth.experimentalExtraAllowedOrigins"
948
- );
949
- const allowedOriginPatterns = allowedOrigins?.map(
950
- (pattern) => new minimatch.Minimatch(pattern, { nocase: true, noglobstar: true })
951
- ) ?? [];
952
- return (origin) => {
953
- if (origin === appOrigin) {
954
- return true;
955
- }
956
- return allowedOriginPatterns.some((pattern) => pattern.match(origin));
957
- };
958
- }
959
-
960
- function bindOidcRouter(targetRouter, options) {
961
- const { baseUrl, auth, tokenIssuer, userInfoDatabaseHandler } = options;
962
- const router = Router__default.default();
963
- targetRouter.use(router);
964
- const config = {
965
- issuer: baseUrl,
966
- token_endpoint: `${baseUrl}/v1/token`,
967
- userinfo_endpoint: `${baseUrl}/v1/userinfo`,
968
- jwks_uri: `${baseUrl}/.well-known/jwks.json`,
969
- response_types_supported: ["id_token"],
970
- subject_types_supported: ["public"],
971
- id_token_signing_alg_values_supported: [
972
- "RS256",
973
- "RS384",
974
- "RS512",
975
- "ES256",
976
- "ES384",
977
- "ES512",
978
- "PS256",
979
- "PS384",
980
- "PS512",
981
- "EdDSA"
982
- ],
983
- scopes_supported: ["openid"],
984
- token_endpoint_auth_methods_supported: [],
985
- claims_supported: ["sub", "ent"],
986
- grant_types_supported: []
987
- };
988
- router.get("/.well-known/openid-configuration", (_req, res) => {
989
- res.json(config);
990
- });
991
- router.get("/.well-known/jwks.json", async (_req, res) => {
992
- const { keys } = await tokenIssuer.listPublicKeys();
993
- res.json({ keys });
994
- });
995
- router.get("/v1/token", (_req, res) => {
996
- res.status(501).send("Not Implemented");
997
- });
998
- router.get("/v1/userinfo", async (req, res) => {
999
- const matches = req.headers.authorization?.match(/^Bearer[ ]+(\S+)$/i);
1000
- const token = matches?.[1];
1001
- if (!token) {
1002
- throw new errors.AuthenticationError("No token provided");
1003
- }
1004
- const credentials = await auth.authenticate(token, {
1005
- allowLimitedAccess: true
1006
- });
1007
- if (!auth.isPrincipal(credentials, "user")) {
1008
- throw new errors.InputError(
1009
- "Userinfo endpoint must be called with a token that represents a user principal"
1010
- );
1011
- }
1012
- const { sub: userEntityRef } = jose.decodeJwt(token);
1013
- if (typeof userEntityRef !== "string") {
1014
- throw new Error("Invalid user token, user entity ref must be a string");
1015
- }
1016
- const userInfo = await userInfoDatabaseHandler.getUserInfo(userEntityRef);
1017
- if (!userInfo) {
1018
- res.status(404).send("User info not found");
1019
- return;
1020
- }
1021
- res.json(userInfo);
1022
- });
1023
- }
1024
-
1025
- const MS_IN_S$1 = 1e3;
1026
- const MAX_TOKEN_LENGTH = 32768;
1027
- class TokenFactory {
1028
- issuer;
1029
- logger;
1030
- keyStore;
1031
- keyDurationSeconds;
1032
- algorithm;
1033
- userInfoDatabaseHandler;
1034
- keyExpiry;
1035
- privateKeyPromise;
1036
- constructor(options) {
1037
- this.issuer = options.issuer;
1038
- this.logger = options.logger;
1039
- this.keyStore = options.keyStore;
1040
- this.keyDurationSeconds = options.keyDurationSeconds;
1041
- this.algorithm = options.algorithm ?? "ES256";
1042
- this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;
1043
- }
1044
- async issueToken(params) {
1045
- const key = await this.getKey();
1046
- const iss = this.issuer;
1047
- const { sub, ent = [sub], ...additionalClaims } = params.claims;
1048
- const aud = pluginAuthNode.tokenTypes.user.audClaim;
1049
- const iat = Math.floor(Date.now() / MS_IN_S$1);
1050
- const exp = iat + this.keyDurationSeconds;
1051
- try {
1052
- catalogModel.parseEntityRef(sub);
1053
- } catch (error) {
1054
- throw new Error(
1055
- '"sub" claim provided by the auth resolver is not a valid EntityRef.'
1056
- );
1057
- }
1058
- if (!key.alg) {
1059
- throw new errors.AuthenticationError("No algorithm was provided in the key");
1060
- }
1061
- this.logger.info(`Issuing token for ${sub}, with entities ${ent}`);
1062
- const signingKey = await jose.importJWK(key);
1063
- const uip = await this.createUserIdentityClaim({
1064
- header: {
1065
- typ: pluginAuthNode.tokenTypes.limitedUser.typParam,
1066
- alg: key.alg,
1067
- kid: key.kid
1068
- },
1069
- payload: { sub, iat, exp },
1070
- key: signingKey
1071
- });
1072
- const claims = {
1073
- ...additionalClaims,
1074
- iss,
1075
- sub,
1076
- ent,
1077
- aud,
1078
- iat,
1079
- exp,
1080
- uip
1081
- };
1082
- const token = await new jose.SignJWT(claims).setProtectedHeader({
1083
- typ: pluginAuthNode.tokenTypes.user.typParam,
1084
- alg: key.alg,
1085
- kid: key.kid
1086
- }).sign(signingKey);
1087
- if (token.length > MAX_TOKEN_LENGTH) {
1088
- throw new Error(
1089
- `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(
1090
- claims
1091
- )}'`
1092
- );
1093
- }
1094
- await this.userInfoDatabaseHandler.addUserInfo({
1095
- claims: lodash.omit(claims, ["aud", "iat", "iss", "uip"])
1096
- });
1097
- return token;
1098
- }
1099
- // This will be called by other services that want to verify ID tokens.
1100
- // It is important that it returns a list of all public keys that could
1101
- // have been used to sign tokens that have not yet expired.
1102
- async listPublicKeys() {
1103
- const { items: keys } = await this.keyStore.listKeys();
1104
- const validKeys = [];
1105
- const expiredKeys = [];
1106
- for (const key of keys) {
1107
- const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
1108
- seconds: 3 * this.keyDurationSeconds
1109
- });
1110
- if (expireAt < luxon.DateTime.local()) {
1111
- expiredKeys.push(key);
1112
- } else {
1113
- validKeys.push(key);
1114
- }
1115
- }
1116
- if (expiredKeys.length > 0) {
1117
- const kids = expiredKeys.map(({ key }) => key.kid);
1118
- this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
1119
- this.keyStore.removeKeys(kids).catch((error) => {
1120
- this.logger.error(`Failed to remove expired keys, ${error}`);
1121
- });
1122
- }
1123
- return { keys: validKeys.map(({ key }) => key) };
1124
- }
1125
- async getKey() {
1126
- if (this.privateKeyPromise) {
1127
- if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
1128
- return this.privateKeyPromise;
1129
- }
1130
- this.logger.info(`Signing key has expired, generating new key`);
1131
- delete this.privateKeyPromise;
1132
- }
1133
- this.keyExpiry = luxon.DateTime.utc().plus({
1134
- seconds: this.keyDurationSeconds
1135
- }).toJSDate();
1136
- const promise = (async () => {
1137
- const key = await jose.generateKeyPair(this.algorithm);
1138
- const publicKey = await jose.exportJWK(key.publicKey);
1139
- const privateKey = await jose.exportJWK(key.privateKey);
1140
- publicKey.kid = privateKey.kid = uuid.v4();
1141
- publicKey.alg = privateKey.alg = this.algorithm;
1142
- this.logger.info(`Created new signing key ${publicKey.kid}`);
1143
- await this.keyStore.addKey(publicKey);
1144
- return privateKey;
1145
- })();
1146
- this.privateKeyPromise = promise;
1147
- try {
1148
- await promise;
1149
- } catch (error) {
1150
- this.logger.error(`Failed to generate new signing key, ${error}`);
1151
- delete this.keyExpiry;
1152
- delete this.privateKeyPromise;
1153
- }
1154
- return promise;
1155
- }
1156
- // Creates a string claim that can be used as part of reconstructing a limited
1157
- // user token. The output of this function is only the signature part of a
1158
- // JWS.
1159
- async createUserIdentityClaim(options) {
1160
- const header = {
1161
- typ: options.header.typ,
1162
- alg: options.header.alg,
1163
- ...options.header.kid ? { kid: options.header.kid } : {}
1164
- };
1165
- const payload = {
1166
- sub: options.payload.sub,
1167
- iat: options.payload.iat,
1168
- exp: options.payload.exp
1169
- };
1170
- const jws = await new jose.GeneralSign(
1171
- new TextEncoder().encode(JSON.stringify(payload))
1172
- ).addSignature(options.key).setProtectedHeader(header).done().sign();
1173
- return jws.signatures[0].signature;
1174
- }
1175
- }
1176
-
1177
- const TABLE$1 = "signing_keys";
1178
- const parseDate = (date) => {
1179
- const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
1180
- if (!parsedDate.isValid) {
1181
- throw new Error(
1182
- `Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`
1183
- );
1184
- }
1185
- return parsedDate.toJSDate();
1186
- };
1187
- class DatabaseKeyStore {
1188
- constructor(client) {
1189
- this.client = client;
1190
- }
1191
- async addKey(key) {
1192
- await this.client(TABLE$1).insert({
1193
- kid: key.kid,
1194
- key: JSON.stringify(key)
1195
- });
1196
- }
1197
- async listKeys() {
1198
- const rows = await this.client(TABLE$1).select();
1199
- return {
1200
- items: rows.map((row) => ({
1201
- key: JSON.parse(row.key),
1202
- createdAt: parseDate(row.created_at)
1203
- }))
1204
- };
1205
- }
1206
- async removeKeys(kids) {
1207
- await this.client(TABLE$1).delete().whereIn("kid", kids);
1208
- }
1209
- }
1210
-
1211
- class MemoryKeyStore {
1212
- keys = /* @__PURE__ */ new Map();
1213
- async addKey(key) {
1214
- this.keys.set(key.kid, {
1215
- createdAt: luxon.DateTime.utc().toJSDate(),
1216
- key: JSON.stringify(key)
1217
- });
1218
- }
1219
- async removeKeys(kids) {
1220
- for (const kid of kids) {
1221
- this.keys.delete(kid);
1222
- }
1223
- }
1224
- async listKeys() {
1225
- return {
1226
- items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
1227
- createdAt,
1228
- key: JSON.parse(keyStr)
1229
- }))
1230
- };
1231
- }
1232
- }
1233
-
1234
- const DEFAULT_TIMEOUT_MS = 1e4;
1235
- const DEFAULT_DOCUMENT_PATH = "sessions";
1236
- class FirestoreKeyStore {
1237
- constructor(database, path, timeout) {
1238
- this.database = database;
1239
- this.path = path;
1240
- this.timeout = timeout;
1241
- }
1242
- static async create(settings) {
1243
- const { path, timeout, ...firestoreSettings } = settings ?? {};
1244
- const database = new firestore.Firestore(firestoreSettings);
1245
- return new FirestoreKeyStore(
1246
- database,
1247
- path ?? DEFAULT_DOCUMENT_PATH,
1248
- timeout ?? DEFAULT_TIMEOUT_MS
1249
- );
1250
- }
1251
- static async verifyConnection(keyStore, logger) {
1252
- try {
1253
- await keyStore.verify();
1254
- } catch (error) {
1255
- if (process.env.NODE_ENV !== "development") {
1256
- throw new Error(
1257
- `Failed to connect to database: ${error.message}`
1258
- );
1259
- }
1260
- logger?.warn(
1261
- `Failed to connect to database: ${error.message}`
1262
- );
1263
- }
1264
- }
1265
- async addKey(key) {
1266
- await this.withTimeout(
1267
- this.database.collection(this.path).doc(key.kid).set({
1268
- kid: key.kid,
1269
- key: JSON.stringify(key)
1270
- })
1271
- );
1272
- }
1273
- async listKeys() {
1274
- const keys = await this.withTimeout(
1275
- this.database.collection(this.path).get()
1276
- );
1277
- return {
1278
- items: keys.docs.map((key) => ({
1279
- key: key.data(),
1280
- createdAt: key.createTime.toDate()
1281
- }))
1282
- };
1283
- }
1284
- async removeKeys(kids) {
1285
- for (const kid of kids) {
1286
- await this.withTimeout(
1287
- this.database.collection(this.path).doc(kid).delete()
1288
- );
1289
- }
1290
- }
1291
- /**
1292
- * Helper function to allow us to modify the timeout used when
1293
- * performing Firestore database operations.
1294
- *
1295
- * The reason for this is that it seems that there's no other
1296
- * practical solution to change the default timeout of 10mins
1297
- * that Firestore has.
1298
- *
1299
- */
1300
- async withTimeout(operation) {
1301
- const timer = new Promise(
1302
- (_, reject) => setTimeout(() => {
1303
- reject(new Error(`Operation timed out after ${this.timeout}ms`));
1304
- }, this.timeout)
1305
- );
1306
- return Promise.race([operation, timer]);
1307
- }
1308
- /**
1309
- * Used to verify that the database is reachable.
1310
- */
1311
- async verify() {
1312
- await this.withTimeout(this.database.collection(this.path).limit(1).get());
1313
- }
1314
- }
1315
-
1316
- const DEFAULT_ALGORITHM = "ES256";
1317
- class StaticKeyStore {
1318
- keyPairs;
1319
- createdAt;
1320
- constructor(keyPairs) {
1321
- if (keyPairs.length === 0) {
1322
- throw new Error("Should provide at least one key pair");
1323
- }
1324
- this.keyPairs = keyPairs;
1325
- this.createdAt = /* @__PURE__ */ new Date();
1326
- }
1327
- static async fromConfig(config) {
1328
- const keyConfigs = config.getConfigArray("auth.keyStore.static.keys").map((c) => {
1329
- const staticKeyConfig = {
1330
- publicKeyFile: c.getString("publicKeyFile"),
1331
- privateKeyFile: c.getString("privateKeyFile"),
1332
- keyId: c.getString("keyId"),
1333
- algorithm: c.getOptionalString("algorithm") ?? DEFAULT_ALGORITHM
1334
- };
1335
- return staticKeyConfig;
1336
- });
1337
- const keyPairs = await Promise.all(
1338
- keyConfigs.map(async (k) => await this.loadKeyPair(k))
1339
- );
1340
- return new StaticKeyStore(keyPairs);
1341
- }
1342
- addKey(_key) {
1343
- throw new Error("Cannot add keys to the static key store");
1344
- }
1345
- listKeys() {
1346
- const keys = this.keyPairs.map((k) => this.keyPairToStoredKey(k));
1347
- return Promise.resolve({ items: keys });
1348
- }
1349
- getPrivateKey(keyId) {
1350
- const keyPair = this.keyPairs.find((k) => k.publicKey.kid === keyId);
1351
- if (keyPair === void 0) {
1352
- throw new Error(`Could not find key with keyId: ${keyId}`);
1353
- }
1354
- return keyPair.privateKey;
1355
- }
1356
- removeKeys(_kids) {
1357
- throw new Error("Cannot remove keys from the static key store");
1358
- }
1359
- keyPairToStoredKey(keyPair) {
1360
- const publicKey = {
1361
- ...keyPair.publicKey,
1362
- use: "sig"
1363
- };
1364
- return {
1365
- key: publicKey,
1366
- createdAt: this.createdAt
1367
- };
1368
- }
1369
- static async loadKeyPair(options) {
1370
- const algorithm = options.algorithm;
1371
- const keyId = options.keyId;
1372
- const publicKey = await this.loadPublicKeyFromFile(
1373
- options.publicKeyFile,
1374
- keyId,
1375
- algorithm
1376
- );
1377
- const privateKey = await this.loadPrivateKeyFromFile(
1378
- options.privateKeyFile,
1379
- keyId,
1380
- algorithm
1381
- );
1382
- return { publicKey, privateKey };
1383
- }
1384
- static async loadPublicKeyFromFile(path, keyId, algorithm) {
1385
- return this.loadKeyFromFile(path, keyId, algorithm, jose.importSPKI);
1386
- }
1387
- static async loadPrivateKeyFromFile(path, keyId, algorithm) {
1388
- return this.loadKeyFromFile(path, keyId, algorithm, jose.importPKCS8);
1389
- }
1390
- static async loadKeyFromFile(path, keyId, algorithm, importer) {
1391
- const content = await fs.promises.readFile(path, { encoding: "utf8", flag: "r" });
1392
- const key = await importer(content, algorithm);
1393
- const jwk = await jose.exportJWK(key);
1394
- jwk.kid = keyId;
1395
- jwk.alg = algorithm;
1396
- return jwk;
1397
- }
1398
- }
1399
-
1400
- class KeyStores {
1401
- /**
1402
- * Looks at the `auth.keyStore` section in the application configuration
1403
- * and returns a KeyStore store. Defaults to `database`
1404
- *
1405
- * @returns a KeyStore store
1406
- */
1407
- static async fromConfig(config, options) {
1408
- const { logger, database } = options;
1409
- const ks = config.getOptionalConfig("auth.keyStore");
1410
- const provider = ks?.getOptionalString("provider") ?? "database";
1411
- logger.info(`Configuring "${provider}" as KeyStore provider`);
1412
- if (provider === "database") {
1413
- return new DatabaseKeyStore(await database.get());
1414
- }
1415
- if (provider === "memory") {
1416
- return new MemoryKeyStore();
1417
- }
1418
- if (provider === "firestore") {
1419
- const settings = ks?.getConfig(provider);
1420
- const keyStore = await FirestoreKeyStore.create(
1421
- lodash.pickBy(
1422
- {
1423
- projectId: settings?.getOptionalString("projectId"),
1424
- keyFilename: settings?.getOptionalString("keyFilename"),
1425
- host: settings?.getOptionalString("host"),
1426
- port: settings?.getOptionalNumber("port"),
1427
- ssl: settings?.getOptionalBoolean("ssl"),
1428
- path: settings?.getOptionalString("path"),
1429
- timeout: settings?.getOptionalNumber("timeout")
1430
- },
1431
- (value) => value !== void 0
1432
- )
1433
- );
1434
- await FirestoreKeyStore.verifyConnection(keyStore, logger);
1435
- return keyStore;
1436
- }
1437
- if (provider === "static") {
1438
- return await StaticKeyStore.fromConfig(config);
1439
- }
1440
- throw new Error(`Unknown KeyStore provider: ${provider}`);
1441
- }
1442
- }
1443
-
1444
- const TABLE = "user_info";
1445
- class UserInfoDatabaseHandler {
1446
- constructor(client) {
1447
- this.client = client;
1448
- }
1449
- async addUserInfo(userInfo) {
1450
- await this.client(TABLE).insert({
1451
- user_entity_ref: userInfo.claims.sub,
1452
- user_info: JSON.stringify(userInfo),
1453
- exp: luxon.DateTime.fromSeconds(userInfo.claims.exp, {
1454
- zone: "utc"
1455
- }).toSQL({ includeOffset: false })
1456
- }).onConflict("user_entity_ref").merge();
1457
- }
1458
- async getUserInfo(userEntityRef) {
1459
- const info = await this.client(TABLE).where({ user_entity_ref: userEntityRef }).first();
1460
- if (!info) {
1461
- return void 0;
1462
- }
1463
- const userInfo = JSON.parse(info.user_info);
1464
- return userInfo;
1465
- }
1466
- }
1467
-
1468
- const migrationsDir = backendPluginApi.resolvePackagePath(
1469
- "@backstage/plugin-auth-backend",
1470
- "migrations"
1471
- );
1472
- class AuthDatabase {
1473
- #database;
1474
- #promise;
1475
- static create(database) {
1476
- return new AuthDatabase(database);
1477
- }
1478
- /** @internal */
1479
- static forTesting() {
1480
- const config$1 = new config.ConfigReader({
1481
- backend: {
1482
- database: {
1483
- client: "better-sqlite3",
1484
- connection: ":memory:",
1485
- useNullAsDefault: true
1486
- }
1487
- }
1488
- });
1489
- const database = backendCommon.DatabaseManager.fromConfig(config$1).forPlugin("auth");
1490
- return new AuthDatabase(database);
1491
- }
1492
- static async runMigrations(knex) {
1493
- await knex.migrate.latest({
1494
- directory: migrationsDir
1495
- });
1496
- }
1497
- constructor(database) {
1498
- this.#database = database;
1499
- }
1500
- get() {
1501
- this.#promise ??= this.#database.getClient().then(async (client) => {
1502
- if (!this.#database.migrations?.skip) {
1503
- await AuthDatabase.runMigrations(client);
1504
- }
1505
- return client;
1506
- });
1507
- return this.#promise;
1508
- }
1509
- }
1510
-
1511
- const TOKEN_EXP_DEFAULT_S = 3600;
1512
- const TOKEN_EXP_MIN_S = 600;
1513
- const TOKEN_EXP_MAX_S = 86400;
1514
- function readBackstageTokenExpiration(config$1) {
1515
- const processingIntervalKey = "auth.backstageTokenExpiration";
1516
- if (!config$1.has(processingIntervalKey)) {
1517
- return TOKEN_EXP_DEFAULT_S;
1518
- }
1519
- const duration = config.readDurationFromConfig(config$1, {
1520
- key: processingIntervalKey
1521
- });
1522
- const durationS = Math.round(types.durationToMilliseconds(duration) / 1e3);
1523
- if (durationS < TOKEN_EXP_MIN_S) {
1524
- return TOKEN_EXP_MIN_S;
1525
- } else if (durationS > TOKEN_EXP_MAX_S) {
1526
- return TOKEN_EXP_MAX_S;
1527
- }
1528
- return durationS;
1529
- }
1530
-
1531
- const MS_IN_S = 1e3;
1532
- class StaticTokenIssuer {
1533
- issuer;
1534
- logger;
1535
- keyStore;
1536
- sessionExpirationSeconds;
1537
- constructor(options, keyStore) {
1538
- this.issuer = options.issuer;
1539
- this.logger = options.logger;
1540
- this.sessionExpirationSeconds = options.sessionExpirationSeconds;
1541
- this.keyStore = keyStore;
1542
- }
1543
- async issueToken(params) {
1544
- const key = await this.getSigningKey();
1545
- const iss = this.issuer;
1546
- const { sub, ent, ...additionalClaims } = params.claims;
1547
- const aud = "backstage";
1548
- const iat = Math.floor(Date.now() / MS_IN_S);
1549
- const exp = iat + this.sessionExpirationSeconds;
1550
- try {
1551
- catalogModel.parseEntityRef(sub);
1552
- } catch (error) {
1553
- throw new Error(
1554
- '"sub" claim provided by the auth resolver is not a valid EntityRef.'
1555
- );
1556
- }
1557
- this.logger.info(`Issuing token for ${sub}, with entities ${ent ?? []}`);
1558
- if (!key.alg) {
1559
- throw new errors.AuthenticationError("No algorithm was provided in the key");
1560
- }
1561
- 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));
1562
- }
1563
- async getSigningKey() {
1564
- const { items: keys } = await this.keyStore.listKeys();
1565
- if (keys.length >= 1) {
1566
- return this.keyStore.getPrivateKey(keys[0].key.kid);
1567
- }
1568
- throw new Error("Keystore should hold at least 1 key");
1569
- }
1570
- async listPublicKeys() {
1571
- const { items: keys } = await this.keyStore.listKeys();
1572
- return { keys: keys.map(({ key }) => key) };
1573
- }
1574
- }
1575
-
1576
- async function createRouter(options) {
1577
- const {
1578
- logger,
1579
- config,
1580
- discovery,
1581
- database,
1582
- tokenFactoryAlgorithm,
1583
- providerFactories = {}
1584
- } = options;
1585
- const { auth, httpAuth } = backendCommon.createLegacyAuthAdapters(options);
1586
- const router = Router__default.default();
1587
- const appUrl = config.getString("app.baseUrl");
1588
- const authUrl = await discovery.getExternalBaseUrl("auth");
1589
- const backstageTokenExpiration = readBackstageTokenExpiration(config);
1590
- const authDb = AuthDatabase.create(database);
1591
- const keyStore = await KeyStores.fromConfig(config, {
1592
- logger,
1593
- database: authDb
1594
- });
1595
- const userInfoDatabaseHandler = new UserInfoDatabaseHandler(
1596
- await authDb.get()
1597
- );
1598
- let tokenIssuer;
1599
- if (keyStore instanceof StaticKeyStore) {
1600
- tokenIssuer = new StaticTokenIssuer(
1601
- {
1602
- logger: logger.child({ component: "token-factory" }),
1603
- issuer: authUrl,
1604
- sessionExpirationSeconds: backstageTokenExpiration
1605
- },
1606
- keyStore
1607
- );
1608
- } else {
1609
- tokenIssuer = new TokenFactory({
1610
- issuer: authUrl,
1611
- keyStore,
1612
- keyDurationSeconds: backstageTokenExpiration,
1613
- logger: logger.child({ component: "token-factory" }),
1614
- algorithm: tokenFactoryAlgorithm ?? config.getOptionalString("auth.identityTokenAlgorithm"),
1615
- userInfoDatabaseHandler
1616
- });
1617
- }
1618
- const secret = config.getOptionalString("auth.session.secret");
1619
- if (secret) {
1620
- router.use(cookieParser__default.default(secret));
1621
- const enforceCookieSSL = authUrl.startsWith("https");
1622
- const KnexSessionStore = connectSessionKnex__default.default(session__default.default);
1623
- router.use(
1624
- session__default.default({
1625
- secret,
1626
- saveUninitialized: false,
1627
- resave: false,
1628
- cookie: { secure: enforceCookieSSL ? "auto" : false },
1629
- store: new KnexSessionStore({
1630
- createtable: false,
1631
- knex: await authDb.get()
1632
- })
1633
- })
1634
- );
1635
- router.use(passport__default.default.initialize());
1636
- router.use(passport__default.default.session());
1637
- } else {
1638
- router.use(cookieParser__default.default());
1639
- }
1640
- router.use(express__default.default.urlencoded({ extended: false }));
1641
- router.use(express__default.default.json());
1642
- const providers = options.disableDefaultProviderFactories ? providerFactories : {
1643
- ...defaultAuthProviderFactories,
1644
- ...providerFactories
1645
- };
1646
- bindProviderRouters(router, {
1647
- providers,
1648
- appUrl,
1649
- baseUrl: authUrl,
1650
- tokenIssuer,
1651
- ...options,
1652
- auth,
1653
- httpAuth
1654
- });
1655
- bindOidcRouter(router, {
1656
- auth,
1657
- tokenIssuer,
1658
- baseUrl: authUrl,
1659
- userInfoDatabaseHandler
1660
- });
1661
- router.use("/:provider/", (req) => {
1662
- const { provider } = req.params;
1663
- throw new errors.NotFoundError(`Unknown auth provider '${provider}'`);
1664
- });
1665
- return router;
1666
- }
1667
-
1668
- const authPlugin = backendPluginApi.createBackendPlugin({
1669
- pluginId: "auth",
1670
- register(reg) {
1671
- const providers = /* @__PURE__ */ new Map();
1672
- let ownershipResolver = void 0;
1673
- reg.registerExtensionPoint(pluginAuthNode.authProvidersExtensionPoint, {
1674
- registerProvider({ providerId, factory }) {
1675
- if (providers.has(providerId)) {
1676
- throw new Error(
1677
- `Auth provider '${providerId}' was already registered`
1678
- );
1679
- }
1680
- providers.set(providerId, factory);
1681
- }
1682
- });
1683
- reg.registerExtensionPoint(pluginAuthNode.authOwnershipResolutionExtensionPoint, {
1684
- setAuthOwnershipResolver(resolver) {
1685
- if (ownershipResolver) {
1686
- throw new Error("Auth ownership resolver is already set");
1687
- }
1688
- ownershipResolver = resolver;
1689
- }
1690
- });
1691
- reg.registerInit({
1692
- deps: {
1693
- httpRouter: backendPluginApi.coreServices.httpRouter,
1694
- logger: backendPluginApi.coreServices.logger,
1695
- config: backendPluginApi.coreServices.rootConfig,
1696
- database: backendPluginApi.coreServices.database,
1697
- discovery: backendPluginApi.coreServices.discovery,
1698
- auth: backendPluginApi.coreServices.auth,
1699
- httpAuth: backendPluginApi.coreServices.httpAuth,
1700
- catalogApi: alpha.catalogServiceRef
1701
- },
1702
- async init({
1703
- httpRouter,
1704
- logger,
1705
- config,
1706
- database,
1707
- discovery,
1708
- auth,
1709
- httpAuth,
1710
- catalogApi
1711
- }) {
1712
- const router = await createRouter({
1713
- logger,
1714
- config,
1715
- database,
1716
- discovery,
1717
- auth,
1718
- httpAuth,
1719
- catalogApi,
1720
- providerFactories: Object.fromEntries(providers),
1721
- disableDefaultProviderFactories: true,
1722
- ownershipResolver
1723
- });
1724
- httpRouter.addAuthPolicy({
1725
- path: "/",
1726
- allow: "unauthenticated"
1727
- });
1728
- httpRouter.use(router);
1729
- }
1730
- });
1731
- }
1732
- });
1733
-
1734
- const OAuthEnvironmentHandler = pluginAuthNode.OAuthEnvironmentHandler;
1735
-
1736
- const readState = pluginAuthNode.decodeOAuthState;
1737
- const encodeState = pluginAuthNode.encodeOAuthState;
1738
- const verifyNonce = (req, providerId) => {
1739
- const cookieNonce = req.cookies[`${providerId}-nonce`];
1740
- const state = readState(req.query.state?.toString() ?? "");
1741
- const stateNonce = state.nonce;
1742
- if (!cookieNonce) {
1743
- throw new Error("Auth response is missing cookie nonce");
1744
- }
1745
- if (stateNonce.length === 0) {
1746
- throw new Error("Auth response is missing state nonce");
1747
- }
1748
- if (cookieNonce !== stateNonce) {
1749
- throw new Error("Invalid nonce");
1750
- }
1751
- };
1752
- const defaultCookieConfigurer = ({
1753
- callbackUrl,
1754
- providerId,
1755
- appOrigin
1756
- }) => {
1757
- const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
1758
- const secure = protocol === "https:";
1759
- let sameSite = "lax";
1760
- if (new URL(appOrigin).hostname !== domain && secure) {
1761
- sameSite = "none";
1762
- }
1763
- const path = pathname.endsWith(`${providerId}/handler/frame`) ? pathname.slice(0, -"/handler/frame".length) : `${pathname}/${providerId}`;
1764
- return { domain, path, secure, sameSite };
1765
- };
1766
-
1767
- const THOUSAND_DAYS_MS = 1e3 * 24 * 60 * 60 * 1e3;
1768
- const TEN_MINUTES_MS = 600 * 1e3;
1769
- class OAuthAdapter {
1770
- constructor(handlers, options) {
1771
- this.handlers = handlers;
1772
- this.options = options;
1773
- this.baseCookieOptions = {
1774
- httpOnly: true,
1775
- sameSite: "lax"
1776
- };
1777
- }
1778
- static fromConfig(config, handlers, options) {
1779
- const { appUrl, baseUrl, isOriginAllowed } = config;
1780
- const { origin: appOrigin } = new url.URL(appUrl);
1781
- const cookieConfigurer = config.cookieConfigurer ?? defaultCookieConfigurer;
1782
- return new OAuthAdapter(handlers, {
1783
- ...options,
1784
- appOrigin,
1785
- baseUrl,
1786
- cookieConfigurer,
1787
- isOriginAllowed
1788
- });
1789
- }
1790
- baseCookieOptions;
1791
- async start(req, res) {
1792
- const scope = req.query.scope?.toString() ?? "";
1793
- const env = req.query.env?.toString();
1794
- const origin = req.query.origin?.toString();
1795
- const redirectUrl = req.query.redirectUrl?.toString();
1796
- const flow = req.query.flow?.toString();
1797
- if (!env) {
1798
- throw new errors.InputError("No env provided in request query parameters");
1799
- }
1800
- const cookieConfig = this.getCookieConfig(origin);
1801
- const nonce = crypto__default.default.randomBytes(16).toString("base64");
1802
- this.setNonceCookie(res, nonce, cookieConfig);
1803
- const state = { nonce, env, origin, redirectUrl, flow };
1804
- if (this.options.persistScopes) {
1805
- state.scope = scope;
1806
- }
1807
- const forwardReq = Object.assign(req, { scope, state });
1808
- const { url, status } = await this.handlers.start(
1809
- forwardReq
1810
- );
1811
- res.statusCode = status || 302;
1812
- res.setHeader("Location", url);
1813
- res.setHeader("Content-Length", "0");
1814
- res.end();
1815
- }
1816
- async frameHandler(req, res) {
1817
- let appOrigin = this.options.appOrigin;
1818
- try {
1819
- const state = readState(req.query.state?.toString() ?? "");
1820
- if (state.origin) {
1821
- try {
1822
- appOrigin = new url.URL(state.origin).origin;
1823
- } catch {
1824
- throw new errors.NotAllowedError("App origin is invalid, failed to parse");
1825
- }
1826
- if (!this.options.isOriginAllowed(appOrigin)) {
1827
- throw new errors.NotAllowedError(`Origin '${appOrigin}' is not allowed`);
1828
- }
1829
- }
1830
- verifyNonce(req, this.options.providerId);
1831
- const { response, refreshToken } = await this.handlers.handler(req);
1832
- const cookieConfig = this.getCookieConfig(appOrigin);
1833
- if (this.options.persistScopes && state.scope) {
1834
- this.setGrantedScopeCookie(res, state.scope, cookieConfig);
1835
- response.providerInfo.scope = state.scope;
1836
- }
1837
- if (refreshToken) {
1838
- this.setRefreshTokenCookie(res, refreshToken, cookieConfig);
1839
- }
1840
- const identity = await this.populateIdentity(response.backstageIdentity);
1841
- const responseObj = {
1842
- type: "authorization_response",
1843
- response: { ...response, backstageIdentity: identity }
1844
- };
1845
- if (state.flow === "redirect") {
1846
- if (!state.redirectUrl) {
1847
- throw new errors.InputError(
1848
- "No redirectUrl provided in request query parameters"
1849
- );
1850
- }
1851
- res.redirect(state.redirectUrl);
1852
- return void 0;
1853
- }
1854
- return postMessageResponse(res, appOrigin, responseObj);
1855
- } catch (error) {
1856
- const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
1857
- return postMessageResponse(res, appOrigin, {
1858
- type: "authorization_response",
1859
- error: { name, message }
1860
- });
1861
- }
1862
- }
1863
- async logout(req, res) {
1864
- if (!ensuresXRequestedWith(req)) {
1865
- throw new errors.AuthenticationError("Invalid X-Requested-With header");
1866
- }
1867
- if (this.handlers.logout) {
1868
- const refreshToken = this.getRefreshTokenFromCookie(req);
1869
- const revokeRequest = Object.assign(req, {
1870
- refreshToken
1871
- });
1872
- await this.handlers.logout(revokeRequest);
1873
- }
1874
- const origin = req.get("origin");
1875
- const cookieConfig = this.getCookieConfig(origin);
1876
- this.removeRefreshTokenCookie(res, cookieConfig);
1877
- res.status(200).end();
1878
- }
1879
- async refresh(req, res) {
1880
- if (!ensuresXRequestedWith(req)) {
1881
- throw new errors.AuthenticationError("Invalid X-Requested-With header");
1882
- }
1883
- if (!this.handlers.refresh) {
1884
- throw new errors.InputError(
1885
- `Refresh token is not supported for provider ${this.options.providerId}`
1886
- );
1887
- }
1888
- try {
1889
- const refreshToken = this.getRefreshTokenFromCookie(req);
1890
- if (!refreshToken) {
1891
- throw new errors.InputError("Missing session cookie");
1892
- }
1893
- let scope = req.query.scope?.toString() ?? "";
1894
- if (this.options.persistScopes) {
1895
- scope = this.getGrantedScopeFromCookie(req);
1896
- }
1897
- const forwardReq = Object.assign(req, { scope, refreshToken });
1898
- const { response, refreshToken: newRefreshToken } = await this.handlers.refresh(forwardReq);
1899
- const backstageIdentity = await this.populateIdentity(
1900
- response.backstageIdentity
1901
- );
1902
- if (newRefreshToken && newRefreshToken !== refreshToken) {
1903
- const origin = req.get("origin");
1904
- const cookieConfig = this.getCookieConfig(origin);
1905
- this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig);
1906
- }
1907
- res.status(200).json({ ...response, backstageIdentity });
1908
- } catch (error) {
1909
- throw new errors.AuthenticationError("Refresh failed", error);
1910
- }
1911
- }
1912
- /**
1913
- * If the response from the OAuth provider includes a Backstage identity, we
1914
- * make sure it's populated with all the information we can derive from the user ID.
1915
- */
1916
- async populateIdentity(identity) {
1917
- if (!identity) {
1918
- return void 0;
1919
- }
1920
- if (!identity.token) {
1921
- throw new errors.InputError(`Identity response must return a token`);
1922
- }
1923
- return prepareBackstageIdentityResponse(identity);
1924
- }
1925
- setNonceCookie = (res, nonce, cookieConfig) => {
1926
- res.cookie(`${this.options.providerId}-nonce`, nonce, {
1927
- maxAge: TEN_MINUTES_MS,
1928
- ...this.baseCookieOptions,
1929
- ...cookieConfig,
1930
- path: `${cookieConfig.path}/handler`
1931
- });
1932
- };
1933
- setGrantedScopeCookie = (res, scope, cookieConfig) => {
1934
- res.cookie(`${this.options.providerId}-granted-scope`, scope, {
1935
- maxAge: THOUSAND_DAYS_MS,
1936
- ...this.baseCookieOptions,
1937
- ...cookieConfig
1938
- });
1939
- };
1940
- getRefreshTokenFromCookie = (req) => {
1941
- return req.cookies[`${this.options.providerId}-refresh-token`];
1942
- };
1943
- getGrantedScopeFromCookie = (req) => {
1944
- return req.cookies[`${this.options.providerId}-granted-scope`];
1945
- };
1946
- setRefreshTokenCookie = (res, refreshToken, cookieConfig) => {
1947
- res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
1948
- maxAge: THOUSAND_DAYS_MS,
1949
- ...this.baseCookieOptions,
1950
- ...cookieConfig
1951
- });
1952
- };
1953
- removeRefreshTokenCookie = (res, cookieConfig) => {
1954
- res.cookie(`${this.options.providerId}-refresh-token`, "", {
1955
- maxAge: 0,
1956
- ...this.baseCookieOptions,
1957
- ...cookieConfig
1958
- });
1959
- };
1960
- getCookieConfig = (origin) => {
1961
- return this.options.cookieConfigurer({
1962
- providerId: this.options.providerId,
1963
- baseUrl: this.options.baseUrl,
1964
- callbackUrl: this.options.callbackUrl,
1965
- appOrigin: origin ?? this.options.appOrigin
1966
- });
1967
- };
1968
- }
1969
-
1970
- exports.CatalogIdentityClient = CatalogIdentityClient;
1971
- exports.OAuthAdapter = OAuthAdapter;
1972
- exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
1973
- exports.createAuthProviderIntegration = createAuthProviderIntegration;
1974
- exports.createOriginFilter = createOriginFilter;
1975
- exports.createRouter = createRouter;
1976
- exports.default = authPlugin;
1977
- exports.defaultAuthProviderFactories = defaultAuthProviderFactories;
1978
- exports.encodeState = encodeState;
1979
- exports.ensuresXRequestedWith = ensuresXRequestedWith;
1980
- exports.getDefaultOwnershipEntityRefs = getDefaultOwnershipEntityRefs;
1981
- exports.postMessageResponse = postMessageResponse;
1982
- exports.prepareBackstageIdentityResponse = prepareBackstageIdentityResponse;
1983
- exports.providers = providers;
1984
- exports.readState = readState;
1985
- exports.verifyNonce = verifyNonce;
5
+ var authPlugin = require('./authPlugin.cjs.js');
6
+ var router = require('./service/router.cjs.js');
7
+ var providers = require('./providers/providers.cjs.js');
8
+ var router$1 = require('./providers/router.cjs.js');
9
+ var createAuthProviderIntegration = require('./providers/createAuthProviderIntegration.cjs.js');
10
+ var prepareBackstageIdentityResponse = require('./providers/prepareBackstageIdentityResponse.cjs.js');
11
+ var authFlowHelpers = require('./lib/flow/authFlowHelpers.cjs.js');
12
+ var OAuthEnvironmentHandler = require('./lib/oauth/OAuthEnvironmentHandler.cjs.js');
13
+ var OAuthAdapter = require('./lib/oauth/OAuthAdapter.cjs.js');
14
+ var helpers = require('./lib/oauth/helpers.cjs.js');
15
+ var CatalogIdentityClient = require('./lib/catalog/CatalogIdentityClient.cjs.js');
16
+ var CatalogAuthResolverContext = require('./lib/resolvers/CatalogAuthResolverContext.cjs.js');
17
+
18
+
19
+
20
+ exports.default = authPlugin.authPlugin;
21
+ exports.createRouter = router.createRouter;
22
+ exports.defaultAuthProviderFactories = providers.defaultAuthProviderFactories;
23
+ exports.providers = providers.providers;
24
+ exports.createOriginFilter = router$1.createOriginFilter;
25
+ exports.createAuthProviderIntegration = createAuthProviderIntegration.createAuthProviderIntegration;
26
+ exports.prepareBackstageIdentityResponse = prepareBackstageIdentityResponse.prepareBackstageIdentityResponse;
27
+ exports.ensuresXRequestedWith = authFlowHelpers.ensuresXRequestedWith;
28
+ exports.postMessageResponse = authFlowHelpers.postMessageResponse;
29
+ exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler.OAuthEnvironmentHandler;
30
+ exports.OAuthAdapter = OAuthAdapter.OAuthAdapter;
31
+ exports.encodeState = helpers.encodeState;
32
+ exports.readState = helpers.readState;
33
+ exports.verifyNonce = helpers.verifyNonce;
34
+ exports.CatalogIdentityClient = CatalogIdentityClient.CatalogIdentityClient;
35
+ exports.getDefaultOwnershipEntityRefs = CatalogAuthResolverContext.getDefaultOwnershipEntityRefs;
1986
36
  //# sourceMappingURL=index.cjs.js.map