@backstage/backend-defaults 0.5.1-next.0 → 0.5.1-next.2

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 (248) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/auth/package.json +1 -1
  3. package/cache/package.json +1 -1
  4. package/database/package.json +1 -1
  5. package/discovery/package.json +1 -1
  6. package/dist/CreateBackend.cjs.js +49 -0
  7. package/dist/CreateBackend.cjs.js.map +1 -0
  8. package/dist/PackageDiscoveryService.cjs.js +109 -0
  9. package/dist/PackageDiscoveryService.cjs.js.map +1 -0
  10. package/dist/auth.cjs.js +2 -996
  11. package/dist/auth.cjs.js.map +1 -1
  12. package/dist/cache.cjs.js +4 -204
  13. package/dist/cache.cjs.js.map +1 -1
  14. package/dist/database.cjs.js +4 -957
  15. package/dist/database.cjs.js.map +1 -1
  16. package/dist/database.d.ts +4 -1
  17. package/dist/discovery.cjs.js +4 -92
  18. package/dist/discovery.cjs.js.map +1 -1
  19. package/dist/discoveryFeatureLoader.cjs.js +19 -0
  20. package/dist/discoveryFeatureLoader.cjs.js.map +1 -0
  21. package/dist/entrypoints/auth/DefaultAuthService.cjs.js +130 -0
  22. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -0
  23. package/dist/entrypoints/auth/JwksClient.cjs.js +49 -0
  24. package/dist/entrypoints/auth/JwksClient.cjs.js.map +1 -0
  25. package/dist/entrypoints/auth/authServiceFactory.cjs.js +57 -0
  26. package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -0
  27. package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js +78 -0
  28. package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js.map +1 -0
  29. package/dist/entrypoints/auth/external/helpers.cjs.js +92 -0
  30. package/dist/entrypoints/auth/external/helpers.cjs.js.map +1 -0
  31. package/dist/entrypoints/auth/external/jwks.cjs.js +63 -0
  32. package/dist/entrypoints/auth/external/jwks.cjs.js.map +1 -0
  33. package/dist/entrypoints/auth/external/legacy.cjs.js +73 -0
  34. package/dist/entrypoints/auth/external/legacy.cjs.js.map +1 -0
  35. package/dist/entrypoints/auth/external/static.cjs.js +33 -0
  36. package/dist/entrypoints/auth/external/static.cjs.js.map +1 -0
  37. package/dist/{cjs/helpers-D2f1CG0o.cjs.js → entrypoints/auth/helpers.cjs.js} +1 -1
  38. package/dist/entrypoints/auth/helpers.cjs.js.map +1 -0
  39. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +147 -0
  40. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -0
  41. package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js +73 -0
  42. package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js.map +1 -0
  43. package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js +75 -0
  44. package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js.map +1 -0
  45. package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js +91 -0
  46. package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js.map +1 -0
  47. package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js +29 -0
  48. package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js.map +1 -0
  49. package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +110 -0
  50. package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -0
  51. package/dist/entrypoints/cache/CacheClient.cjs.js +50 -0
  52. package/dist/entrypoints/cache/CacheClient.cjs.js.map +1 -0
  53. package/dist/entrypoints/cache/CacheManager.cjs.js +147 -0
  54. package/dist/entrypoints/cache/CacheManager.cjs.js.map +1 -0
  55. package/dist/entrypoints/cache/cacheServiceFactory.cjs.js +22 -0
  56. package/dist/entrypoints/cache/cacheServiceFactory.cjs.js.map +1 -0
  57. package/dist/entrypoints/cache/types.cjs.js +10 -0
  58. package/dist/entrypoints/cache/types.cjs.js.map +1 -0
  59. package/dist/entrypoints/database/DatabaseManager.cjs.js +173 -0
  60. package/dist/entrypoints/database/DatabaseManager.cjs.js.map +1 -0
  61. package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js +14 -0
  62. package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js.map +1 -0
  63. package/dist/entrypoints/database/connectors/defaultSchemaOverride.cjs.js +12 -0
  64. package/dist/entrypoints/database/connectors/defaultSchemaOverride.cjs.js.map +1 -0
  65. package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js +10 -0
  66. package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js.map +1 -0
  67. package/dist/entrypoints/database/connectors/mysql.cjs.js +278 -0
  68. package/dist/entrypoints/database/connectors/mysql.cjs.js.map +1 -0
  69. package/dist/entrypoints/database/connectors/postgres.cjs.js +304 -0
  70. package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -0
  71. package/dist/entrypoints/database/connectors/sqlite3.cjs.js +251 -0
  72. package/dist/entrypoints/database/connectors/sqlite3.cjs.js.map +1 -0
  73. package/dist/entrypoints/database/databaseServiceFactory.cjs.js +36 -0
  74. package/dist/entrypoints/database/databaseServiceFactory.cjs.js.map +1 -0
  75. package/dist/entrypoints/discovery/HostDiscovery.cjs.js +86 -0
  76. package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -0
  77. package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js +17 -0
  78. package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -0
  79. package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js +192 -0
  80. package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js.map +1 -0
  81. package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js +19 -0
  82. package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js.map +1 -0
  83. package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js +26 -0
  84. package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js.map +1 -0
  85. package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js +63 -0
  86. package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js.map +1 -0
  87. package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js +52 -0
  88. package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js.map +1 -0
  89. package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js +48 -0
  90. package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -0
  91. package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js +88 -0
  92. package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js.map +1 -0
  93. package/dist/entrypoints/logger/loggerServiceFactory.cjs.js +17 -0
  94. package/dist/entrypoints/logger/loggerServiceFactory.cjs.js.map +1 -0
  95. package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js +22 -0
  96. package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js.map +1 -0
  97. package/dist/{cjs/createConfigSecretEnumerator-DShyoWWL.cjs.js → entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js} +1 -1
  98. package/dist/entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js.map +1 -0
  99. package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js +26 -0
  100. package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js.map +1 -0
  101. package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js +41 -0
  102. package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -0
  103. package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js +77 -0
  104. package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js.map +1 -0
  105. package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js +29 -0
  106. package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js.map +1 -0
  107. package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js +187 -0
  108. package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js.map +1 -0
  109. package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js +28 -0
  110. package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js.map +1 -0
  111. package/dist/{cjs/config-BDOwXIyo.cjs.js → entrypoints/rootHttpRouter/http/config.cjs.js} +1 -1
  112. package/dist/entrypoints/rootHttpRouter/http/config.cjs.js.map +1 -0
  113. package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js +88 -0
  114. package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js.map +1 -0
  115. package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js +130 -0
  116. package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js.map +1 -0
  117. package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js +51 -0
  118. package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js.map +1 -0
  119. package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js +62 -0
  120. package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js.map +1 -0
  121. package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +73 -0
  122. package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -0
  123. package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js +76 -0
  124. package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -0
  125. package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js +114 -0
  126. package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -0
  127. package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js +30 -0
  128. package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js.map +1 -0
  129. package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js +18 -0
  130. package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js.map +1 -0
  131. package/dist/entrypoints/scheduler/database/tables.cjs.js +8 -0
  132. package/dist/entrypoints/scheduler/database/tables.cjs.js.map +1 -0
  133. package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js +37 -0
  134. package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js.map +1 -0
  135. package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js +105 -0
  136. package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js.map +1 -0
  137. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js +138 -0
  138. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -0
  139. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js +59 -0
  140. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js.map +1 -0
  141. package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js +275 -0
  142. package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js.map +1 -0
  143. package/dist/entrypoints/scheduler/lib/types.cjs.js +60 -0
  144. package/dist/entrypoints/scheduler/lib/types.cjs.js.map +1 -0
  145. package/dist/entrypoints/scheduler/lib/util.cjs.js +66 -0
  146. package/dist/entrypoints/scheduler/lib/util.cjs.js.map +1 -0
  147. package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js +19 -0
  148. package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js.map +1 -0
  149. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js +274 -0
  150. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -0
  151. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js +261 -0
  152. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -0
  153. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +148 -0
  154. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -0
  155. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +174 -0
  156. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -0
  157. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +170 -0
  158. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -0
  159. package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js +182 -0
  160. package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js.map +1 -0
  161. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +132 -0
  162. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -0
  163. package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js +147 -0
  164. package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js.map +1 -0
  165. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +122 -0
  166. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -0
  167. package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js +226 -0
  168. package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js.map +1 -0
  169. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +277 -0
  170. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -0
  171. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js +129 -0
  172. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -0
  173. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js +120 -0
  174. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -0
  175. package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js +49 -0
  176. package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -0
  177. package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js +46 -0
  178. package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js.map +1 -0
  179. package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js +68 -0
  180. package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js.map +1 -0
  181. package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js +46 -0
  182. package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -0
  183. package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js +78 -0
  184. package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map +1 -0
  185. package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js +147 -0
  186. package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map +1 -0
  187. package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js +161 -0
  188. package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js.map +1 -0
  189. package/dist/entrypoints/urlReader/lib/tree/util.cjs.js +28 -0
  190. package/dist/entrypoints/urlReader/lib/tree/util.cjs.js.map +1 -0
  191. package/dist/entrypoints/urlReader/lib/util.cjs.js +11 -0
  192. package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -0
  193. package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js +29 -0
  194. package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js.map +1 -0
  195. package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js +59 -0
  196. package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -0
  197. package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js +17 -0
  198. package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js.map +1 -0
  199. package/dist/httpAuth.cjs.js +3 -187
  200. package/dist/httpAuth.cjs.js.map +1 -1
  201. package/dist/httpRouter.cjs.js +2 -166
  202. package/dist/httpRouter.cjs.js.map +1 -1
  203. package/dist/index.cjs.js +4 -160
  204. package/dist/index.cjs.js.map +1 -1
  205. package/dist/lib/escapeRegExp.cjs.js +8 -0
  206. package/dist/lib/escapeRegExp.cjs.js.map +1 -0
  207. package/dist/lifecycle.cjs.js +3 -58
  208. package/dist/lifecycle.cjs.js.map +1 -1
  209. package/dist/logger.cjs.js +3 -12
  210. package/dist/logger.cjs.js.map +1 -1
  211. package/dist/package.json.cjs.js +252 -0
  212. package/dist/package.json.cjs.js.map +1 -0
  213. package/dist/permissions.cjs.js +3 -17
  214. package/dist/permissions.cjs.js.map +1 -1
  215. package/dist/rootConfig.cjs.js +4 -22
  216. package/dist/rootConfig.cjs.js.map +1 -1
  217. package/dist/rootHealth.cjs.js +3 -35
  218. package/dist/rootHealth.cjs.js.map +1 -1
  219. package/dist/rootHttpRouter.cjs.js +15 -651
  220. package/dist/rootHttpRouter.cjs.js.map +1 -1
  221. package/dist/rootLifecycle.cjs.js +3 -70
  222. package/dist/rootLifecycle.cjs.js.map +1 -1
  223. package/dist/rootLogger.cjs.js +4 -137
  224. package/dist/rootLogger.cjs.js.map +1 -1
  225. package/dist/scheduler.cjs.js +4 -693
  226. package/dist/scheduler.cjs.js.map +1 -1
  227. package/dist/scheduler.d.ts +2 -1
  228. package/dist/urlReader.cjs.js +32 -2962
  229. package/dist/urlReader.cjs.js.map +1 -1
  230. package/dist/userInfo.cjs.js +2 -64
  231. package/dist/userInfo.cjs.js.map +1 -1
  232. package/httpAuth/package.json +1 -1
  233. package/httpRouter/package.json +1 -1
  234. package/lifecycle/package.json +1 -1
  235. package/logger/package.json +1 -1
  236. package/package.json +20 -20
  237. package/permissions/package.json +1 -1
  238. package/rootConfig/package.json +1 -1
  239. package/rootHealth/package.json +1 -1
  240. package/rootHttpRouter/package.json +1 -1
  241. package/rootLifecycle/package.json +1 -1
  242. package/rootLogger/package.json +1 -1
  243. package/scheduler/package.json +1 -1
  244. package/urlReader/package.json +1 -1
  245. package/userInfo/package.json +1 -1
  246. package/dist/cjs/config-BDOwXIyo.cjs.js.map +0 -1
  247. package/dist/cjs/createConfigSecretEnumerator-DShyoWWL.cjs.js.map +0 -1
  248. package/dist/cjs/helpers-D2f1CG0o.cjs.js.map +0 -1
@@ -0,0 +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';\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","JwksClient"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var luxon = require('luxon');
5
+
6
+ const MIGRATIONS_TABLE = "backstage_backend_public_keys__knex_migrations";
7
+ const TABLE = "backstage_backend_public_keys__keys";
8
+ function applyDatabaseMigrations(knex) {
9
+ const migrationsDir = backendPluginApi.resolvePackagePath(
10
+ "@backstage/backend-defaults",
11
+ "migrations/auth"
12
+ );
13
+ return knex.migrate.latest({
14
+ directory: migrationsDir,
15
+ tableName: MIGRATIONS_TABLE
16
+ });
17
+ }
18
+ class DatabaseKeyStore {
19
+ constructor(client, logger) {
20
+ this.client = client;
21
+ this.logger = logger;
22
+ }
23
+ static async create(options) {
24
+ const { database, logger } = options;
25
+ const client = await database.getClient();
26
+ if (!database.migrations?.skip) {
27
+ await applyDatabaseMigrations(client);
28
+ }
29
+ return new DatabaseKeyStore(client, logger);
30
+ }
31
+ async addKey(options) {
32
+ await this.client(TABLE).insert({
33
+ id: options.key.kid,
34
+ key: JSON.stringify(options.key),
35
+ expires_at: options.expiresAt.toISOString()
36
+ });
37
+ }
38
+ async listKeys() {
39
+ const rows = await this.client(TABLE).select();
40
+ const keys = rows.map((row) => ({
41
+ id: row.id,
42
+ key: JSON.parse(row.key),
43
+ expiresAt: new Date(row.expires_at)
44
+ }));
45
+ const validKeys = [];
46
+ const expiredKeys = [];
47
+ for (const key of keys) {
48
+ if (luxon.DateTime.fromJSDate(key.expiresAt) < luxon.DateTime.local()) {
49
+ expiredKeys.push(key);
50
+ } else {
51
+ validKeys.push(key);
52
+ }
53
+ }
54
+ if (expiredKeys.length > 0) {
55
+ const kids = expiredKeys.map(({ key }) => key.kid);
56
+ this.logger.info(
57
+ `Removing expired plugin service keys, '${kids.join("', '")}'`
58
+ );
59
+ this.client(TABLE).delete().whereIn("id", kids).catch((error) => {
60
+ this.logger.error(
61
+ "Failed to remove expired plugin service keys",
62
+ error
63
+ );
64
+ });
65
+ }
66
+ return { keys: validKeys };
67
+ }
68
+ }
69
+
70
+ exports.DatabaseKeyStore = DatabaseKeyStore;
71
+ exports.TABLE = TABLE;
72
+ exports.applyDatabaseMigrations = applyDatabaseMigrations;
73
+ //# sourceMappingURL=DatabaseKeyStore.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatabaseKeyStore.cjs.js","sources":["../../../../../src/entrypoints/auth/plugin/keys/DatabaseKeyStore.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 DatabaseService,\n LoggerService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { Knex } from 'knex';\nimport { DateTime } from 'luxon';\nimport { KeyStore } from './types';\n\nconst MIGRATIONS_TABLE = 'backstage_backend_public_keys__knex_migrations';\n\n/** @internal */\nexport const TABLE = 'backstage_backend_public_keys__keys';\n\ntype Row = {\n id: string;\n key: string;\n expires_at: string;\n};\n\nexport function applyDatabaseMigrations(knex: Knex): Promise<void> {\n const migrationsDir = resolvePackagePath(\n '@backstage/backend-defaults',\n 'migrations/auth',\n );\n\n return knex.migrate.latest({\n directory: migrationsDir,\n tableName: MIGRATIONS_TABLE,\n });\n}\n\n/** @internal */\nexport class DatabaseKeyStore implements KeyStore {\n static async create(options: {\n database: DatabaseService;\n logger: LoggerService;\n }) {\n const { database, logger } = options;\n\n const client = await database.getClient();\n if (!database.migrations?.skip) {\n await applyDatabaseMigrations(client);\n }\n return new DatabaseKeyStore(client, logger);\n }\n\n private constructor(\n private readonly client: Knex,\n private readonly logger: LoggerService,\n ) {}\n\n async addKey(options: {\n id: string;\n key: JsonObject & { kid: string };\n expiresAt: Date;\n }) {\n await this.client<Row>(TABLE).insert({\n id: options.key.kid,\n key: JSON.stringify(options.key),\n expires_at: options.expiresAt.toISOString(),\n });\n }\n\n async listKeys() {\n const rows = await this.client<Row>(TABLE).select();\n const keys = rows.map(row => ({\n id: row.id,\n key: JSON.parse(row.key),\n expiresAt: new Date(row.expires_at),\n }));\n\n const validKeys = [];\n const expiredKeys = [];\n\n for (const key of keys) {\n if (DateTime.fromJSDate(key.expiresAt) < DateTime.local()) {\n expiredKeys.push(key);\n } else {\n validKeys.push(key);\n }\n }\n\n // Lazily prune expired keys. This may cause duplicate removals if we have concurrent callers, but w/e\n if (expiredKeys.length > 0) {\n const kids = expiredKeys.map(({ key }) => key.kid);\n\n this.logger.info(\n `Removing expired plugin service keys, '${kids.join(\"', '\")}'`,\n );\n\n // We don't await this, just let it run in the background\n this.client<Row>(TABLE)\n .delete()\n .whereIn('id', kids)\n .catch(error => {\n this.logger.error(\n 'Failed to remove expired plugin service keys',\n error,\n );\n });\n }\n\n return { keys: validKeys };\n }\n}\n"],"names":["resolvePackagePath","DateTime"],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ var types = require('@backstage/types');
4
+ var jose = require('jose');
5
+ var uuid = require('uuid');
6
+ var DatabaseKeyStore = require('./DatabaseKeyStore.cjs.js');
7
+
8
+ const SECONDS_IN_MS = 1e3;
9
+ const KEY_EXPIRATION_MARGIN_FACTOR = 3;
10
+ class DatabasePluginKeySource {
11
+ constructor(keyStore, logger, keyDurationSeconds, algorithm) {
12
+ this.keyStore = keyStore;
13
+ this.logger = logger;
14
+ this.keyDurationSeconds = keyDurationSeconds;
15
+ this.algorithm = algorithm;
16
+ }
17
+ privateKeyPromise;
18
+ keyExpiry;
19
+ static async create(options) {
20
+ const keyStore = await DatabaseKeyStore.DatabaseKeyStore.create({
21
+ database: options.database,
22
+ logger: options.logger
23
+ });
24
+ return new DatabasePluginKeySource(
25
+ keyStore,
26
+ options.logger,
27
+ Math.round(types.durationToMilliseconds(options.keyDuration) / 1e3),
28
+ options.algorithm ?? "ES256"
29
+ );
30
+ }
31
+ async getPrivateSigningKey() {
32
+ if (this.privateKeyPromise) {
33
+ if (this.keyExpiry && this.keyExpiry.getTime() > Date.now()) {
34
+ return this.privateKeyPromise;
35
+ }
36
+ this.logger.info(`Signing key has expired, generating new key`);
37
+ delete this.privateKeyPromise;
38
+ }
39
+ this.keyExpiry = new Date(
40
+ Date.now() + this.keyDurationSeconds * SECONDS_IN_MS
41
+ );
42
+ const promise = (async () => {
43
+ const kid = uuid.v4();
44
+ const key = await jose.generateKeyPair(this.algorithm);
45
+ const publicKey = await jose.exportJWK(key.publicKey);
46
+ const privateKey = await jose.exportJWK(key.privateKey);
47
+ publicKey.kid = privateKey.kid = kid;
48
+ publicKey.alg = privateKey.alg = this.algorithm;
49
+ this.logger.info(`Created new signing key ${kid}`);
50
+ await this.keyStore.addKey({
51
+ id: kid,
52
+ key: publicKey,
53
+ expiresAt: new Date(
54
+ Date.now() + this.keyDurationSeconds * SECONDS_IN_MS * KEY_EXPIRATION_MARGIN_FACTOR
55
+ )
56
+ });
57
+ return privateKey;
58
+ })();
59
+ this.privateKeyPromise = promise;
60
+ try {
61
+ await promise;
62
+ } catch (error) {
63
+ this.logger.error(`Failed to generate new signing key, ${error}`);
64
+ delete this.keyExpiry;
65
+ delete this.privateKeyPromise;
66
+ }
67
+ return promise;
68
+ }
69
+ listKeys() {
70
+ return this.keyStore.listKeys();
71
+ }
72
+ }
73
+
74
+ exports.DatabasePluginKeySource = DatabasePluginKeySource;
75
+ //# sourceMappingURL=DatabasePluginKeySource.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatabasePluginKeySource.cjs.js","sources":["../../../../../src/entrypoints/auth/plugin/keys/DatabasePluginKeySource.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 { DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { JWK, exportJWK, generateKeyPair } from 'jose';\nimport { v4 as uuid } from 'uuid';\nimport { DatabaseKeyStore } from './DatabaseKeyStore';\nimport { InternalKey, KeyPayload, KeyStore } from './types';\nimport { PluginKeySource } from './types';\n\nconst SECONDS_IN_MS = 1000;\n\n/**\n * The margin for how many times longer we make the public key available\n * compared to how long we use the private key to sign new tokens.\n */\nconst KEY_EXPIRATION_MARGIN_FACTOR = 3;\n\nexport class DatabasePluginKeySource implements PluginKeySource {\n private privateKeyPromise?: Promise<JWK>;\n private keyExpiry?: Date;\n\n constructor(\n private readonly keyStore: KeyStore,\n private readonly logger: LoggerService,\n private readonly keyDurationSeconds: number,\n private readonly algorithm: string,\n ) {}\n\n public static async create(options: {\n logger: LoggerService;\n database: DatabaseService;\n keyDuration: HumanDuration;\n algorithm?: string;\n }): Promise<PluginKeySource> {\n const keyStore = await DatabaseKeyStore.create({\n database: options.database,\n logger: options.logger,\n });\n\n return new DatabasePluginKeySource(\n keyStore,\n options.logger,\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.algorithm ?? 'ES256',\n );\n }\n\n async getPrivateSigningKey(): Promise<JWK> {\n // Make sure that we only generate one key at a time\n if (this.privateKeyPromise) {\n if (this.keyExpiry && this.keyExpiry.getTime() > Date.now()) {\n return this.privateKeyPromise;\n }\n this.logger.info(`Signing key has expired, generating new key`);\n delete this.privateKeyPromise;\n }\n\n this.keyExpiry = new Date(\n Date.now() + this.keyDurationSeconds * SECONDS_IN_MS,\n );\n\n const promise = (async () => {\n // This generates a new signing key to be used to sign tokens until the next key rotation\n const kid = uuid();\n const key = await generateKeyPair(this.algorithm);\n const publicKey = await exportJWK(key.publicKey);\n const privateKey = await exportJWK(key.privateKey);\n publicKey.kid = privateKey.kid = kid;\n publicKey.alg = privateKey.alg = this.algorithm;\n\n // We're not allowed to use the key until it has been successfully stored\n // TODO: some token verification implementations aggressively cache the list of keys, and\n // don't attempt to fetch new ones even if they encounter an unknown kid. Therefore we\n // may want to keep using the existing key for some period of time until we switch to\n // the new one. This also needs to be implemented cross-service though, meaning new services\n // that boot up need to be able to grab an existing key to use for signing.\n this.logger.info(`Created new signing key ${kid}`);\n\n await this.keyStore.addKey({\n id: kid,\n key: publicKey as InternalKey,\n expiresAt: new Date(\n Date.now() +\n this.keyDurationSeconds *\n SECONDS_IN_MS *\n KEY_EXPIRATION_MARGIN_FACTOR,\n ),\n });\n\n // At this point we are allowed to start using the new key\n return privateKey;\n })();\n\n this.privateKeyPromise = promise;\n\n try {\n // If we fail to generate a new key, we need to clear the state so that\n // the next caller will try to generate another key.\n await promise;\n } catch (error) {\n this.logger.error(`Failed to generate new signing key, ${error}`);\n delete this.keyExpiry;\n delete this.privateKeyPromise;\n }\n\n return promise;\n }\n\n listKeys(): Promise<{ keys: KeyPayload[] }> {\n return this.keyStore.listKeys();\n }\n}\n"],"names":["DatabaseKeyStore","durationToMilliseconds","uuid","generateKeyPair","exportJWK"],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ var types = require('@backstage/types');
4
+ var fs = require('fs');
5
+ var jose = require('jose');
6
+
7
+ const DEFAULT_ALGORITHM = "ES256";
8
+ const SECONDS_IN_MS = 1e3;
9
+ class StaticConfigPluginKeySource {
10
+ constructor(keyPairs, keyDurationSeconds) {
11
+ this.keyPairs = keyPairs;
12
+ this.keyDurationSeconds = keyDurationSeconds;
13
+ }
14
+ static async create(options) {
15
+ const keyConfigs = options.sourceConfig.getConfigArray("static.keys").map((c) => {
16
+ const staticKeyConfig = {
17
+ publicKeyFile: c.getString("publicKeyFile"),
18
+ privateKeyFile: c.getOptionalString("privateKeyFile"),
19
+ keyId: c.getString("keyId"),
20
+ algorithm: c.getOptionalString("algorithm") ?? DEFAULT_ALGORITHM
21
+ };
22
+ return staticKeyConfig;
23
+ });
24
+ const keyPairs = await Promise.all(
25
+ keyConfigs.map(async (k) => await this.loadKeyPair(k))
26
+ );
27
+ if (keyPairs.length < 1) {
28
+ throw new Error(
29
+ "At least one key pair must be provided in static.keys, when the static key store type is used"
30
+ );
31
+ } else if (!keyPairs[0].privateKey) {
32
+ throw new Error(
33
+ "Private key for signing must be provided in the first key pair in static.keys, when the static key store type is used"
34
+ );
35
+ }
36
+ return new StaticConfigPluginKeySource(
37
+ keyPairs,
38
+ types.durationToMilliseconds(options.keyDuration) / SECONDS_IN_MS
39
+ );
40
+ }
41
+ async getPrivateSigningKey() {
42
+ return this.keyPairs[0].privateKey;
43
+ }
44
+ async listKeys() {
45
+ const keys = this.keyPairs.map((k) => this.keyPairToStoredKey(k));
46
+ return { keys };
47
+ }
48
+ static async loadKeyPair(options) {
49
+ const algorithm = options.algorithm;
50
+ const keyId = options.keyId;
51
+ const publicKey = await this.loadPublicKeyFromFile(
52
+ options.publicKeyFile,
53
+ keyId,
54
+ algorithm
55
+ );
56
+ const privateKey = options.privateKeyFile ? await this.loadPrivateKeyFromFile(
57
+ options.privateKeyFile,
58
+ keyId,
59
+ algorithm
60
+ ) : void 0;
61
+ return { publicKey, privateKey, keyId };
62
+ }
63
+ static async loadPublicKeyFromFile(path, keyId, algorithm) {
64
+ return this.loadKeyFromFile(path, keyId, algorithm, jose.importSPKI);
65
+ }
66
+ static async loadPrivateKeyFromFile(path, keyId, algorithm) {
67
+ return this.loadKeyFromFile(path, keyId, algorithm, jose.importPKCS8);
68
+ }
69
+ static async loadKeyFromFile(path, keyId, algorithm, importer) {
70
+ const content = await fs.promises.readFile(path, { encoding: "utf8", flag: "r" });
71
+ const key = await importer(content, algorithm);
72
+ const jwk = await jose.exportJWK(key);
73
+ jwk.kid = keyId;
74
+ jwk.alg = algorithm;
75
+ return jwk;
76
+ }
77
+ keyPairToStoredKey(keyPair) {
78
+ const publicKey = {
79
+ ...keyPair.publicKey,
80
+ kid: keyPair.keyId
81
+ };
82
+ return {
83
+ key: publicKey,
84
+ id: keyPair.keyId,
85
+ expiresAt: new Date(Date.now() + this.keyDurationSeconds * SECONDS_IN_MS)
86
+ };
87
+ }
88
+ }
89
+
90
+ exports.StaticConfigPluginKeySource = StaticConfigPluginKeySource;
91
+ //# sourceMappingURL=StaticConfigPluginKeySource.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaticConfigPluginKeySource.cjs.js","sources":["../../../../../src/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.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 { Config } from '@backstage/config';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { promises as fs } from 'fs';\nimport { JWK, exportJWK, importPKCS8, importSPKI } from 'jose';\nimport { KeyLike } from 'jose/dist/types/types';\nimport { KeyPayload } from './types';\nimport { PluginKeySource } from './types';\n\nexport type KeyPair = {\n publicKey: JWK;\n privateKey?: JWK;\n keyId: string;\n};\n\nexport type StaticKeyConfig = {\n publicKeyFile: string;\n privateKeyFile?: string;\n keyId: string;\n algorithm: string;\n};\n\nconst DEFAULT_ALGORITHM = 'ES256';\n\nconst SECONDS_IN_MS = 1000;\n\n/**\n * Key source that loads predefined public/private key pairs from disk.\n *\n * The private key should be represented using the PKCS#8 format,\n * while the public key should be in the SPKI format.\n *\n * @remarks\n *\n * You can generate a public and private key pair, using\n * openssl:\n *\n * Generate a private key using the ES256 algorithm\n * ```sh\n * openssl ecparam -name prime256v1 -genkey -out private.ec.key\n * ```\n * Convert it to PKCS#8 format\n * ```sh\n * openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private.ec.key -out private.key\n * ```\n * Extract the public key\n * ```sh\n * openssl ec -inform PEM -outform PEM -pubout -in private.key -out public.key\n * ```\n *\n * Provide the paths to private.key and public.key as the respective\n * private and public key paths in the `create` method.\n */\nexport class StaticConfigPluginKeySource implements PluginKeySource {\n private constructor(\n private readonly keyPairs: KeyPair[],\n private readonly keyDurationSeconds: number,\n ) {}\n\n public static async create(options: {\n sourceConfig: Config;\n keyDuration: HumanDuration;\n }): Promise<PluginKeySource> {\n const keyConfigs = options.sourceConfig\n .getConfigArray('static.keys')\n .map(c => {\n const staticKeyConfig: StaticKeyConfig = {\n publicKeyFile: c.getString('publicKeyFile'),\n privateKeyFile: c.getOptionalString('privateKeyFile'),\n keyId: c.getString('keyId'),\n algorithm: c.getOptionalString('algorithm') ?? DEFAULT_ALGORITHM,\n };\n\n return staticKeyConfig;\n });\n\n const keyPairs = await Promise.all(\n keyConfigs.map(async k => await this.loadKeyPair(k)),\n );\n\n if (keyPairs.length < 1) {\n throw new Error(\n 'At least one key pair must be provided in static.keys, when the static key store type is used',\n );\n } else if (!keyPairs[0].privateKey) {\n throw new Error(\n 'Private key for signing must be provided in the first key pair in static.keys, when the static key store type is used',\n );\n }\n\n return new StaticConfigPluginKeySource(\n keyPairs,\n durationToMilliseconds(options.keyDuration) / SECONDS_IN_MS,\n );\n }\n\n async getPrivateSigningKey(): Promise<JWK> {\n return this.keyPairs[0].privateKey!;\n }\n\n async listKeys(): Promise<{ keys: KeyPayload[] }> {\n const keys = this.keyPairs.map(k => this.keyPairToStoredKey(k));\n return { keys };\n }\n\n private static async loadKeyPair(options: StaticKeyConfig): Promise<KeyPair> {\n const algorithm = options.algorithm;\n const keyId = options.keyId;\n const publicKey = await this.loadPublicKeyFromFile(\n options.publicKeyFile,\n keyId,\n algorithm,\n );\n const privateKey = options.privateKeyFile\n ? await this.loadPrivateKeyFromFile(\n options.privateKeyFile,\n keyId,\n algorithm,\n )\n : undefined;\n\n return { publicKey, privateKey, keyId };\n }\n\n private static async loadPublicKeyFromFile(\n path: string,\n keyId: string,\n algorithm: string,\n ): Promise<JWK> {\n return this.loadKeyFromFile(path, keyId, algorithm, importSPKI);\n }\n\n private static async loadPrivateKeyFromFile(\n path: string,\n keyId: string,\n algorithm: string,\n ): Promise<JWK> {\n return this.loadKeyFromFile(path, keyId, algorithm, importPKCS8);\n }\n\n private static async loadKeyFromFile(\n path: string,\n keyId: string,\n algorithm: string,\n importer: (content: string, algorithm: string) => Promise<KeyLike>,\n ): Promise<JWK> {\n const content = await fs.readFile(path, { encoding: 'utf8', flag: 'r' });\n const key = await importer(content, algorithm);\n const jwk = await exportJWK(key);\n jwk.kid = keyId;\n jwk.alg = algorithm;\n\n return jwk;\n }\n\n private keyPairToStoredKey(keyPair: KeyPair): KeyPayload {\n const publicKey = {\n ...keyPair.publicKey,\n kid: keyPair.keyId,\n };\n\n return {\n key: publicKey,\n id: keyPair.keyId,\n expiresAt: new Date(Date.now() + this.keyDurationSeconds * SECONDS_IN_MS),\n };\n }\n}\n"],"names":["durationToMilliseconds","importSPKI","importPKCS8","fs","exportJWK"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ var DatabasePluginKeySource = require('./DatabasePluginKeySource.cjs.js');
4
+ var StaticConfigPluginKeySource = require('./StaticConfigPluginKeySource.cjs.js');
5
+
6
+ const CONFIG_ROOT_KEY = "backend.auth.pluginKeyStore";
7
+ async function createPluginKeySource(options) {
8
+ const keyStoreConfig = options.config.getOptionalConfig(CONFIG_ROOT_KEY);
9
+ const type = keyStoreConfig?.getOptionalString("type") ?? "database";
10
+ if (!keyStoreConfig || type === "database") {
11
+ return DatabasePluginKeySource.DatabasePluginKeySource.create({
12
+ database: options.database,
13
+ logger: options.logger,
14
+ keyDuration: options.keyDuration,
15
+ algorithm: options.algorithm
16
+ });
17
+ } else if (type === "static") {
18
+ return StaticConfigPluginKeySource.StaticConfigPluginKeySource.create({
19
+ sourceConfig: keyStoreConfig,
20
+ keyDuration: options.keyDuration
21
+ });
22
+ }
23
+ throw new Error(
24
+ `Unsupported config value ${CONFIG_ROOT_KEY}.type '${type}'; expected one of 'database', 'static'`
25
+ );
26
+ }
27
+
28
+ exports.createPluginKeySource = createPluginKeySource;
29
+ //# sourceMappingURL=createPluginKeySource.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createPluginKeySource.cjs.js","sources":["../../../../../src/entrypoints/auth/plugin/keys/createPluginKeySource.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 DatabaseService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { HumanDuration } from '@backstage/types';\nimport { DatabasePluginKeySource } from './DatabasePluginKeySource';\nimport { StaticConfigPluginKeySource } from './StaticConfigPluginKeySource';\nimport { PluginKeySource } from './types';\n\nconst CONFIG_ROOT_KEY = 'backend.auth.pluginKeyStore';\n\nexport async function createPluginKeySource(options: {\n config: RootConfigService;\n database: DatabaseService;\n logger: LoggerService;\n keyDuration: HumanDuration;\n algorithm?: string;\n}): Promise<PluginKeySource> {\n const keyStoreConfig = options.config.getOptionalConfig(CONFIG_ROOT_KEY);\n const type = keyStoreConfig?.getOptionalString('type') ?? 'database';\n\n if (!keyStoreConfig || type === 'database') {\n return DatabasePluginKeySource.create({\n database: options.database,\n logger: options.logger,\n keyDuration: options.keyDuration,\n algorithm: options.algorithm,\n });\n } else if (type === 'static') {\n return StaticConfigPluginKeySource.create({\n sourceConfig: keyStoreConfig,\n keyDuration: options.keyDuration,\n });\n }\n\n throw new Error(\n `Unsupported config value ${CONFIG_ROOT_KEY}.type '${type}'; expected one of 'database', 'static'`,\n );\n}\n"],"names":["DatabasePluginKeySource","StaticConfigPluginKeySource"],"mappings":";;;;;AA0BA,MAAM,eAAkB,GAAA,6BAAA,CAAA;AAExB,eAAsB,sBAAsB,OAMf,EAAA;AAC3B,EAAA,MAAM,cAAiB,GAAA,OAAA,CAAQ,MAAO,CAAA,iBAAA,CAAkB,eAAe,CAAA,CAAA;AACvE,EAAA,MAAM,IAAO,GAAA,cAAA,EAAgB,iBAAkB,CAAA,MAAM,CAAK,IAAA,UAAA,CAAA;AAE1D,EAAI,IAAA,CAAC,cAAkB,IAAA,IAAA,KAAS,UAAY,EAAA;AAC1C,IAAA,OAAOA,gDAAwB,MAAO,CAAA;AAAA,MACpC,UAAU,OAAQ,CAAA,QAAA;AAAA,MAClB,QAAQ,OAAQ,CAAA,MAAA;AAAA,MAChB,aAAa,OAAQ,CAAA,WAAA;AAAA,MACrB,WAAW,OAAQ,CAAA,SAAA;AAAA,KACpB,CAAA,CAAA;AAAA,GACH,MAAA,IAAW,SAAS,QAAU,EAAA;AAC5B,IAAA,OAAOC,wDAA4B,MAAO,CAAA;AAAA,MACxC,YAAc,EAAA,cAAA;AAAA,MACd,aAAa,OAAQ,CAAA,WAAA;AAAA,KACtB,CAAA,CAAA;AAAA,GACH;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA,yBAAA,EAA4B,eAAe,CAAA,OAAA,EAAU,IAAI,CAAA,uCAAA,CAAA;AAAA,GAC3D,CAAA;AACF;;;;"}
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var pluginAuthNode = require('@backstage/plugin-auth-node');
5
+ var jose = require('jose');
6
+ var JwksClient = require('../JwksClient.cjs.js');
7
+
8
+ class UserTokenHandler {
9
+ constructor(jwksClient) {
10
+ this.jwksClient = jwksClient;
11
+ }
12
+ static create(options) {
13
+ const jwksClient = new JwksClient.JwksClient(async () => {
14
+ const url = await options.discovery.getBaseUrl("auth");
15
+ return new URL(`${url}/.well-known/jwks.json`);
16
+ });
17
+ return new UserTokenHandler(jwksClient);
18
+ }
19
+ async verifyToken(token) {
20
+ const verifyOpts = this.#getTokenVerificationOptions(token);
21
+ if (!verifyOpts) {
22
+ return void 0;
23
+ }
24
+ await this.jwksClient.refreshKeyStore(token);
25
+ const { payload } = await jose.jwtVerify(
26
+ token,
27
+ this.jwksClient.getKey,
28
+ verifyOpts
29
+ ).catch((e) => {
30
+ throw new errors.AuthenticationError("Invalid token", e);
31
+ });
32
+ const userEntityRef = payload.sub;
33
+ if (!userEntityRef) {
34
+ throw new errors.AuthenticationError("No user sub found in token");
35
+ }
36
+ return { userEntityRef };
37
+ }
38
+ #getTokenVerificationOptions(token) {
39
+ try {
40
+ const { typ } = jose.decodeProtectedHeader(token);
41
+ if (typ === pluginAuthNode.tokenTypes.user.typParam) {
42
+ return {
43
+ requiredClaims: ["iat", "exp", "sub"],
44
+ typ: pluginAuthNode.tokenTypes.user.typParam
45
+ };
46
+ }
47
+ if (typ === pluginAuthNode.tokenTypes.limitedUser.typParam) {
48
+ return {
49
+ requiredClaims: ["iat", "exp", "sub"],
50
+ typ: pluginAuthNode.tokenTypes.limitedUser.typParam
51
+ };
52
+ }
53
+ const { aud } = jose.decodeJwt(token);
54
+ if (aud === pluginAuthNode.tokenTypes.user.audClaim) {
55
+ return {
56
+ audience: pluginAuthNode.tokenTypes.user.audClaim
57
+ };
58
+ }
59
+ } catch {
60
+ }
61
+ return void 0;
62
+ }
63
+ createLimitedUserToken(backstageToken) {
64
+ const [headerRaw, payloadRaw] = backstageToken.split(".");
65
+ const header = JSON.parse(
66
+ new TextDecoder().decode(jose.base64url.decode(headerRaw))
67
+ );
68
+ const payload = JSON.parse(
69
+ new TextDecoder().decode(jose.base64url.decode(payloadRaw))
70
+ );
71
+ const tokenType = header.typ;
72
+ if (!tokenType || tokenType === pluginAuthNode.tokenTypes.limitedUser.typParam) {
73
+ return { token: backstageToken, expiresAt: new Date(payload.exp * 1e3) };
74
+ }
75
+ if (tokenType !== pluginAuthNode.tokenTypes.user.typParam) {
76
+ throw new errors.AuthenticationError(
77
+ "Failed to create limited user token, invalid token type"
78
+ );
79
+ }
80
+ const limitedUserToken = [
81
+ jose.base64url.encode(
82
+ JSON.stringify({
83
+ typ: pluginAuthNode.tokenTypes.limitedUser.typParam,
84
+ alg: header.alg,
85
+ kid: header.kid
86
+ })
87
+ ),
88
+ jose.base64url.encode(
89
+ JSON.stringify({
90
+ sub: payload.sub,
91
+ iat: payload.iat,
92
+ exp: payload.exp
93
+ })
94
+ ),
95
+ payload.uip
96
+ ].join(".");
97
+ return { token: limitedUserToken, expiresAt: new Date(payload.exp * 1e3) };
98
+ }
99
+ isLimitedUserToken(token) {
100
+ try {
101
+ const { typ } = jose.decodeProtectedHeader(token);
102
+ return typ === pluginAuthNode.tokenTypes.limitedUser.typParam;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+ }
108
+
109
+ exports.UserTokenHandler = UserTokenHandler;
110
+ //# sourceMappingURL=UserTokenHandler.cjs.js.map
@@ -0,0 +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 } 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: { discovery: DiscoveryService }): 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);\n }\n\n constructor(private readonly jwksClient: JwksClient) {}\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 throw new AuthenticationError('Invalid token', e);\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 // 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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"}
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var types = require('./types.cjs.js');
5
+
6
+ class DefaultCacheClient {
7
+ #client;
8
+ #clientFactory;
9
+ #options;
10
+ constructor(client, clientFactory, options) {
11
+ this.#client = client;
12
+ this.#clientFactory = clientFactory;
13
+ this.#options = options;
14
+ }
15
+ async get(key) {
16
+ const k = this.getNormalizedKey(key);
17
+ const value = await this.#client.get(k);
18
+ return value;
19
+ }
20
+ async set(key, value, opts = {}) {
21
+ const k = this.getNormalizedKey(key);
22
+ const ttl = opts.ttl !== void 0 ? types.ttlToMilliseconds(opts.ttl) : void 0;
23
+ await this.#client.set(k, value, ttl);
24
+ }
25
+ async delete(key) {
26
+ const k = this.getNormalizedKey(key);
27
+ await this.#client.delete(k);
28
+ }
29
+ withOptions(options) {
30
+ const newOptions = { ...this.#options, ...options };
31
+ return new DefaultCacheClient(
32
+ this.#clientFactory(newOptions),
33
+ this.#clientFactory,
34
+ newOptions
35
+ );
36
+ }
37
+ /**
38
+ * Ensures keys are well-formed for any/all cache stores.
39
+ */
40
+ getNormalizedKey(candidateKey) {
41
+ const wellFormedKey = Buffer.from(candidateKey).toString("base64");
42
+ if (wellFormedKey.length < 200) {
43
+ return wellFormedKey;
44
+ }
45
+ return crypto.createHash("sha256").update(candidateKey).digest("base64");
46
+ }
47
+ }
48
+
49
+ exports.DefaultCacheClient = DefaultCacheClient;
50
+ //# sourceMappingURL=CacheClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CacheClient.cjs.js","sources":["../../../src/entrypoints/cache/CacheClient.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 CacheServiceSetOptions,\n} from '@backstage/backend-plugin-api';\nimport { JsonValue } from '@backstage/types';\nimport { createHash } from 'crypto';\nimport Keyv from 'keyv';\nimport { ttlToMilliseconds } from './types';\n\nexport type CacheClientFactory = (options: CacheServiceOptions) => Keyv;\n\n/**\n * A basic, concrete implementation of the CacheService, suitable for almost\n * all uses in Backstage.\n */\nexport class DefaultCacheClient implements CacheService {\n #client: Keyv;\n #clientFactory: CacheClientFactory;\n #options: CacheServiceOptions;\n\n constructor(\n client: Keyv,\n clientFactory: CacheClientFactory,\n options: CacheServiceOptions,\n ) {\n this.#client = client;\n this.#clientFactory = clientFactory;\n this.#options = options;\n }\n\n async get<TValue extends JsonValue>(\n key: string,\n ): Promise<TValue | undefined> {\n const k = this.getNormalizedKey(key);\n const value = await this.#client.get(k);\n return value as TValue | undefined;\n }\n\n async set(\n key: string,\n value: JsonValue,\n opts: CacheServiceSetOptions = {},\n ): Promise<void> {\n const k = this.getNormalizedKey(key);\n const ttl =\n opts.ttl !== undefined ? ttlToMilliseconds(opts.ttl) : undefined;\n await this.#client.set(k, value, ttl);\n }\n\n async delete(key: string): Promise<void> {\n const k = this.getNormalizedKey(key);\n await this.#client.delete(k);\n }\n\n withOptions(options: CacheServiceOptions): CacheService {\n const newOptions = { ...this.#options, ...options };\n return new DefaultCacheClient(\n this.#clientFactory(newOptions),\n this.#clientFactory,\n newOptions,\n );\n }\n\n /**\n * Ensures keys are well-formed for any/all cache stores.\n */\n private getNormalizedKey(candidateKey: string): string {\n // Remove potentially invalid characters.\n const wellFormedKey = Buffer.from(candidateKey).toString('base64');\n\n // Memcache in particular doesn't do well with keys > 250 bytes.\n // Padded because a plugin ID is also prepended to the key.\n if (wellFormedKey.length < 200) {\n return wellFormedKey;\n }\n\n return createHash('sha256').update(candidateKey).digest('base64');\n }\n}\n"],"names":["ttlToMilliseconds","createHash"],"mappings":";;;;;AAgCO,MAAM,kBAA2C,CAAA;AAAA,EACtD,OAAA,CAAA;AAAA,EACA,cAAA,CAAA;AAAA,EACA,QAAA,CAAA;AAAA,EAEA,WAAA,CACE,MACA,EAAA,aAAA,EACA,OACA,EAAA;AACA,IAAA,IAAA,CAAK,OAAU,GAAA,MAAA,CAAA;AACf,IAAA,IAAA,CAAK,cAAiB,GAAA,aAAA,CAAA;AACtB,IAAA,IAAA,CAAK,QAAW,GAAA,OAAA,CAAA;AAAA,GAClB;AAAA,EAEA,MAAM,IACJ,GAC6B,EAAA;AAC7B,IAAM,MAAA,CAAA,GAAI,IAAK,CAAA,gBAAA,CAAiB,GAAG,CAAA,CAAA;AACnC,IAAA,MAAM,KAAQ,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA;AACtC,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAAA,EAEA,MAAM,GACJ,CAAA,GAAA,EACA,KACA,EAAA,IAAA,GAA+B,EAChB,EAAA;AACf,IAAM,MAAA,CAAA,GAAI,IAAK,CAAA,gBAAA,CAAiB,GAAG,CAAA,CAAA;AACnC,IAAA,MAAM,MACJ,IAAK,CAAA,GAAA,KAAQ,SAAYA,uBAAkB,CAAA,IAAA,CAAK,GAAG,CAAI,GAAA,KAAA,CAAA,CAAA;AACzD,IAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,GAAI,CAAA,CAAA,EAAG,OAAO,GAAG,CAAA,CAAA;AAAA,GACtC;AAAA,EAEA,MAAM,OAAO,GAA4B,EAAA;AACvC,IAAM,MAAA,CAAA,GAAI,IAAK,CAAA,gBAAA,CAAiB,GAAG,CAAA,CAAA;AACnC,IAAM,MAAA,IAAA,CAAK,OAAQ,CAAA,MAAA,CAAO,CAAC,CAAA,CAAA;AAAA,GAC7B;AAAA,EAEA,YAAY,OAA4C,EAAA;AACtD,IAAA,MAAM,aAAa,EAAE,GAAG,IAAK,CAAA,QAAA,EAAU,GAAG,OAAQ,EAAA,CAAA;AAClD,IAAA,OAAO,IAAI,kBAAA;AAAA,MACT,IAAA,CAAK,eAAe,UAAU,CAAA;AAAA,MAC9B,IAAK,CAAA,cAAA;AAAA,MACL,UAAA;AAAA,KACF,CAAA;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,YAA8B,EAAA;AAErD,IAAA,MAAM,gBAAgB,MAAO,CAAA,IAAA,CAAK,YAAY,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AAIjE,IAAI,IAAA,aAAA,CAAc,SAAS,GAAK,EAAA;AAC9B,MAAO,OAAA,aAAA,CAAA;AAAA,KACT;AAEA,IAAA,OAAOC,kBAAW,QAAQ,CAAA,CAAE,OAAO,YAAY,CAAA,CAAE,OAAO,QAAQ,CAAA,CAAA;AAAA,GAClE;AACF;;;;"}