@backstage/backend-defaults 0.5.3 → 0.6.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 (77) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/config.d.ts +34 -6
  3. package/dist/auth.cjs.js +1 -0
  4. package/dist/auth.cjs.js.map +1 -1
  5. package/dist/auth.d.ts +27 -1
  6. package/dist/cache.d.ts +0 -1
  7. package/dist/database.d.ts +1 -1
  8. package/dist/entrypoints/auth/DefaultAuthService.cjs.js +4 -1
  9. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
  10. package/dist/entrypoints/auth/authServiceFactory.cjs.js +30 -9
  11. package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -1
  12. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +6 -10
  13. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
  14. package/dist/entrypoints/cache/CacheManager.cjs.js +20 -19
  15. package/dist/entrypoints/cache/CacheManager.cjs.js.map +1 -1
  16. package/dist/entrypoints/database/connectors/postgres.cjs.js +40 -6
  17. package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -1
  18. package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js +22 -9
  19. package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js.map +1 -1
  20. package/dist/entrypoints/httpRouter/http/createAuthIntegrationRouter.cjs.js.map +1 -0
  21. package/dist/entrypoints/httpRouter/http/createCookieAuthRefreshMiddleware.cjs.js.map +1 -0
  22. package/dist/entrypoints/httpRouter/http/createCredentialsBarrier.cjs.js.map +1 -0
  23. package/dist/entrypoints/httpRouter/http/createLifecycleMiddleware.cjs.js.map +1 -0
  24. package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js +4 -4
  25. package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -1
  26. package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js +36 -13
  27. package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js.map +1 -1
  28. package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js +9 -3
  29. package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -1
  30. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js +15 -0
  31. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -1
  32. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js +156 -0
  33. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -0
  34. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +5 -11
  35. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -1
  36. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +5 -14
  37. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -1
  38. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +5 -14
  39. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
  40. package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js +5 -14
  41. package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js.map +1 -1
  42. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +2 -10
  43. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
  44. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +4 -9
  45. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -1
  46. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +7 -16
  47. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
  48. package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js +16 -2
  49. package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -1
  50. package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js +2 -0
  51. package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js.map +1 -1
  52. package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js +23 -4
  53. package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -1
  54. package/dist/entrypoints/urlReader/lib/util.cjs.js +29 -1
  55. package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -1
  56. package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js +1 -6
  57. package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -1
  58. package/dist/httpAuth.cjs.js +1 -0
  59. package/dist/httpAuth.cjs.js.map +1 -1
  60. package/dist/httpAuth.d.ts +36 -2
  61. package/dist/httpRouter.cjs.js +8 -0
  62. package/dist/httpRouter.cjs.js.map +1 -1
  63. package/dist/httpRouter.d.ts +62 -1
  64. package/dist/package.json.cjs.js +20 -6
  65. package/dist/package.json.cjs.js.map +1 -1
  66. package/dist/urlReader.cjs.js +2 -0
  67. package/dist/urlReader.cjs.js.map +1 -1
  68. package/dist/urlReader.d.ts +41 -5
  69. package/package.json +33 -23
  70. package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js.map +0 -1
  71. package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js.map +0 -1
  72. package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js.map +0 -1
  73. package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js.map +0 -1
  74. /package/dist/entrypoints/httpRouter/{createAuthIntegrationRouter.cjs.js → http/createAuthIntegrationRouter.cjs.js} +0 -0
  75. /package/dist/entrypoints/httpRouter/{createCookieAuthRefreshMiddleware.cjs.js → http/createCookieAuthRefreshMiddleware.cjs.js} +0 -0
  76. /package/dist/entrypoints/httpRouter/{createCredentialsBarrier.cjs.js → http/createCredentialsBarrier.cjs.js} +0 -0
  77. /package/dist/entrypoints/httpRouter/{createLifecycleMiddleware.cjs.js → http/createLifecycleMiddleware.cjs.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # @backstage/backend-defaults
2
2
 
3
+ ## 0.6.0-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - fe87fbf: Add task metrics as two gauges that track the last start and end timestamps as epoch seconds.
8
+ - 1ac6b72: Support `connection.type: cloudsql` in database client for usage with `@google-cloud/cloud-sql-connector` and `iam` auth
9
+ - 5c9cc05: Use native fetch instead of node-fetch
10
+ - cf627c6: Fixed an issue in the WinstonLogger where Errors thrown and given to logger.error with field values that could not be cast to a string would throw a TypeError
11
+ - Updated dependencies
12
+ - @backstage/plugin-auth-node@0.5.5-next.1
13
+ - @backstage/backend-app-api@1.1.0-next.1
14
+ - @backstage/config-loader@1.9.3-next.0
15
+ - @backstage/backend-plugin-api@1.1.0-next.1
16
+ - @backstage/plugin-permission-node@0.8.6-next.1
17
+ - @backstage/backend-dev-utils@0.1.5
18
+ - @backstage/cli-common@0.1.15
19
+ - @backstage/cli-node@0.2.11-next.0
20
+ - @backstage/config@1.3.0
21
+ - @backstage/errors@1.2.5
22
+ - @backstage/integration@1.16.0-next.0
23
+ - @backstage/integration-aws-node@0.1.13
24
+ - @backstage/types@1.2.0
25
+ - @backstage/plugin-events-node@0.4.6-next.1
26
+
27
+ ## 0.6.0-next.0
28
+
29
+ ### Minor Changes
30
+
31
+ - de6f280: **BREAKING** Upgraded @keyv/redis and keyv packages to resolve a bug related to incorrect resolution of cache keys.
32
+
33
+ This is a breaking change for clients using the `redis` store for cache with `useRedisSets` option set to false since cache keys will be calculated differently (without the sets:namespace: prefix). For clients with default configuration (or useRedisSets set to false) the cache keys will stay the same, but since @keyv/redis library no longer supports redis sets they won't be utilised anymore.
34
+
35
+ If you were using `useRedisSets` option in configuration make sure to remove it from `app-config.yaml`:
36
+
37
+ ```diff
38
+ backend:
39
+ cache:
40
+ store: redis
41
+ connection: redis://user:pass@cache.example.com:6379
42
+ - useRedisSets: false
43
+ ```
44
+
45
+ - 277092a: Implemented `AzureBlobStorageUrlReader` to read from the url of committed location from the entity provider
46
+ - 18a2c00: All middleware used by the default `coreServices.http` is now exported for use by custom implementations.
47
+
48
+ ### Patch Changes
49
+
50
+ - 8863b38: Export `PluginTokenHandler` and `pluginTokenHandlerDecoratorServiceRef` to allow for custom decoration of the plugin token handler without having to re-implement the entire handler.
51
+ - 97c6837: Export `DefaultHttpAuthService` to allow for custom token extraction logic.
52
+ - e5255f1: Log request and response metadata so it can be used for filtering log messages.
53
+ The format of the request date was also changed from `clf` to `utc`.
54
+ - Updated dependencies
55
+ - @backstage/integration@1.16.0-next.0
56
+ - @backstage/backend-plugin-api@1.0.3-next.0
57
+ - @backstage/backend-app-api@1.0.3-next.0
58
+ - @backstage/plugin-events-node@0.4.6-next.0
59
+ - @backstage/cli-node@0.2.11-next.0
60
+ - @backstage/plugin-auth-node@0.5.5-next.0
61
+ - @backstage/backend-dev-utils@0.1.5
62
+ - @backstage/cli-common@0.1.15
63
+ - @backstage/config@1.3.0
64
+ - @backstage/config-loader@1.9.2
65
+ - @backstage/errors@1.2.5
66
+ - @backstage/integration-aws-node@0.1.13
67
+ - @backstage/types@1.2.0
68
+ - @backstage/plugin-permission-node@0.8.6-next.0
69
+
3
70
  ## 0.5.3
4
71
 
5
72
  ### Patch Changes
package/config.d.ts CHANGED
@@ -384,6 +384,16 @@ export interface Config {
384
384
  */
385
385
  connection:
386
386
  | string
387
+ | {
388
+ /**
389
+ * The specific config for cloudsql connections
390
+ */
391
+ type: 'cloudsql';
392
+ /**
393
+ * The instance connection name for the cloudsql instance, e.g. `project:region:instance`
394
+ */
395
+ instance: string;
396
+ }
387
397
  | {
388
398
  /**
389
399
  * Password that belongs to the client User
@@ -441,7 +451,30 @@ export interface Config {
441
451
  * Database connection string or Knex object override
442
452
  * @visibility secret
443
453
  */
444
- connection?: string | object;
454
+ connection?:
455
+ | string
456
+ | {
457
+ /**
458
+ * The specific config for cloudsql connections
459
+ */
460
+ type: 'cloudsql';
461
+ /**
462
+ * The instance connection name for the cloudsql instance, e.g. `project:region:instance`
463
+ */
464
+ instance: string;
465
+ }
466
+ | {
467
+ /**
468
+ * Password that belongs to the client User
469
+ * @visibility secret
470
+ */
471
+ password?: string;
472
+ /**
473
+ * Other connection settings
474
+ */
475
+ [key: string]: unknown;
476
+ };
477
+
445
478
  /**
446
479
  * Whether to ensure the given database exists by creating it if it does not.
447
480
  * Defaults to base config if unspecified.
@@ -486,11 +519,6 @@ export interface Config {
486
519
  connection: string;
487
520
  /** An optional default TTL (in milliseconds, if given as a number). */
488
521
  defaultTtl?: number | HumanDuration | string;
489
- /**
490
- * Whether or not [useRedisSets](https://github.com/jaredwray/keyv/tree/main/packages/redis#useredissets) should be configured to this redis cache.
491
- * Defaults to true if unspecified.
492
- */
493
- useRedisSets?: boolean;
494
522
  }
495
523
  | {
496
524
  store: 'memcache';
package/dist/auth.cjs.js CHANGED
@@ -5,4 +5,5 @@ var authServiceFactory = require('./entrypoints/auth/authServiceFactory.cjs.js')
5
5
 
6
6
 
7
7
  exports.authServiceFactory = authServiceFactory.authServiceFactory;
8
+ exports.pluginTokenHandlerDecoratorServiceRef = authServiceFactory.pluginTokenHandlerDecoratorServiceRef;
8
9
  //# sourceMappingURL=auth.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
1
+ {"version":3,"file":"auth.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
package/dist/auth.d.ts CHANGED
@@ -1,5 +1,31 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
2
 
3
+ /**
4
+ * @public
5
+ * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}.
6
+ */
7
+ interface PluginTokenHandler {
8
+ verifyToken(token: string): Promise<{
9
+ subject: string;
10
+ limitedUserToken?: string;
11
+ } | undefined>;
12
+ issueToken(options: {
13
+ pluginId: string;
14
+ targetPluginId: string;
15
+ onBehalfOf?: {
16
+ limitedUserToken: string;
17
+ expiresAt: Date;
18
+ };
19
+ }): Promise<{
20
+ token: string;
21
+ }>;
22
+ }
23
+
24
+ /**
25
+ * @public
26
+ * This service is used to decorate the default plugin token handler with custom logic.
27
+ */
28
+ declare const pluginTokenHandlerDecoratorServiceRef: _backstage_backend_plugin_api.ServiceRef<(defaultImplementation: PluginTokenHandler) => PluginTokenHandler, "plugin", "singleton">;
3
29
  /**
4
30
  * Handles token authentication and credentials management.
5
31
  *
@@ -11,4 +37,4 @@ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
11
37
  */
12
38
  declare const authServiceFactory: _backstage_backend_plugin_api.ServiceFactory<_backstage_backend_plugin_api.AuthService, "plugin", "singleton">;
13
39
 
14
- export { authServiceFactory };
40
+ export { type PluginTokenHandler, authServiceFactory, pluginTokenHandlerDecoratorServiceRef };
package/dist/cache.d.ts CHANGED
@@ -45,7 +45,6 @@ declare class CacheManager {
45
45
  private readonly logger?;
46
46
  private readonly store;
47
47
  private readonly connection;
48
- private readonly useRedisSets;
49
48
  private readonly errorHandler;
50
49
  private readonly defaultTtl?;
51
50
  /**
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
3
- import { RootConfigService, DatabaseService, RootLoggerService, RootLifecycleService, LoggerService, LifecycleService } from '@backstage/backend-plugin-api';
3
+ import { DatabaseService, RootLoggerService, RootLifecycleService, RootConfigService, LoggerService, LifecycleService } from '@backstage/backend-plugin-api';
4
4
 
5
5
  /**
6
6
  * Database access and management via `knex`.
@@ -95,7 +95,10 @@ class DefaultAuthService {
95
95
  return this.pluginTokenHandler.issueToken({
96
96
  pluginId: this.pluginId,
97
97
  targetPluginId,
98
- onBehalfOf
98
+ onBehalfOf: {
99
+ limitedUserToken: onBehalfOf.token,
100
+ expiresAt: onBehalfOf.expiresAt
101
+ }
99
102
  });
100
103
  }
101
104
  default:
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n constructor(\n private readonly userTokenHandler: UserTokenHandler,\n private readonly pluginTokenHandler: PluginTokenHandler,\n private readonly externalTokenHandler: ExternalTokenHandler,\n private readonly pluginId: string,\n private readonly disableDefaultAuthPolicy: boolean,\n private readonly pluginKeySource: PluginKeySource,\n ) {}\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existance.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf,\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":";;;;;;AAuCO,MAAM,kBAA0C,CAAA;AAAA,EACrD,YACmB,gBACA,EAAA,kBAAA,EACA,oBACA,EAAA,QAAA,EACA,0BACA,eACjB,EAAA;AANiB,IAAA,IAAA,CAAA,gBAAA,GAAA,gBAAA;AACA,IAAA,IAAA,CAAA,kBAAA,GAAA,kBAAA;AACA,IAAA,IAAA,CAAA,oBAAA,GAAA,oBAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,wBAAA,GAAA,wBAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AAAA;AAChB,EAEH,MAAM,YACJ,CAAA,KAAA,EACA,OAG+B,EAAA;AAC/B,IAAA,MAAM,YAAe,GAAA,MAAM,IAAK,CAAA,kBAAA,CAAmB,YAAY,KAAK,CAAA;AACpE,IAAA,IAAI,YAAc,EAAA;AAChB,MAAA,IAAI,aAAa,gBAAkB,EAAA;AACjC,QAAMA,MAAAA,WAAAA,GAAa,MAAM,IAAA,CAAK,gBAAiB,CAAA,WAAA;AAAA,UAC7C,YAAa,CAAA;AAAA,SACf;AACA,QAAA,IAAI,CAACA,WAAY,EAAA;AACf,UAAA,MAAM,IAAIC,0BAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAO,OAAAC,0CAAA;AAAA,UACLF,WAAW,CAAA,aAAA;AAAA,UACX,YAAa,CAAA,gBAAA;AAAA,UACb,IAAA,CAAK,iBAAkB,CAAA,YAAA,CAAa,gBAAgB;AAAA,SACtD;AAAA;AAEF,MAAO,OAAAG,6CAAA,CAAsC,aAAa,OAAO,CAAA;AAAA;AAGnE,IAAA,MAAM,UAAa,GAAA,MAAM,IAAK,CAAA,gBAAA,CAAiB,YAAY,KAAK,CAAA;AAChE,IAAA,IAAI,UAAY,EAAA;AACd,MAAA,IACE,CAAC,OAAS,EAAA,kBAAA,IACV,KAAK,gBAAiB,CAAA,kBAAA,CAAmB,KAAK,CAC9C,EAAA;AACA,QAAM,MAAA,IAAIF,2BAAoB,4BAA4B,CAAA;AAAA;AAG5D,MAAO,OAAAC,0CAAA;AAAA,QACL,UAAW,CAAA,aAAA;AAAA,QACX,KAAA;AAAA,QACA,IAAA,CAAK,kBAAkB,KAAK;AAAA,OAC9B;AAAA;AAGF,IAAA,MAAM,cAAiB,GAAA,MAAM,IAAK,CAAA,oBAAA,CAAqB,YAAY,KAAK,CAAA;AACxE,IAAA,IAAI,cAAgB,EAAA;AAClB,MAAO,OAAAC,6CAAA;AAAA,QACL,cAAe,CAAA,OAAA;AAAA,QACf,KAAA,CAAA;AAAA,QACA,cAAe,CAAA;AAAA,OACjB;AAAA;AAGF,IAAM,MAAA,IAAIF,2BAAoB,eAAe,CAAA;AAAA;AAC/C,EAEA,WAAA,CACE,aACA,IACqE,EAAA;AACrE,IAAA,MAAM,YAAY,WAAY,CAAA,SAAA;AAI9B,IAAA,IAAI,SAAS,SAAW,EAAA;AACtB,MAAO,OAAA,IAAA;AAAA;AAGT,IAAI,IAAA,SAAA,CAAU,SAAS,IAAM,EAAA;AAC3B,MAAO,OAAA,KAAA;AAAA;AAGT,IAAO,OAAA,IAAA;AAAA;AACT,EAEA,MAAM,kBAEJ,GAAA;AACA,IAAA,OAAOG,0CAAmC,EAAA;AAAA;AAC5C,EAEA,MAAM,wBAEJ,GAAA;AACA,IAAA,OAAOD,6CAAsC,CAAA,CAAA,OAAA,EAAU,IAAK,CAAA,QAAQ,CAAE,CAAA,CAAA;AAAA;AACxE,EAEA,MAAM,sBAAsB,OAGG,EAAA;AAC7B,IAAM,MAAA,EAAE,gBAAmB,GAAA,OAAA;AAC3B,IAAM,MAAA,eAAA,GAAkBE,sCAA+B,CAAA,OAAA,CAAQ,UAAU,CAAA;AACzE,IAAM,MAAA,EAAE,IAAK,EAAA,GAAI,eAAgB,CAAA,SAAA;AAOjC,IAAI,IAAA,IAAA,KAAS,MAAU,IAAA,IAAA,CAAK,wBAA0B,EAAA;AACpD,MAAO,OAAA,EAAE,OAAO,EAAG,EAAA;AAAA;AAKrB,IAAA,QAAQ,IAAM;AAAA;AAAA,MAEZ,KAAK,SAAA;AACH,QAAO,OAAA,IAAA,CAAK,mBAAmB,UAAW,CAAA;AAAA,UACxC,UAAU,IAAK,CAAA,QAAA;AAAA,UACf;AAAA,SACD,CAAA;AAAA,MACH,KAAK,MAAQ,EAAA;AACX,QAAM,MAAA,EAAE,OAAU,GAAA,eAAA;AAClB,QAAA,IAAI,CAAC,KAAO,EAAA;AACV,UAAM,MAAA,IAAI,MAAM,gDAAgD,CAAA;AAAA;AAElE,QAAM,MAAA,UAAA,GAAa,MAAM,IAAA,CAAK,gBAAiB,CAAA,sBAAA;AAAA,UAC7C;AAAA,SACF;AACA,QAAO,OAAA,IAAA,CAAK,mBAAmB,UAAW,CAAA;AAAA,UACxC,UAAU,IAAK,CAAA,QAAA;AAAA,UACf,cAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA;AACH,MACA;AACE,QAAA,MAAM,IAAIJ,0BAAA;AAAA,UACR,uDAAuD,IAAI,CAAA,CAAA;AAAA,SAC7D;AAAA;AACJ;AACF,EAEA,MAAM,oBACJ,WAC6C,EAAA;AAC7C,IAAA,MAAM,EAAE,KAAA,EAAO,cAAe,EAAA,GAC5BI,uCAA+B,WAAW,CAAA;AAC5C,IAAA,IAAI,CAAC,cAAgB,EAAA;AACnB,MAAA,MAAM,IAAIJ,0BAAA;AAAA,QACR;AAAA,OACF;AAAA;AAGF,IAAO,OAAA,IAAA,CAAK,gBAAiB,CAAA,sBAAA,CAAuB,cAAc,CAAA;AAAA;AACpE,EAEA,MAAM,qBAAyD,GAAA;AAC7D,IAAA,MAAM,EAAE,IAAK,EAAA,GAAI,MAAM,IAAA,CAAK,gBAAgB,QAAS,EAAA;AACrD,IAAO,OAAA,EAAE,MAAM,IAAK,CAAA,GAAA,CAAI,CAAC,EAAE,GAAA,EAAU,KAAA,GAAG,CAAE,EAAA;AAAA;AAC5C,EAEA,kBAAkB,KAAe,EAAA;AAC/B,IAAA,MAAM,EAAE,GAAA,EAAQ,GAAAK,cAAA,CAAU,KAAK,CAAA;AAC/B,IAAA,IAAI,CAAC,GAAK,EAAA;AACR,MAAM,MAAA,IAAIL,2BAAoB,kCAAkC,CAAA;AAAA;AAElE,IAAO,OAAA,IAAI,IAAK,CAAA,GAAA,GAAM,GAAI,CAAA;AAAA;AAE9B;;;;"}
1
+ {"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n constructor(\n private readonly userTokenHandler: UserTokenHandler,\n private readonly pluginTokenHandler: PluginTokenHandler,\n private readonly externalTokenHandler: ExternalTokenHandler,\n private readonly pluginId: string,\n private readonly disableDefaultAuthPolicy: boolean,\n private readonly pluginKeySource: PluginKeySource,\n ) {}\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existance.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":"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"}
@@ -7,6 +7,16 @@ var PluginTokenHandler = require('./plugin/PluginTokenHandler.cjs.js');
7
7
  var createPluginKeySource = require('./plugin/keys/createPluginKeySource.cjs.js');
8
8
  var UserTokenHandler = require('./user/UserTokenHandler.cjs.js');
9
9
 
10
+ const pluginTokenHandlerDecoratorServiceRef = backendPluginApi.createServiceRef({
11
+ id: "core.auth.pluginTokenHandlerDecorator",
12
+ defaultFactory: async (service) => backendPluginApi.createServiceFactory({
13
+ service,
14
+ deps: {},
15
+ factory: async () => {
16
+ return (impl) => impl;
17
+ }
18
+ })
19
+ });
10
20
  const authServiceFactory = backendPluginApi.createServiceFactory({
11
21
  service: backendPluginApi.coreServices.auth,
12
22
  deps: {
@@ -14,9 +24,17 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
14
24
  logger: backendPluginApi.coreServices.rootLogger,
15
25
  discovery: backendPluginApi.coreServices.discovery,
16
26
  plugin: backendPluginApi.coreServices.pluginMetadata,
17
- database: backendPluginApi.coreServices.database
27
+ database: backendPluginApi.coreServices.database,
28
+ pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef
18
29
  },
19
- async factory({ config, discovery, plugin, logger, database }) {
30
+ async factory({
31
+ config,
32
+ discovery,
33
+ plugin,
34
+ logger,
35
+ database,
36
+ pluginTokenHandlerDecorator
37
+ }) {
20
38
  const disableDefaultAuthPolicy = config.getOptionalBoolean(
21
39
  "backend.auth.dangerouslyDisableDefaultAuthPolicy"
22
40
  ) ?? false;
@@ -30,13 +48,15 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
30
48
  const userTokens = UserTokenHandler.UserTokenHandler.create({
31
49
  discovery
32
50
  });
33
- const pluginTokens = PluginTokenHandler.PluginTokenHandler.create({
34
- ownPluginId: plugin.getId(),
35
- logger,
36
- keySource,
37
- keyDuration,
38
- discovery
39
- });
51
+ const pluginTokens = pluginTokenHandlerDecorator(
52
+ PluginTokenHandler.DefaultPluginTokenHandler.create({
53
+ ownPluginId: plugin.getId(),
54
+ logger,
55
+ keySource,
56
+ keyDuration,
57
+ discovery
58
+ })
59
+ );
40
60
  const externalTokens = ExternalTokenHandler.ExternalTokenHandler.create({
41
61
  ownPluginId: plugin.getId(),
42
62
  config,
@@ -54,4 +74,5 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
54
74
  });
55
75
 
56
76
  exports.authServiceFactory = authServiceFactory;
77
+ exports.pluginTokenHandlerDecoratorServiceRef = pluginTokenHandlerDecoratorServiceRef;
57
78
  //# sourceMappingURL=authServiceFactory.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.rootLogger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n },\n async factory({ config, discovery, plugin, logger, database }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n });\n\n const pluginTokens = PluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n });\n\n const externalTokens = ExternalTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceFactory","coreServices","createPluginKeySource","UserTokenHandler","PluginTokenHandler","ExternalTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAmCO,MAAM,qBAAqBA,qCAAqB,CAAA;AAAA,EACrD,SAASC,6BAAa,CAAA,IAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,UAAUA,6BAAa,CAAA;AAAA,GACzB;AAAA,EACA,MAAM,QAAQ,EAAE,MAAA,EAAQ,WAAW,MAAQ,EAAA,MAAA,EAAQ,UAAY,EAAA;AAC7D,IAAA,MAAM,2BACJ,MAAO,CAAA,kBAAA;AAAA,MACL;AAAA,KACG,IAAA,KAAA;AAEP,IAAM,MAAA,WAAA,GAAc,EAAE,KAAA,EAAO,CAAE,EAAA;AAE/B,IAAM,MAAA,SAAA,GAAY,MAAMC,2CAAsB,CAAA;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAM,MAAA,UAAA,GAAaC,kCAAiB,MAAO,CAAA;AAAA,MACzC;AAAA,KACD,CAAA;AAED,IAAM,MAAA,YAAA,GAAeC,sCAAmB,MAAO,CAAA;AAAA,MAC7C,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,MAC1B,MAAA;AAAA,MACA,SAAA;AAAA,MACA,WAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAM,MAAA,cAAA,GAAiBC,0CAAqB,MAAO,CAAA;AAAA,MACjD,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,MAC1B,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAM,EAAA;AAAA,MACb,wBAAA;AAAA,MACA;AAAA,KACF;AAAA;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.rootLogger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef,\n },\n async factory({\n config,\n discovery,\n plugin,\n logger,\n database,\n pluginTokenHandlerDecorator,\n }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n });\n\n const pluginTokens = pluginTokenHandlerDecorator(\n DefaultPluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n }),\n );\n\n const externalTokens = ExternalTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceRef","createServiceFactory","coreServices","createPluginKeySource","UserTokenHandler","DefaultPluginTokenHandler","ExternalTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAkCO,MAAM,wCAAwCA,iCAEnD,CAAA;AAAA,EACA,EAAI,EAAA,uCAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAqB,CAAA;AAAA,IACnB,OAAA;AAAA,IACA,MAAM,EAAC;AAAA,IACP,SAAS,YAAY;AACnB,MAAA,OAAO,CAAQ,IAAA,KAAA,IAAA;AAAA;AACjB,GACD;AACL,CAAC;AAWM,MAAM,qBAAqBA,qCAAqB,CAAA;AAAA,EACrD,SAASC,6BAAa,CAAA,IAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,IACvB,2BAA6B,EAAA;AAAA,GAC/B;AAAA,EACA,MAAM,OAAQ,CAAA;AAAA,IACZ,MAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACC,EAAA;AACD,IAAA,MAAM,2BACJ,MAAO,CAAA,kBAAA;AAAA,MACL;AAAA,KACG,IAAA,KAAA;AAEP,IAAM,MAAA,WAAA,GAAc,EAAE,KAAA,EAAO,CAAE,EAAA;AAE/B,IAAM,MAAA,SAAA,GAAY,MAAMC,2CAAsB,CAAA;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAM,MAAA,UAAA,GAAaC,kCAAiB,MAAO,CAAA;AAAA,MACzC;AAAA,KACD,CAAA;AAED,IAAA,MAAM,YAAe,GAAA,2BAAA;AAAA,MACnBC,6CAA0B,MAAO,CAAA;AAAA,QAC/B,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,QAC1B,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAEA,IAAM,MAAA,cAAA,GAAiBC,0CAAqB,MAAO,CAAA;AAAA,MACjD,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,MAC1B,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAM,EAAA;AAAA,MACb,wBAAA;AAAA,MACA;AAAA,KACF;AAAA;AAEJ,CAAC;;;;;"}
@@ -5,15 +5,10 @@ var errors = require('@backstage/errors');
5
5
  var pluginAuthNode = require('@backstage/plugin-auth-node');
6
6
  var JwksClient = require('../JwksClient.cjs.js');
7
7
  var types = require('@backstage/types');
8
- var fetch = require('node-fetch');
9
-
10
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
-
12
- var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
13
8
 
14
9
  const SECONDS_IN_MS = 1e3;
15
10
  const ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;
16
- class PluginTokenHandler {
11
+ class DefaultPluginTokenHandler {
17
12
  constructor(logger, ownPluginId, keySource, algorithm, keyDurationSeconds, discovery) {
18
13
  this.logger = logger;
19
14
  this.ownPluginId = ownPluginId;
@@ -27,7 +22,7 @@ class PluginTokenHandler {
27
22
  supportedTargetPlugins = /* @__PURE__ */ new Set();
28
23
  targetPluginInflightChecks = /* @__PURE__ */ new Map();
29
24
  static create(options) {
30
- return new PluginTokenHandler(
25
+ return new DefaultPluginTokenHandler(
31
26
  options.logger,
32
27
  options.ownPluginId,
33
28
  options.keySource,
@@ -80,7 +75,7 @@ class PluginTokenHandler {
80
75
  ourExp,
81
76
  Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS)
82
77
  ) : ourExp;
83
- const claims = { sub, aud, iat, exp, obo: onBehalfOf?.token };
78
+ const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };
84
79
  const token = await new jose.SignJWT(claims).setProtectedHeader({
85
80
  typ: pluginAuthNode.tokenTypes.plugin.typParam,
86
81
  alg: this.algorithm,
@@ -98,7 +93,7 @@ class PluginTokenHandler {
98
93
  }
99
94
  const doCheck = async () => {
100
95
  try {
101
- const res = await fetch__default.default(
96
+ const res = await fetch(
102
97
  `${await this.discovery.getBaseUrl(
103
98
  targetPluginId
104
99
  )}/.backstage/auth/v1/jwks.json`
@@ -116,6 +111,7 @@ class PluginTokenHandler {
116
111
  this.supportedTargetPlugins.add(targetPluginId);
117
112
  return true;
118
113
  } catch (error) {
114
+ errors.assertError(error);
119
115
  this.logger.error("Unexpected failure for target JWKS check", error);
120
116
  return false;
121
117
  } finally {
@@ -148,5 +144,5 @@ class PluginTokenHandler {
148
144
  }
149
145
  }
150
146
 
151
- exports.PluginTokenHandler = PluginTokenHandler;
147
+ exports.DefaultPluginTokenHandler = DefaultPluginTokenHandler;
152
148
  //# sourceMappingURL=PluginTokenHandler.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\nimport fetch from 'node-fetch';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\nexport class PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new PluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n throw new AuthenticationError('Invalid plugin token', e);\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { token: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.token };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","fetch","JwksClient"],"mappings":"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"}
1
+ {"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n throw new AuthenticationError('Invalid plugin token', e);\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
@@ -23,7 +23,6 @@ class CacheManager {
23
23
  logger;
24
24
  store;
25
25
  connection;
26
- useRedisSets;
27
26
  errorHandler;
28
27
  defaultTtl;
29
28
  /**
@@ -36,10 +35,14 @@ class CacheManager {
36
35
  const store = config$1.getOptionalString("backend.cache.store") || "memory";
37
36
  const defaultTtlConfig = config$1.getOptional("backend.cache.defaultTtl");
38
37
  const connectionString = config$1.getOptionalString("backend.cache.connection") || "";
39
- const useRedisSets = config$1.getOptionalBoolean("backend.cache.useRedisSets") ?? true;
40
38
  const logger = options.logger?.child({
41
39
  type: "cacheManager"
42
40
  });
41
+ if (config$1.has("backend.cache.useRedisSets")) {
42
+ logger?.warn(
43
+ "The 'backend.cache.useRedisSets' configuration key is deprecated and no longer has any effect. The underlying '@keyv/redis' library no longer supports redis sets."
44
+ );
45
+ }
43
46
  let defaultTtl;
44
47
  if (defaultTtlConfig !== void 0) {
45
48
  if (typeof defaultTtlConfig === "number") {
@@ -53,21 +56,19 @@ class CacheManager {
53
56
  return new CacheManager(
54
57
  store,
55
58
  connectionString,
56
- useRedisSets,
57
59
  options.onError,
58
60
  logger,
59
61
  defaultTtl
60
62
  );
61
63
  }
62
64
  /** @internal */
63
- constructor(store, connectionString, useRedisSets, errorHandler, logger, defaultTtl) {
65
+ constructor(store, connectionString, errorHandler, logger, defaultTtl) {
64
66
  if (!this.storeFactories.hasOwnProperty(store)) {
65
67
  throw new Error(`Unknown cache store: ${store}`);
66
68
  }
67
69
  this.logger = logger;
68
70
  this.store = store;
69
71
  this.connection = connectionString;
70
- this.useRedisSets = useRedisSets;
71
72
  this.errorHandler = errorHandler;
72
73
  this.defaultTtl = defaultTtl;
73
74
  }
@@ -91,14 +92,14 @@ class CacheManager {
91
92
  return this.storeFactories[this.store](pluginId, ttl);
92
93
  }
93
94
  createRedisStoreFactory() {
94
- const KeyvRedis = require("@keyv/redis");
95
- let store;
95
+ const KeyvRedis = require("@keyv/redis").default;
96
+ const stores = {};
96
97
  return (pluginId, defaultTtl) => {
97
- if (!store) {
98
- store = new KeyvRedis(this.connection, {
99
- useRedisSets: this.useRedisSets
98
+ if (!stores[pluginId]) {
99
+ stores[pluginId] = new KeyvRedis(this.connection, {
100
+ keyPrefixSeparator: ":"
100
101
  });
101
- store.on("error", (err) => {
102
+ stores[pluginId].on("error", (err) => {
102
103
  this.logger?.error("Failed to create redis cache client", err);
103
104
  this.errorHandler?.(err);
104
105
  });
@@ -106,19 +107,19 @@ class CacheManager {
106
107
  return new Keyv__default.default({
107
108
  namespace: pluginId,
108
109
  ttl: defaultTtl,
109
- store,
110
+ store: stores[pluginId],
110
111
  emitErrors: false,
111
- useRedisSets: this.useRedisSets
112
+ useKeyPrefix: false
112
113
  });
113
114
  };
114
115
  }
115
116
  createMemcacheStoreFactory() {
116
- const KeyvMemcache = require("@keyv/memcache");
117
- let store;
117
+ const KeyvMemcache = require("@keyv/memcache").default;
118
+ const stores = {};
118
119
  return (pluginId, defaultTtl) => {
119
- if (!store) {
120
- store = new KeyvMemcache(this.connection);
121
- store.on("error", (err) => {
120
+ if (!stores[pluginId]) {
121
+ stores[pluginId] = new KeyvMemcache(this.connection);
122
+ stores[pluginId].on("error", (err) => {
122
123
  this.logger?.error("Failed to create memcache cache client", err);
123
124
  this.errorHandler?.(err);
124
125
  });
@@ -127,7 +128,7 @@ class CacheManager {
127
128
  namespace: pluginId,
128
129
  ttl: defaultTtl,
129
130
  emitErrors: false,
130
- store
131
+ store: stores[pluginId]
131
132
  });
132
133
  };
133
134
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CacheManager.cjs.js","sources":["../../../src/entrypoints/cache/CacheManager.ts"],"sourcesContent":["/*\n * Copyright 2021 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 CacheService,\n CacheServiceOptions,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport Keyv from 'keyv';\nimport { DefaultCacheClient } from './CacheClient';\nimport { CacheManagerOptions, ttlToMilliseconds } from './types';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { readDurationFromConfig } from '@backstage/config';\n\ntype StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv;\n\n/**\n * Implements a Cache Manager which will automatically create new cache clients\n * for plugins when requested. All requested cache clients are created with the\n * connection details provided.\n *\n * @public\n */\nexport class CacheManager {\n /**\n * Keys represent supported `backend.cache.store` values, mapped to factories\n * that return Keyv instances appropriate to the store.\n */\n private readonly storeFactories = {\n redis: this.createRedisStoreFactory(),\n memcache: this.createMemcacheStoreFactory(),\n memory: this.createMemoryStoreFactory(),\n };\n\n private readonly logger?: LoggerService;\n private readonly store: keyof CacheManager['storeFactories'];\n private readonly connection: string;\n private readonly useRedisSets: boolean;\n private readonly errorHandler: CacheManagerOptions['onError'];\n private readonly defaultTtl?: number;\n\n /**\n * Creates a new {@link CacheManager} instance by reading from the `backend`\n * config section, specifically the `.cache` key.\n *\n * @param config - The loaded application configuration.\n */\n static fromConfig(\n config: RootConfigService,\n options: CacheManagerOptions = {},\n ): CacheManager {\n // If no `backend.cache` config is provided, instantiate the CacheManager\n // with an in-memory cache client.\n const store = config.getOptionalString('backend.cache.store') || 'memory';\n const defaultTtlConfig = config.getOptional('backend.cache.defaultTtl');\n const connectionString =\n config.getOptionalString('backend.cache.connection') || '';\n const useRedisSets =\n config.getOptionalBoolean('backend.cache.useRedisSets') ?? true;\n const logger = options.logger?.child({\n type: 'cacheManager',\n });\n\n let defaultTtl: number | undefined;\n if (defaultTtlConfig !== undefined) {\n if (typeof defaultTtlConfig === 'number') {\n defaultTtl = defaultTtlConfig;\n } else {\n defaultTtl = durationToMilliseconds(\n readDurationFromConfig(config, { key: 'backend.cache.defaultTtl' }),\n );\n }\n }\n\n return new CacheManager(\n store,\n connectionString,\n useRedisSets,\n options.onError,\n logger,\n defaultTtl,\n );\n }\n\n /** @internal */\n constructor(\n store: string,\n connectionString: string,\n useRedisSets: boolean,\n errorHandler: CacheManagerOptions['onError'],\n logger?: LoggerService,\n defaultTtl?: number,\n ) {\n if (!this.storeFactories.hasOwnProperty(store)) {\n throw new Error(`Unknown cache store: ${store}`);\n }\n this.logger = logger;\n this.store = store as keyof CacheManager['storeFactories'];\n this.connection = connectionString;\n this.useRedisSets = useRedisSets;\n this.errorHandler = errorHandler;\n this.defaultTtl = defaultTtl;\n }\n\n /**\n * Generates a PluginCacheManager for consumption by plugins.\n *\n * @param pluginId - The plugin that the cache manager should be created for.\n * Plugin names should be unique.\n */\n forPlugin(pluginId: string): CacheService {\n const clientFactory = (options: CacheServiceOptions) => {\n const ttl = options.defaultTtl ?? this.defaultTtl;\n return this.getClientWithTtl(\n pluginId,\n ttl !== undefined ? ttlToMilliseconds(ttl) : undefined,\n );\n };\n\n return new DefaultCacheClient(clientFactory({}), clientFactory, {});\n }\n\n private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv {\n return this.storeFactories[this.store](pluginId, ttl);\n }\n\n private createRedisStoreFactory(): StoreFactory {\n const KeyvRedis = require('@keyv/redis');\n let store: typeof KeyvRedis | undefined;\n return (pluginId, defaultTtl) => {\n if (!store) {\n store = new KeyvRedis(this.connection, {\n useRedisSets: this.useRedisSets,\n });\n // Always provide an error handler to avoid stopping the process\n store.on('error', (err: Error) => {\n this.logger?.error('Failed to create redis cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n store,\n emitErrors: false,\n useRedisSets: this.useRedisSets,\n });\n };\n }\n\n private createMemcacheStoreFactory(): StoreFactory {\n const KeyvMemcache = require('@keyv/memcache');\n let store: typeof KeyvMemcache | undefined;\n return (pluginId, defaultTtl) => {\n if (!store) {\n store = new KeyvMemcache(this.connection);\n // Always provide an error handler to avoid stopping the process\n store.on('error', (err: Error) => {\n this.logger?.error('Failed to create memcache cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store,\n });\n };\n }\n\n private createMemoryStoreFactory(): StoreFactory {\n const store = new Map();\n return (pluginId, defaultTtl) =>\n new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store,\n });\n }\n}\n"],"names":["config","durationToMilliseconds","readDurationFromConfig","ttlToMilliseconds","DefaultCacheClient","Keyv"],"mappings":"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"}
1
+ {"version":3,"file":"CacheManager.cjs.js","sources":["../../../src/entrypoints/cache/CacheManager.ts"],"sourcesContent":["/*\n * Copyright 2021 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 CacheService,\n CacheServiceOptions,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport Keyv from 'keyv';\nimport { DefaultCacheClient } from './CacheClient';\nimport { CacheManagerOptions, ttlToMilliseconds } from './types';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { readDurationFromConfig } from '@backstage/config';\n\ntype StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv;\n\n/**\n * Implements a Cache Manager which will automatically create new cache clients\n * for plugins when requested. All requested cache clients are created with the\n * connection details provided.\n *\n * @public\n */\nexport class CacheManager {\n /**\n * Keys represent supported `backend.cache.store` values, mapped to factories\n * that return Keyv instances appropriate to the store.\n */\n private readonly storeFactories = {\n redis: this.createRedisStoreFactory(),\n memcache: this.createMemcacheStoreFactory(),\n memory: this.createMemoryStoreFactory(),\n };\n\n private readonly logger?: LoggerService;\n private readonly store: keyof CacheManager['storeFactories'];\n private readonly connection: string;\n private readonly errorHandler: CacheManagerOptions['onError'];\n private readonly defaultTtl?: number;\n\n /**\n * Creates a new {@link CacheManager} instance by reading from the `backend`\n * config section, specifically the `.cache` key.\n *\n * @param config - The loaded application configuration.\n */\n static fromConfig(\n config: RootConfigService,\n options: CacheManagerOptions = {},\n ): CacheManager {\n // If no `backend.cache` config is provided, instantiate the CacheManager\n // with an in-memory cache client.\n const store = config.getOptionalString('backend.cache.store') || 'memory';\n const defaultTtlConfig = config.getOptional('backend.cache.defaultTtl');\n const connectionString =\n config.getOptionalString('backend.cache.connection') || '';\n const logger = options.logger?.child({\n type: 'cacheManager',\n });\n\n if (config.has('backend.cache.useRedisSets')) {\n logger?.warn(\n \"The 'backend.cache.useRedisSets' configuration key is deprecated and no longer has any effect. The underlying '@keyv/redis' library no longer supports redis sets.\",\n );\n }\n\n let defaultTtl: number | undefined;\n if (defaultTtlConfig !== undefined) {\n if (typeof defaultTtlConfig === 'number') {\n defaultTtl = defaultTtlConfig;\n } else {\n defaultTtl = durationToMilliseconds(\n readDurationFromConfig(config, { key: 'backend.cache.defaultTtl' }),\n );\n }\n }\n\n return new CacheManager(\n store,\n connectionString,\n options.onError,\n logger,\n defaultTtl,\n );\n }\n\n /** @internal */\n constructor(\n store: string,\n connectionString: string,\n errorHandler: CacheManagerOptions['onError'],\n logger?: LoggerService,\n defaultTtl?: number,\n ) {\n if (!this.storeFactories.hasOwnProperty(store)) {\n throw new Error(`Unknown cache store: ${store}`);\n }\n this.logger = logger;\n this.store = store as keyof CacheManager['storeFactories'];\n this.connection = connectionString;\n this.errorHandler = errorHandler;\n this.defaultTtl = defaultTtl;\n }\n\n /**\n * Generates a PluginCacheManager for consumption by plugins.\n *\n * @param pluginId - The plugin that the cache manager should be created for.\n * Plugin names should be unique.\n */\n forPlugin(pluginId: string): CacheService {\n const clientFactory = (options: CacheServiceOptions) => {\n const ttl = options.defaultTtl ?? this.defaultTtl;\n return this.getClientWithTtl(\n pluginId,\n ttl !== undefined ? ttlToMilliseconds(ttl) : undefined,\n );\n };\n\n return new DefaultCacheClient(clientFactory({}), clientFactory, {});\n }\n\n private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv {\n return this.storeFactories[this.store](pluginId, ttl);\n }\n\n private createRedisStoreFactory(): StoreFactory {\n const KeyvRedis = require('@keyv/redis').default;\n const stores: Record<string, typeof KeyvRedis> = {};\n\n return (pluginId, defaultTtl) => {\n if (!stores[pluginId]) {\n stores[pluginId] = new KeyvRedis(this.connection, {\n keyPrefixSeparator: ':',\n });\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create redis cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n store: stores[pluginId],\n emitErrors: false,\n useKeyPrefix: false,\n });\n };\n }\n\n private createMemcacheStoreFactory(): StoreFactory {\n const KeyvMemcache = require('@keyv/memcache').default;\n const stores: Record<string, typeof KeyvMemcache> = {};\n\n return (pluginId, defaultTtl) => {\n if (!stores[pluginId]) {\n stores[pluginId] = new KeyvMemcache(this.connection);\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create memcache cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store: stores[pluginId],\n });\n };\n }\n\n private createMemoryStoreFactory(): StoreFactory {\n const store = new Map();\n return (pluginId, defaultTtl) =>\n new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store,\n });\n }\n}\n"],"names":["config","durationToMilliseconds","readDurationFromConfig","ttlToMilliseconds","DefaultCacheClient","Keyv"],"mappings":"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"}