@backstage/backend-defaults 0.17.7 → 0.17.9-next.0

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 (43) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/config.schema.json +1 -1
  3. package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js +34 -9
  4. package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js.map +1 -1
  5. package/dist/alpha/entrypoints/actionsRegistry/actionFilters.cjs.js +67 -0
  6. package/dist/alpha/entrypoints/actionsRegistry/actionFilters.cjs.js.map +1 -0
  7. package/dist/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.cjs.js +3 -0
  8. package/dist/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.cjs.js.map +1 -1
  9. package/dist/entrypoints/auth/DefaultAuthService.cjs.js +18 -2
  10. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
  11. package/dist/entrypoints/auth/JwksClient.cjs.js +61 -32
  12. package/dist/entrypoints/auth/JwksClient.cjs.js.map +1 -1
  13. package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js +2 -1
  14. package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js.map +1 -1
  15. package/dist/entrypoints/auth/helpers.cjs.js +7 -1
  16. package/dist/entrypoints/auth/helpers.cjs.js.map +1 -1
  17. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +0 -1
  18. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
  19. package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +0 -1
  20. package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -1
  21. package/dist/entrypoints/database/DatabaseManager.cjs.js +12 -0
  22. package/dist/entrypoints/database/DatabaseManager.cjs.js.map +1 -1
  23. package/dist/entrypoints/database/connectors/postgres.cjs.js +216 -59
  24. package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -1
  25. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js +4 -0
  26. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -1
  27. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js +8 -1
  28. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -1
  29. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js +8 -1
  30. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -1
  31. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +8 -4
  32. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
  33. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +19 -12
  34. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
  35. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js +2 -1
  36. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -1
  37. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js +58 -25
  38. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -1
  39. package/dist/entrypoints/urlReader/lib/util.cjs.js +30 -0
  40. package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -1
  41. package/dist/package.json.cjs.js +1 -1
  42. package/dist/urlReader.d.ts +7 -1
  43. package/package.json +17 -17
@@ -1 +1 @@
1
- {"version":3,"file":"ExternalAuthTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/external/ExternalAuthTokenHandler.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 BackstagePrincipalAccessRestrictions,\n createServiceRef,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport { legacyTokenHandler } from './legacy';\nimport { staticTokenHandler } from './static';\nimport { jwksTokenHandler } from './jwks';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\nimport { readAccessRestrictionsFromConfig } from './helpers';\n\nconst NEW_CONFIG_KEY = 'backend.auth.externalAccess';\nconst OLD_CONFIG_KEY = 'backend.auth.keys';\nlet loggedDeprecationWarning = false;\n\n/**\n * @public\n * This service is used to add custom handlers for external token.\n */\nexport const externalTokenHandlersServiceRef = createServiceRef<\n ExternalTokenHandler<unknown>\n>({\n id: 'core.auth.externalTokenHandlers',\n multiton: true,\n});\n\nconst defaultHandlers: Record<string, ExternalTokenHandler<unknown>> = {\n static: staticTokenHandler,\n legacy: legacyTokenHandler,\n jwks: jwksTokenHandler,\n};\n\ntype ContextMapEntry<T> = {\n context: T;\n handler: ExternalTokenHandler<T>;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\n/**\n * Handles all types of external caller token types (i.e. not Backstage user\n * tokens, nor Backstage backend plugin tokens).\n *\n * @internal\n */\nexport class ExternalAuthTokenHandler {\n static create(options: {\n ownPluginId: string;\n config: RootConfigService;\n logger: LoggerService;\n externalTokenHandlers?: ExternalTokenHandler<unknown>[];\n }): ExternalAuthTokenHandler {\n const {\n ownPluginId,\n config,\n externalTokenHandlers: customHandlers = [],\n logger,\n } = options;\n\n const handlersTypes = customHandlers.reduce<\n Record<string, ExternalTokenHandler<unknown>>\n >(\n (acc, handler) => {\n if (acc[handler.type]) {\n throw new Error(\n `Duplicate external token handler type '${handler.type}', each handler must have a unique type`,\n );\n }\n acc[handler.type] = handler;\n return acc;\n },\n { ...defaultHandlers },\n );\n\n const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];\n const contexts: ContextMapEntry<unknown>[] = handlerConfigs.map(\n handlerConfig => {\n const type = handlerConfig.getString('type');\n\n const handler = handlersTypes[type];\n if (!handler) {\n const valid = Object.keys(handlersTypes)\n .map(h => `'${h}'`)\n .join(', ');\n throw new Error(\n `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,\n );\n }\n return {\n context: handler.initialize({\n options: handlerConfig.getConfig('options'),\n }),\n handler,\n allAccessRestrictions: handlerConfig\n ? readAccessRestrictionsFromConfig(handlerConfig)\n : undefined,\n };\n },\n );\n\n // Load the old keys too\n const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];\n\n if (legacyConfigs.length && !loggedDeprecationWarning) {\n loggedDeprecationWarning = true;\n\n logger.warn(\n `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`,\n );\n }\n\n for (const legacyConfig of legacyConfigs) {\n contexts.push({\n context: legacyTokenHandler.initialize({\n legacy: true,\n options: legacyConfig,\n }),\n handler: legacyTokenHandler,\n allAccessRestrictions: readAccessRestrictionsFromConfig(legacyConfig),\n });\n }\n\n return new ExternalAuthTokenHandler(ownPluginId, contexts);\n }\n\n private readonly ownPluginId: string;\n private readonly contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[];\n\n constructor(\n ownPluginId: string,\n contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[],\n ) {\n this.ownPluginId = ownPluginId;\n this.contexts = contexts;\n }\n\n async verifyToken(token: string): Promise<\n | {\n subject: string;\n accessRestrictions?: BackstagePrincipalAccessRestrictions;\n }\n | undefined\n > {\n for (const { handler, allAccessRestrictions, context } of this.contexts) {\n const result = await handler.verifyToken(token, context);\n if (result) {\n if (allAccessRestrictions) {\n const accessRestrictions = allAccessRestrictions.get(\n this.ownPluginId,\n );\n if (!accessRestrictions) {\n const valid = [...allAccessRestrictions.keys()]\n .map(k => `'${k}'`)\n .join(', ');\n throw new NotAllowedError(\n `This token's access is restricted to plugin(s) ${valid}`,\n );\n }\n\n return {\n ...result,\n accessRestrictions,\n };\n }\n\n return result;\n }\n }\n\n return undefined;\n }\n}\n"],"names":["createServiceRef","staticTokenHandler","legacyTokenHandler","jwksTokenHandler","readAccessRestrictionsFromConfig","NotAllowedError"],"mappings":"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"}
1
+ {"version":3,"file":"ExternalAuthTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/external/ExternalAuthTokenHandler.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 BackstagePrincipalAccessRestrictions,\n createServiceRef,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport { legacyTokenHandler } from './legacy';\nimport { staticTokenHandler } from './static';\nimport { jwksTokenHandler } from './jwks';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\nimport { readAccessRestrictionsFromConfig } from './helpers';\n\nconst NEW_CONFIG_KEY = 'backend.auth.externalAccess';\nconst OLD_CONFIG_KEY = 'backend.auth.keys';\nlet loggedDeprecationWarning = false;\n\n/**\n * @public\n * This service is used to add custom handlers for external token.\n */\nexport const externalTokenHandlersServiceRef = createServiceRef<\n ExternalTokenHandler<unknown>\n>({\n id: 'core.auth.externalTokenHandlers',\n multiton: true,\n});\n\nconst defaultHandlers: Record<string, ExternalTokenHandler<unknown>> = {\n static: staticTokenHandler,\n legacy: legacyTokenHandler,\n jwks: jwksTokenHandler,\n};\n\ntype ContextMapEntry<T> = {\n context: T;\n handler: ExternalTokenHandler<T>;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\n/**\n * Handles all types of external caller token types (i.e. not Backstage user\n * tokens, nor Backstage backend plugin tokens).\n *\n * @internal\n */\nexport class ExternalAuthTokenHandler {\n static create(options: {\n ownPluginId: string;\n config: RootConfigService;\n logger: LoggerService;\n externalTokenHandlers?: ExternalTokenHandler<unknown>[];\n }): ExternalAuthTokenHandler {\n const {\n ownPluginId,\n config,\n externalTokenHandlers: customHandlers = [],\n logger,\n } = options;\n\n const handlersTypes = customHandlers.reduce<\n Record<string, ExternalTokenHandler<unknown>>\n >(\n (acc, handler) => {\n if (acc[handler.type]) {\n throw new Error(\n `Duplicate external token handler type '${handler.type}', each handler must have a unique type`,\n );\n }\n acc[handler.type] = handler;\n return acc;\n },\n { ...defaultHandlers },\n );\n\n const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];\n const contexts: ContextMapEntry<unknown>[] = handlerConfigs.map(\n handlerConfig => {\n const type = handlerConfig.getString('type');\n\n const handler = handlersTypes[type];\n if (!handler) {\n const valid = Object.keys(handlersTypes)\n .map(h => `'${h}'`)\n .join(', ');\n throw new Error(\n `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,\n );\n }\n return {\n context: handler.initialize({\n options: handlerConfig.getConfig('options'),\n }),\n handler,\n allAccessRestrictions: handlerConfig\n ? readAccessRestrictionsFromConfig(handlerConfig)\n : undefined,\n };\n },\n );\n\n // Load the old keys too\n const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];\n\n if (legacyConfigs.length && !loggedDeprecationWarning) {\n loggedDeprecationWarning = true;\n\n logger.warn(\n `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`,\n );\n }\n\n for (const legacyConfig of legacyConfigs) {\n contexts.push({\n context: legacyTokenHandler.initialize({\n legacy: true,\n options: legacyConfig,\n }),\n handler: legacyTokenHandler,\n allAccessRestrictions: readAccessRestrictionsFromConfig(legacyConfig),\n });\n }\n\n return new ExternalAuthTokenHandler(ownPluginId, contexts);\n }\n\n private readonly ownPluginId: string;\n private readonly contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[];\n\n constructor(\n ownPluginId: string,\n contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[],\n ) {\n this.ownPluginId = ownPluginId;\n this.contexts = contexts;\n }\n\n async verifyToken(token: string): Promise<\n | {\n subject: string;\n accessRestrictions?: BackstagePrincipalAccessRestrictions;\n allAccessRestrictions?: AccessRestrictionsMap;\n }\n | undefined\n > {\n for (const { handler, allAccessRestrictions, context } of this.contexts) {\n const result = await handler.verifyToken(token, context);\n if (result) {\n if (allAccessRestrictions) {\n const accessRestrictions = allAccessRestrictions.get(\n this.ownPluginId,\n );\n if (!accessRestrictions) {\n const valid = [...allAccessRestrictions.keys()]\n .map(k => `'${k}'`)\n .join(', ');\n throw new NotAllowedError(\n `This token's access is restricted to plugin(s) ${valid}`,\n );\n }\n\n return {\n ...result,\n accessRestrictions,\n allAccessRestrictions,\n };\n }\n\n return result;\n }\n }\n\n return undefined;\n }\n}\n"],"names":["createServiceRef","staticTokenHandler","legacyTokenHandler","jwksTokenHandler","readAccessRestrictionsFromConfig","NotAllowedError"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  var node_crypto = require('node:crypto');
4
4
 
5
- function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
5
+ function createCredentialsWithServicePrincipal(sub, token, accessRestrictions, allAccessRestrictions) {
6
6
  const principal = createServicePrincipal(sub, accessRestrictions);
7
7
  const result = {
8
8
  $$type: "@backstage/BackstageCredentials",
@@ -16,6 +16,12 @@ function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
16
16
  writable: true,
17
17
  value: token
18
18
  },
19
+ allAccessRestrictions: {
20
+ enumerable: false,
21
+ configurable: true,
22
+ writable: true,
23
+ value: allAccessRestrictions
24
+ },
19
25
  toString: {
20
26
  enumerable: false,
21
27
  configurable: true,
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.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 BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\nimport { createHash } from 'node:crypto';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n const principal = createServicePrincipal(sub, accessRestrictions);\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n actor?: string,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n const principal = createUserPrincipal(\n sub,\n actor ? createServicePrincipal(actor) : undefined,\n );\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n const principal = createNonePrincipal();\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n\nfunction createServicePrincipal(\n sub: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): BackstageServicePrincipal {\n const result = {\n type: 'service',\n subject: sub,\n accessRestrictions,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = sub;\n if (accessRestrictions) {\n const hash = createHash('sha256')\n .update(JSON.stringify(accessRestrictions))\n .digest('base64')\n .replace(/=+$/, '');\n parts += `,accessRestrictions=${hash}`;\n }\n return `servicePrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createUserPrincipal(\n userEntityRef: string,\n actor?: BackstageServicePrincipal,\n): BackstageUserPrincipal {\n const result = {\n type: 'user',\n userEntityRef,\n actor,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = userEntityRef;\n if (actor) {\n parts += `,actor={${actor}}`;\n }\n return `userPrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createNonePrincipal(): BackstageNonePrincipal {\n const result = {\n type: 'none',\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => 'nonePrincipal',\n },\n });\n return result;\n}\n"],"names":["createHash"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.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 BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\nimport { AccessRestrictionsMap } from './external/types';\nimport { createHash } from 'node:crypto';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n allAccessRestrictions?: AccessRestrictionsMap,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n const principal = createServicePrincipal(sub, accessRestrictions);\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n allAccessRestrictions: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: allAccessRestrictions,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n actor?: string,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n const principal = createUserPrincipal(\n sub,\n actor ? createServicePrincipal(actor) : undefined,\n );\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n const principal = createNonePrincipal();\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n\nfunction createServicePrincipal(\n sub: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): BackstageServicePrincipal {\n const result = {\n type: 'service',\n subject: sub,\n accessRestrictions,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = sub;\n if (accessRestrictions) {\n const hash = createHash('sha256')\n .update(JSON.stringify(accessRestrictions))\n .digest('base64')\n .replace(/=+$/, '');\n parts += `,accessRestrictions=${hash}`;\n }\n return `servicePrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createUserPrincipal(\n userEntityRef: string,\n actor?: BackstageServicePrincipal,\n): BackstageUserPrincipal {\n const result = {\n type: 'user',\n userEntityRef,\n actor,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = userEntityRef;\n if (actor) {\n parts += `,actor={${actor}}`;\n }\n return `userPrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createNonePrincipal(): BackstageNonePrincipal {\n const result = {\n type: 'none',\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => 'nonePrincipal',\n },\n });\n return result;\n}\n"],"names":["createHash"],"mappings":"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"}
@@ -56,7 +56,6 @@ class DefaultPluginTokenHandler {
56
56
  );
57
57
  }
58
58
  const jwksClient = await this.getJwksClient(pluginId);
59
- await jwksClient.refreshKeyStore(token);
60
59
  const { payload } = await jose.jwtVerify(
61
60
  token,
62
61
  jwksClient.getKey,
@@ -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, toError } 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.io/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 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 private constructor(\n logger: LoggerService,\n ownPluginId: string,\n keySource: PluginKeySource,\n algorithm: string,\n keyDurationSeconds: number,\n discovery: DiscoveryService,\n ) {\n this.logger = logger;\n this.ownPluginId = ownPluginId;\n this.keySource = keySource;\n this.algorithm = algorithm;\n this.keyDurationSeconds = keyDurationSeconds;\n this.discovery = discovery;\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 this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\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 this.logger.error(\n 'Unexpected failure for target JWKS check',\n toError(error),\n );\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","toError","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 { AuthenticationError, toError } 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.io/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 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 private constructor(\n logger: LoggerService,\n ownPluginId: string,\n keySource: PluginKeySource,\n algorithm: string,\n keyDurationSeconds: number,\n discovery: DiscoveryService,\n ) {\n this.logger = logger;\n this.ownPluginId = ownPluginId;\n this.keySource = keySource;\n this.algorithm = algorithm;\n this.keyDurationSeconds = keyDurationSeconds;\n this.discovery = discovery;\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\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 this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\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 this.logger.error(\n 'Unexpected failure for target JWKS check',\n toError(error),\n );\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","toError","JwksClient"],"mappings":"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"}
@@ -24,7 +24,6 @@ class UserTokenHandler {
24
24
  if (!verifyOpts) {
25
25
  return void 0;
26
26
  }
27
- await this.jwksClient.refreshKeyStore(token);
28
27
  const { payload } = await jose.jwtVerify(
29
28
  token,
30
29
  this.jwksClient.getKey,
@@ -1 +1 @@
1
- {"version":3,"file":"UserTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/user/UserTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport {\n base64url,\n decodeJwt,\n decodeProtectedHeader,\n jwtVerify,\n JWTVerifyOptions,\n} from 'jose';\nimport { JwksClient } from '../JwksClient';\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @internal\n */\nexport class UserTokenHandler {\n static create(options: {\n discovery: DiscoveryService;\n logger: LoggerService;\n }): UserTokenHandler {\n const jwksClient = new JwksClient(async () => {\n const url = await options.discovery.getBaseUrl('auth');\n return new URL(`${url}/.well-known/jwks.json`);\n });\n return new UserTokenHandler(jwksClient, options.logger);\n }\n\n private readonly jwksClient: JwksClient;\n private readonly logger: LoggerService;\n\n constructor(jwksClient: JwksClient, logger: LoggerService) {\n this.jwksClient = jwksClient;\n this.logger = logger;\n }\n\n async verifyToken(token: string) {\n const verifyOpts = this.#getTokenVerificationOptions(token);\n if (!verifyOpts) {\n return undefined;\n }\n\n await this.jwksClient.refreshKeyStore(token);\n\n // Verify a limited token, ensuring the necessarily claims are present and token type is correct\n const { payload } = await jwtVerify(\n token,\n this.jwksClient.getKey,\n verifyOpts,\n ).catch(e => {\n this.logger.warn('Failed to verify incoming user token', e);\n throw new AuthenticationError('Failed user token verification');\n });\n\n const userEntityRef = payload.sub;\n\n if (!userEntityRef) {\n throw new AuthenticationError('No user sub found in token');\n }\n\n return { userEntityRef };\n }\n\n #getTokenVerificationOptions(token: string): JWTVerifyOptions | undefined {\n try {\n const { typ } = decodeProtectedHeader(token);\n\n if (typ === tokenTypes.user.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.user.typParam,\n };\n }\n\n if (typ === tokenTypes.limitedUser.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.limitedUser.typParam,\n };\n }\n\n const { aud } = decodeJwt(token);\n if (aud === tokenTypes.user.audClaim) {\n return {\n audience: tokenTypes.user.audClaim,\n };\n }\n } catch {\n /* ignore */\n }\n\n return undefined;\n }\n\n createLimitedUserToken(backstageToken: string) {\n const [headerRaw, payloadRaw] = backstageToken.split('.');\n const header = JSON.parse(\n new TextDecoder().decode(base64url.decode(headerRaw)),\n );\n const payload = JSON.parse(\n new TextDecoder().decode(base64url.decode(payloadRaw)),\n );\n\n const tokenType = header.typ;\n\n // Only new user tokens can be used to create a limited user token. If we\n // can't create a limited token, or the token is already a limited one, we\n // return the original token\n if (!tokenType || tokenType === tokenTypes.limitedUser.typParam) {\n return { token: backstageToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n if (tokenType !== tokenTypes.user.typParam) {\n throw new AuthenticationError(\n 'Failed to create limited user token, invalid token type',\n );\n }\n\n if (!payload.uip) {\n throw new AuthenticationError(\n 'Failed to create limited user token, missing user identity proof',\n );\n }\n\n // NOTE: The order and properties in both the header and payload must match\n // the usage in plugins/auth-backend/src/identity/TokenFactory.ts\n const limitedUserToken = [\n base64url.encode(\n JSON.stringify({\n typ: tokenTypes.limitedUser.typParam,\n alg: header.alg,\n kid: header.kid,\n }),\n ),\n base64url.encode(\n JSON.stringify({\n sub: payload.sub,\n iat: payload.iat,\n exp: payload.exp,\n }),\n ),\n payload.uip,\n ].join('.');\n\n return { token: limitedUserToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n isLimitedUserToken(token: string): boolean {\n try {\n const { typ } = decodeProtectedHeader(token);\n return typ === tokenTypes.limitedUser.typParam;\n } catch {\n return false;\n }\n }\n}\n"],"names":["JwksClient","jwtVerify","AuthenticationError","decodeProtectedHeader","tokenTypes","decodeJwt","base64url"],"mappings":"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"}
1
+ {"version":3,"file":"UserTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/user/UserTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport {\n base64url,\n decodeJwt,\n decodeProtectedHeader,\n jwtVerify,\n JWTVerifyOptions,\n} from 'jose';\nimport { JwksClient } from '../JwksClient';\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @internal\n */\nexport class UserTokenHandler {\n static create(options: {\n discovery: DiscoveryService;\n logger: LoggerService;\n }): UserTokenHandler {\n const jwksClient = new JwksClient(async () => {\n const url = await options.discovery.getBaseUrl('auth');\n return new URL(`${url}/.well-known/jwks.json`);\n });\n return new UserTokenHandler(jwksClient, options.logger);\n }\n\n private readonly jwksClient: JwksClient;\n private readonly logger: LoggerService;\n\n constructor(jwksClient: JwksClient, logger: LoggerService) {\n this.jwksClient = jwksClient;\n this.logger = logger;\n }\n\n async verifyToken(token: string) {\n const verifyOpts = this.#getTokenVerificationOptions(token);\n if (!verifyOpts) {\n return undefined;\n }\n\n // Verify a limited token, ensuring the necessarily claims are present and token type is correct\n const { payload } = await jwtVerify(\n token,\n this.jwksClient.getKey,\n verifyOpts,\n ).catch(e => {\n this.logger.warn('Failed to verify incoming user token', e);\n throw new AuthenticationError('Failed user token verification');\n });\n\n const userEntityRef = payload.sub;\n\n if (!userEntityRef) {\n throw new AuthenticationError('No user sub found in token');\n }\n\n return { userEntityRef };\n }\n\n #getTokenVerificationOptions(token: string): JWTVerifyOptions | undefined {\n try {\n const { typ } = decodeProtectedHeader(token);\n\n if (typ === tokenTypes.user.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.user.typParam,\n };\n }\n\n if (typ === tokenTypes.limitedUser.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.limitedUser.typParam,\n };\n }\n\n const { aud } = decodeJwt(token);\n if (aud === tokenTypes.user.audClaim) {\n return {\n audience: tokenTypes.user.audClaim,\n };\n }\n } catch {\n /* ignore */\n }\n\n return undefined;\n }\n\n createLimitedUserToken(backstageToken: string) {\n const [headerRaw, payloadRaw] = backstageToken.split('.');\n const header = JSON.parse(\n new TextDecoder().decode(base64url.decode(headerRaw)),\n );\n const payload = JSON.parse(\n new TextDecoder().decode(base64url.decode(payloadRaw)),\n );\n\n const tokenType = header.typ;\n\n // Only new user tokens can be used to create a limited user token. If we\n // can't create a limited token, or the token is already a limited one, we\n // return the original token\n if (!tokenType || tokenType === tokenTypes.limitedUser.typParam) {\n return { token: backstageToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n if (tokenType !== tokenTypes.user.typParam) {\n throw new AuthenticationError(\n 'Failed to create limited user token, invalid token type',\n );\n }\n\n if (!payload.uip) {\n throw new AuthenticationError(\n 'Failed to create limited user token, missing user identity proof',\n );\n }\n\n // NOTE: The order and properties in both the header and payload must match\n // the usage in plugins/auth-backend/src/identity/TokenFactory.ts\n const limitedUserToken = [\n base64url.encode(\n JSON.stringify({\n typ: tokenTypes.limitedUser.typParam,\n alg: header.alg,\n kid: header.kid,\n }),\n ),\n base64url.encode(\n JSON.stringify({\n sub: payload.sub,\n iat: payload.iat,\n exp: payload.exp,\n }),\n ),\n payload.uip,\n ].join('.');\n\n return { token: limitedUserToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n isLimitedUserToken(token: string): boolean {\n try {\n const { typ } = decodeProtectedHeader(token);\n return typ === tokenTypes.limitedUser.typParam;\n } catch {\n return false;\n }\n }\n}\n"],"names":["JwksClient","jwtVerify","AuthenticationError","decodeProtectedHeader","tokenTypes","decodeJwt","base64url"],"mappings":"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"}
@@ -68,6 +68,18 @@ class DatabaseManagerImpl {
68
68
  }
69
69
  })
70
70
  );
71
+ const connectors = new Set(Object.values(this.connectors));
72
+ await Promise.all(
73
+ Array.from(connectors, async (connector) => {
74
+ try {
75
+ await connector.shutdown?.();
76
+ } catch (error) {
77
+ deps?.logger?.error(
78
+ `Problem closing database connector: ${errors.stringifyError(error)}`
79
+ );
80
+ }
81
+ })
82
+ );
71
83
  }
72
84
  /**
73
85
  * Provides the client type which should be used for a given plugin.
@@ -1 +1 @@
1
- {"version":3,"file":"DatabaseManager.cjs.js","sources":["../../../src/entrypoints/database/DatabaseManager.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 DatabaseService,\n LifecycleService,\n LoggerService,\n RootConfigService,\n RootLifecycleService,\n RootLoggerService,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { stringifyError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { MysqlConnector } from './connectors/mysql';\nimport { PgConnector } from './connectors/postgres';\nimport { Sqlite3Connector } from './connectors/sqlite3';\nimport { Connector } from './types';\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\n/**\n * Creation options for {@link DatabaseManager}.\n *\n * @public\n */\nexport type DatabaseManagerOptions = {\n migrations?: DatabaseService['migrations'];\n rootLogger?: RootLoggerService;\n rootLifecycle?: RootLifecycleService;\n};\n\n/**\n * Testable implementation class for {@link DatabaseManager} below.\n */\nexport class DatabaseManagerImpl {\n private readonly config: Config;\n private readonly connectors: Record<string, Connector>;\n private readonly options?: DatabaseManagerOptions;\n private readonly databaseCache: Map<string, Promise<Knex>>;\n private readonly keepaliveIntervals: Map<string, NodeJS.Timeout>;\n\n constructor(\n config: Config,\n connectors: Record<string, Connector>,\n options?: DatabaseManagerOptions,\n databaseCache: Map<string, Promise<Knex>> = new Map(),\n keepaliveIntervals: Map<string, NodeJS.Timeout> = new Map(),\n ) {\n this.config = config;\n this.connectors = connectors;\n this.options = options;\n this.databaseCache = databaseCache;\n this.keepaliveIntervals = keepaliveIntervals;\n // If a rootLifecycle service was provided, register a shutdown hook to\n // clean up any database connections.\n if (options?.rootLifecycle !== undefined) {\n options.rootLifecycle.addShutdownHook(async () => {\n await this.shutdown({ logger: options.rootLogger });\n });\n }\n }\n\n /**\n * Generates a DatabaseService for consumption by plugins.\n *\n * @param pluginId - The plugin that the database manager should be created for. Plugin names\n * should be unique as they are used to look up database config overrides under\n * `backend.database.plugin`.\n */\n forPlugin(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): DatabaseService {\n const client = this.getClientType(pluginId).client;\n const connector = this.connectors[client];\n if (!connector) {\n throw new Error(\n `Unsupported database client type '${client}' specified for plugin '${pluginId}'`,\n );\n }\n const getClient = () => this.getDatabase(pluginId, connector, deps);\n\n const skip =\n this.options?.migrations?.skip ??\n this.config.getOptionalBoolean(`plugin.${pluginId}.skipMigrations`) ??\n this.config.getOptionalBoolean('skipMigrations') ??\n false;\n\n return { getClient, migrations: { skip } };\n }\n\n /**\n * Destroys all known connections.\n */\n private async shutdown(deps?: { logger?: LoggerService }): Promise<void> {\n const pluginIds = Array.from(this.databaseCache.keys());\n await Promise.allSettled(\n pluginIds.map(async pluginId => {\n // We no longer need to keep connections alive.\n clearInterval(this.keepaliveIntervals.get(pluginId));\n\n const connection = await this.databaseCache.get(pluginId);\n if (connection) {\n if (connection.client.config.includes('sqlite3')) {\n return; // sqlite3 does not support destroy, it hangs\n }\n await connection.destroy().catch((error: unknown) => {\n deps?.logger?.error(\n `Problem closing database connection for ${pluginId}: ${stringifyError(\n error,\n )}`,\n );\n });\n }\n }),\n );\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n /**\n * Provides a scoped Knex client for a plugin as per application config.\n *\n * @param pluginId - Plugin to get a Knex client for\n * @returns Promise which resolves to a scoped Knex database client for a\n * plugin\n */\n private async getDatabase(\n pluginId: string,\n connector: Connector,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n if (this.databaseCache.has(pluginId)) {\n return this.databaseCache.get(pluginId)!;\n }\n\n const clientPromise = connector.getClient(pluginId, deps);\n this.databaseCache.set(pluginId, clientPromise);\n\n if (process.env.NODE_ENV !== 'test') {\n clientPromise.then(client =>\n this.startKeepaliveLoop(pluginId, client, deps.logger),\n );\n }\n\n return clientPromise;\n }\n\n private startKeepaliveLoop(\n pluginId: string,\n client: Knex,\n logger: LoggerService,\n ): void {\n let lastKeepaliveFailed = false;\n\n this.keepaliveIntervals.set(\n pluginId,\n setInterval(() => {\n // During testing it can happen that the environment is torn down and\n // this client is `undefined`, but this interval is still run.\n client?.raw('select 1').then(\n () => {\n lastKeepaliveFailed = false;\n },\n (error: unknown) => {\n if (!lastKeepaliveFailed) {\n lastKeepaliveFailed = true;\n logger.warn(\n `Database keepalive failed for plugin ${pluginId}, ${stringifyError(\n error,\n )}`,\n );\n }\n },\n );\n }, 60 * 1000),\n );\n }\n}\n\n// NOTE: This class looks odd but is kept around for API compatibility reasons\n/**\n * Manages database connections for Backstage backend plugins.\n *\n * @public\n * @remarks\n *\n * The database manager allows the user to set connection and client settings on\n * a per pluginId basis by defining a database config block under\n * `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may\n * set `prefix` which is used to prefix generated database names if config is\n * not provided.\n */\nexport class DatabaseManager {\n /**\n * Creates a {@link DatabaseManager} from `backend.database` config.\n *\n * @param config - The loaded application configuration.\n * @param options - An optional configuration object.\n */\n static fromConfig(\n config: RootConfigService,\n options?: DatabaseManagerOptions,\n ): DatabaseManager {\n const databaseConfig = config.getConfig('backend.database');\n const prefix =\n databaseConfig.getOptionalString('prefix') || 'backstage_plugin_';\n return new DatabaseManager(\n new DatabaseManagerImpl(\n databaseConfig,\n {\n pg: new PgConnector(databaseConfig, prefix),\n sqlite3: new Sqlite3Connector(databaseConfig),\n 'better-sqlite3': new Sqlite3Connector(databaseConfig),\n mysql: new MysqlConnector(databaseConfig, prefix),\n mysql2: new MysqlConnector(databaseConfig, prefix),\n },\n options,\n ),\n );\n }\n\n private readonly impl: DatabaseManagerImpl;\n\n private constructor(impl: DatabaseManagerImpl) {\n this.impl = impl;\n }\n\n /**\n * Generates a DatabaseService for consumption by plugins.\n *\n * @param pluginId - The plugin that the database manager should be created for. Plugin names\n * should be unique as they are used to look up database config overrides under\n * `backend.database.plugin`.\n */\n forPlugin(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): DatabaseService {\n return this.impl.forPlugin(pluginId, deps);\n }\n}\n"],"names":["stringifyError","PgConnector","Sqlite3Connector","MysqlConnector"],"mappings":";;;;;;;AAmCA,SAAS,WAAW,QAAA,EAA0B;AAC5C,EAAA,OAAO,UAAU,QAAQ,CAAA,CAAA;AAC3B;AAgBO,MAAM,mBAAA,CAAoB;AAAA,EACd,MAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA,aAAA;AAAA,EACA,kBAAA;AAAA,EAEjB,WAAA,CACE,MAAA,EACA,UAAA,EACA,OAAA,EACA,aAAA,mBAA4C,IAAI,GAAA,EAAI,EACpD,kBAAA,mBAAkD,IAAI,GAAA,EAAI,EAC1D;AACA,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AACrB,IAAA,IAAA,CAAK,kBAAA,GAAqB,kBAAA;AAG1B,IAAA,IAAI,OAAA,EAAS,kBAAkB,MAAA,EAAW;AACxC,MAAA,OAAA,CAAQ,aAAA,CAAc,gBAAgB,YAAY;AAChD,QAAA,MAAM,KAAK,QAAA,CAAS,EAAE,MAAA,EAAQ,OAAA,CAAQ,YAAY,CAAA;AAAA,MACpD,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAA,CACE,UACA,IAAA,EAIiB;AACjB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,aAAA,CAAc,QAAQ,CAAA,CAAE,MAAA;AAC5C,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA;AACxC,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,kCAAA,EAAqC,MAAM,CAAA,wBAAA,EAA2B,QAAQ,CAAA,CAAA;AAAA,OAChF;AAAA,IACF;AACA,IAAA,MAAM,YAAY,MAAM,IAAA,CAAK,WAAA,CAAY,QAAA,EAAU,WAAW,IAAI,CAAA;AAElE,IAAA,MAAM,OACJ,IAAA,CAAK,OAAA,EAAS,UAAA,EAAY,IAAA,IAC1B,KAAK,MAAA,CAAO,kBAAA,CAAmB,CAAA,OAAA,EAAU,QAAQ,iBAAiB,CAAA,IAClE,IAAA,CAAK,MAAA,CAAO,kBAAA,CAAmB,gBAAgB,CAAA,IAC/C,KAAA;AAEF,IAAA,OAAO,EAAE,SAAA,EAAW,UAAA,EAAY,EAAE,MAAK,EAAE;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,SAAS,IAAA,EAAkD;AACvE,IAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,aAAA,CAAc,MAAM,CAAA;AACtD,IAAA,MAAM,OAAA,CAAQ,UAAA;AAAA,MACZ,SAAA,CAAU,GAAA,CAAI,OAAM,QAAA,KAAY;AAE9B,QAAA,aAAA,CAAc,IAAA,CAAK,kBAAA,CAAmB,GAAA,CAAI,QAAQ,CAAC,CAAA;AAEnD,QAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,aAAA,CAAc,IAAI,QAAQ,CAAA;AACxD,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,IAAI,UAAA,CAAW,MAAA,CAAO,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,EAAG;AAChD,YAAA;AAAA,UACF;AACA,UAAA,MAAM,UAAA,CAAW,OAAA,EAAQ,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AACnD,YAAA,IAAA,EAAM,MAAA,EAAQ,KAAA;AAAA,cACZ,CAAA,wCAAA,EAA2C,QAAQ,CAAA,EAAA,EAAKA,qBAAA;AAAA,gBACtD;AAAA,eACD,CAAA;AAAA,aACH;AAAA,UACF,CAAC,CAAA;AAAA,QACH;AAAA,MACF,CAAC;AAAA,KACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,cAAc,QAAA,EAGpB;AACA,IAAA,MAAM,YAAA,GAAe,KAAK,MAAA,CAAO,iBAAA;AAAA,MAC/B,CAAA,EAAG,UAAA,CAAW,QAAQ,CAAC,CAAA,OAAA;AAAA,KACzB;AAEA,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,QAAQ,CAAA;AACjD,IAAA,MAAM,SAAS,YAAA,IAAgB,UAAA;AAC/B,IAAA,OAAO;AAAA,MACL,MAAA;AAAA,MACA,YAAY,MAAA,KAAW;AAAA,KACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,WAAA,CACZ,QAAA,EACA,SAAA,EACA,IAAA,EAIe;AACf,IAAA,IAAI,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,QAAQ,CAAA,EAAG;AACpC,MAAA,OAAO,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,QAAQ,CAAA;AAAA,IACxC;AAEA,IAAA,MAAM,aAAA,GAAgB,SAAA,CAAU,SAAA,CAAU,QAAA,EAAU,IAAI,CAAA;AACxD,IAAA,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,QAAA,EAAU,aAAa,CAAA;AAE9C,IAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,MAAA,EAAQ;AACnC,MAAA,aAAA,CAAc,IAAA;AAAA,QAAK,YACjB,IAAA,CAAK,kBAAA,CAAmB,QAAA,EAAU,MAAA,EAAQ,KAAK,MAAM;AAAA,OACvD;AAAA,IACF;AAEA,IAAA,OAAO,aAAA;AAAA,EACT;AAAA,EAEQ,kBAAA,CACN,QAAA,EACA,MAAA,EACA,MAAA,EACM;AACN,IAAA,IAAI,mBAAA,GAAsB,KAAA;AAE1B,IAAA,IAAA,CAAK,kBAAA,CAAmB,GAAA;AAAA,MACtB,QAAA;AAAA,MACA,YAAY,MAAM;AAGhB,QAAA,MAAA,EAAQ,GAAA,CAAI,UAAU,CAAA,CAAE,IAAA;AAAA,UACtB,MAAM;AACJ,YAAA,mBAAA,GAAsB,KAAA;AAAA,UACxB,CAAA;AAAA,UACA,CAAC,KAAA,KAAmB;AAClB,YAAA,IAAI,CAAC,mBAAA,EAAqB;AACxB,cAAA,mBAAA,GAAsB,IAAA;AACtB,cAAA,MAAA,CAAO,IAAA;AAAA,gBACL,CAAA,qCAAA,EAAwC,QAAQ,CAAA,EAAA,EAAKA,qBAAA;AAAA,kBACnD;AAAA,iBACD,CAAA;AAAA,eACH;AAAA,YACF;AAAA,UACF;AAAA,SACF;AAAA,MACF,CAAA,EAAG,KAAK,GAAI;AAAA,KACd;AAAA,EACF;AACF;AAeO,MAAM,eAAA,CAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO3B,OAAO,UAAA,CACL,MAAA,EACA,OAAA,EACiB;AACjB,IAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,SAAA,CAAU,kBAAkB,CAAA;AAC1D,IAAA,MAAM,MAAA,GACJ,cAAA,CAAe,iBAAA,CAAkB,QAAQ,CAAA,IAAK,mBAAA;AAChD,IAAA,OAAO,IAAI,eAAA;AAAA,MACT,IAAI,mBAAA;AAAA,QACF,cAAA;AAAA,QACA;AAAA,UACE,EAAA,EAAI,IAAIC,oBAAA,CAAY,cAAA,EAAgB,MAAM,CAAA;AAAA,UAC1C,OAAA,EAAS,IAAIC,wBAAA,CAAiB,cAAc,CAAA;AAAA,UAC5C,gBAAA,EAAkB,IAAIA,wBAAA,CAAiB,cAAc,CAAA;AAAA,UACrD,KAAA,EAAO,IAAIC,oBAAA,CAAe,cAAA,EAAgB,MAAM,CAAA;AAAA,UAChD,MAAA,EAAQ,IAAIA,oBAAA,CAAe,cAAA,EAAgB,MAAM;AAAA,SACnD;AAAA,QACA;AAAA;AACF,KACF;AAAA,EACF;AAAA,EAEiB,IAAA;AAAA,EAET,YAAY,IAAA,EAA2B;AAC7C,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAA,CACE,UACA,IAAA,EAIiB;AACjB,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,QAAA,EAAU,IAAI,CAAA;AAAA,EAC3C;AACF;;;;;"}
1
+ {"version":3,"file":"DatabaseManager.cjs.js","sources":["../../../src/entrypoints/database/DatabaseManager.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 DatabaseService,\n LifecycleService,\n LoggerService,\n RootConfigService,\n RootLifecycleService,\n RootLoggerService,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { stringifyError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { MysqlConnector } from './connectors/mysql';\nimport { PgConnector } from './connectors/postgres';\nimport { Sqlite3Connector } from './connectors/sqlite3';\nimport { Connector } from './types';\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\n/**\n * Creation options for {@link DatabaseManager}.\n *\n * @public\n */\nexport type DatabaseManagerOptions = {\n migrations?: DatabaseService['migrations'];\n rootLogger?: RootLoggerService;\n rootLifecycle?: RootLifecycleService;\n};\n\n/**\n * Testable implementation class for {@link DatabaseManager} below.\n */\nexport class DatabaseManagerImpl {\n private readonly config: Config;\n private readonly connectors: Record<string, Connector>;\n private readonly options?: DatabaseManagerOptions;\n private readonly databaseCache: Map<string, Promise<Knex>>;\n private readonly keepaliveIntervals: Map<string, NodeJS.Timeout>;\n\n constructor(\n config: Config,\n connectors: Record<string, Connector>,\n options?: DatabaseManagerOptions,\n databaseCache: Map<string, Promise<Knex>> = new Map(),\n keepaliveIntervals: Map<string, NodeJS.Timeout> = new Map(),\n ) {\n this.config = config;\n this.connectors = connectors;\n this.options = options;\n this.databaseCache = databaseCache;\n this.keepaliveIntervals = keepaliveIntervals;\n // If a rootLifecycle service was provided, register a shutdown hook to\n // clean up any database connections.\n if (options?.rootLifecycle !== undefined) {\n options.rootLifecycle.addShutdownHook(async () => {\n await this.shutdown({ logger: options.rootLogger });\n });\n }\n }\n\n /**\n * Generates a DatabaseService for consumption by plugins.\n *\n * @param pluginId - The plugin that the database manager should be created for. Plugin names\n * should be unique as they are used to look up database config overrides under\n * `backend.database.plugin`.\n */\n forPlugin(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): DatabaseService {\n const client = this.getClientType(pluginId).client;\n const connector = this.connectors[client];\n if (!connector) {\n throw new Error(\n `Unsupported database client type '${client}' specified for plugin '${pluginId}'`,\n );\n }\n const getClient = () => this.getDatabase(pluginId, connector, deps);\n\n const skip =\n this.options?.migrations?.skip ??\n this.config.getOptionalBoolean(`plugin.${pluginId}.skipMigrations`) ??\n this.config.getOptionalBoolean('skipMigrations') ??\n false;\n\n return { getClient, migrations: { skip } };\n }\n\n /**\n * Destroys all known connections.\n */\n private async shutdown(deps?: { logger?: LoggerService }): Promise<void> {\n const pluginIds = Array.from(this.databaseCache.keys());\n await Promise.allSettled(\n pluginIds.map(async pluginId => {\n // We no longer need to keep connections alive.\n clearInterval(this.keepaliveIntervals.get(pluginId));\n\n const connection = await this.databaseCache.get(pluginId);\n if (connection) {\n if (connection.client.config.includes('sqlite3')) {\n return; // sqlite3 does not support destroy, it hangs\n }\n await connection.destroy().catch((error: unknown) => {\n deps?.logger?.error(\n `Problem closing database connection for ${pluginId}: ${stringifyError(\n error,\n )}`,\n );\n });\n }\n }),\n );\n\n const connectors = new Set(Object.values(this.connectors));\n await Promise.all(\n Array.from(connectors, async connector => {\n try {\n await connector.shutdown?.();\n } catch (error) {\n deps?.logger?.error(\n `Problem closing database connector: ${stringifyError(error)}`,\n );\n }\n }),\n );\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n /**\n * Provides a scoped Knex client for a plugin as per application config.\n *\n * @param pluginId - Plugin to get a Knex client for\n * @returns Promise which resolves to a scoped Knex database client for a\n * plugin\n */\n private async getDatabase(\n pluginId: string,\n connector: Connector,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n if (this.databaseCache.has(pluginId)) {\n return this.databaseCache.get(pluginId)!;\n }\n\n const clientPromise = connector.getClient(pluginId, deps);\n this.databaseCache.set(pluginId, clientPromise);\n\n if (process.env.NODE_ENV !== 'test') {\n clientPromise.then(client =>\n this.startKeepaliveLoop(pluginId, client, deps.logger),\n );\n }\n\n return clientPromise;\n }\n\n private startKeepaliveLoop(\n pluginId: string,\n client: Knex,\n logger: LoggerService,\n ): void {\n let lastKeepaliveFailed = false;\n\n this.keepaliveIntervals.set(\n pluginId,\n setInterval(() => {\n // During testing it can happen that the environment is torn down and\n // this client is `undefined`, but this interval is still run.\n client?.raw('select 1').then(\n () => {\n lastKeepaliveFailed = false;\n },\n (error: unknown) => {\n if (!lastKeepaliveFailed) {\n lastKeepaliveFailed = true;\n logger.warn(\n `Database keepalive failed for plugin ${pluginId}, ${stringifyError(\n error,\n )}`,\n );\n }\n },\n );\n }, 60 * 1000),\n );\n }\n}\n\n// NOTE: This class looks odd but is kept around for API compatibility reasons\n/**\n * Manages database connections for Backstage backend plugins.\n *\n * @public\n * @remarks\n *\n * The database manager allows the user to set connection and client settings on\n * a per pluginId basis by defining a database config block under\n * `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may\n * set `prefix` which is used to prefix generated database names if config is\n * not provided.\n */\nexport class DatabaseManager {\n /**\n * Creates a {@link DatabaseManager} from `backend.database` config.\n *\n * @param config - The loaded application configuration.\n * @param options - An optional configuration object.\n */\n static fromConfig(\n config: RootConfigService,\n options?: DatabaseManagerOptions,\n ): DatabaseManager {\n const databaseConfig = config.getConfig('backend.database');\n const prefix =\n databaseConfig.getOptionalString('prefix') || 'backstage_plugin_';\n return new DatabaseManager(\n new DatabaseManagerImpl(\n databaseConfig,\n {\n pg: new PgConnector(databaseConfig, prefix),\n sqlite3: new Sqlite3Connector(databaseConfig),\n 'better-sqlite3': new Sqlite3Connector(databaseConfig),\n mysql: new MysqlConnector(databaseConfig, prefix),\n mysql2: new MysqlConnector(databaseConfig, prefix),\n },\n options,\n ),\n );\n }\n\n private readonly impl: DatabaseManagerImpl;\n\n private constructor(impl: DatabaseManagerImpl) {\n this.impl = impl;\n }\n\n /**\n * Generates a DatabaseService for consumption by plugins.\n *\n * @param pluginId - The plugin that the database manager should be created for. Plugin names\n * should be unique as they are used to look up database config overrides under\n * `backend.database.plugin`.\n */\n forPlugin(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): DatabaseService {\n return this.impl.forPlugin(pluginId, deps);\n }\n}\n"],"names":["stringifyError","PgConnector","Sqlite3Connector","MysqlConnector"],"mappings":"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"}