@backstage/backend-defaults 0.18.0 → 0.18.1-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 (126) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/CreateBackend.cjs.js.map +1 -1
  3. package/dist/PackageDiscoveryService.cjs.js.map +1 -1
  4. package/dist/alpha/entrypoints/actions/DefaultActionsService.cjs.js.map +1 -1
  5. package/dist/alpha/entrypoints/actions/actionsServiceFactory.cjs.js.map +1 -1
  6. package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js.map +1 -1
  7. package/dist/alpha/entrypoints/actionsRegistry/actionFilters.cjs.js.map +1 -1
  8. package/dist/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.cjs.js.map +1 -1
  9. package/dist/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.cjs.js.map +1 -1
  10. package/dist/alpha/entrypoints/metrics/DefaultMetricsService.cjs.js.map +1 -1
  11. package/dist/alpha/entrypoints/metrics/metricsServiceFactory.cjs.js.map +1 -1
  12. package/dist/alpha/entrypoints/tracing/DefaultTracingService.cjs.js.map +1 -1
  13. package/dist/alpha/entrypoints/tracing/tracingServiceFactory.cjs.js.map +1 -1
  14. package/dist/discoveryFeatureLoader.cjs.js.map +1 -1
  15. package/dist/entrypoints/auditor/DefaultAuditorService.cjs.js.map +1 -1
  16. package/dist/entrypoints/auditor/WinstonRootAuditorService.cjs.js.map +1 -1
  17. package/dist/entrypoints/auditor/auditorServiceFactory.cjs.js.map +1 -1
  18. package/dist/entrypoints/auditor/utils.cjs.js.map +1 -1
  19. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
  20. package/dist/entrypoints/auth/JwksClient.cjs.js.map +1 -1
  21. package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -1
  22. package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js.map +1 -1
  23. package/dist/entrypoints/auth/external/helpers.cjs.js.map +1 -1
  24. package/dist/entrypoints/auth/external/jwks.cjs.js.map +1 -1
  25. package/dist/entrypoints/auth/external/legacy.cjs.js.map +1 -1
  26. package/dist/entrypoints/auth/external/static.cjs.js.map +1 -1
  27. package/dist/entrypoints/auth/helpers.cjs.js.map +1 -1
  28. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
  29. package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js.map +1 -1
  30. package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js.map +1 -1
  31. package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js.map +1 -1
  32. package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js.map +1 -1
  33. package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -1
  34. package/dist/entrypoints/cache/CacheClient.cjs.js.map +1 -1
  35. package/dist/entrypoints/cache/CacheManager.cjs.js.map +1 -1
  36. package/dist/entrypoints/cache/cacheServiceFactory.cjs.js.map +1 -1
  37. package/dist/entrypoints/cache/providers/infinispan/InfinispanKeyvStore.cjs.js.map +1 -1
  38. package/dist/entrypoints/cache/providers/infinispan/InfinispanOptionsMapper.cjs.js.map +1 -1
  39. package/dist/entrypoints/cache/types.cjs.js.map +1 -1
  40. package/dist/entrypoints/database/DatabaseManager.cjs.js.map +1 -1
  41. package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js.map +1 -1
  42. package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js.map +1 -1
  43. package/dist/entrypoints/database/connectors/mysql.cjs.js.map +1 -1
  44. package/dist/entrypoints/database/connectors/postgres.cjs.js +2 -0
  45. package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -1
  46. package/dist/entrypoints/database/connectors/sqlite3.cjs.js.map +1 -1
  47. package/dist/entrypoints/database/databaseServiceFactory.cjs.js.map +1 -1
  48. package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -1
  49. package/dist/entrypoints/discovery/SrvResolvers.cjs.js.map +1 -1
  50. package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -1
  51. package/dist/entrypoints/discovery/parsing.cjs.js.map +1 -1
  52. package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js.map +1 -1
  53. package/dist/entrypoints/httpRouter/http/createAuthIntegrationRouter.cjs.js.map +1 -1
  54. package/dist/entrypoints/httpRouter/http/createCookieAuthRefreshMiddleware.cjs.js.map +1 -1
  55. package/dist/entrypoints/httpRouter/http/createCredentialsBarrier.cjs.js.map +1 -1
  56. package/dist/entrypoints/httpRouter/http/createLifecycleMiddleware.cjs.js.map +1 -1
  57. package/dist/entrypoints/httpRouter/http/createRateLimitMiddleware.cjs.js.map +1 -1
  58. package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -1
  59. package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js.map +1 -1
  60. package/dist/entrypoints/logger/loggerServiceFactory.cjs.js.map +1 -1
  61. package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js.map +1 -1
  62. package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js.map +1 -1
  63. package/dist/entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js.map +1 -1
  64. package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js.map +1 -1
  65. package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -1
  66. package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js.map +1 -1
  67. package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js.map +1 -1
  68. package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js.map +1 -1
  69. package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js.map +1 -1
  70. package/dist/entrypoints/rootHttpRouter/http/config.cjs.js.map +1 -1
  71. package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js.map +1 -1
  72. package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js.map +1 -1
  73. package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js.map +1 -1
  74. package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js.map +1 -1
  75. package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -1
  76. package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -1
  77. package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -1
  78. package/dist/entrypoints/rootLogger/config.cjs.js.map +1 -1
  79. package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js.map +1 -1
  80. package/dist/entrypoints/rootLogger/types.cjs.js.map +1 -1
  81. package/dist/entrypoints/rootLogger/utils.cjs.js.map +1 -1
  82. package/dist/entrypoints/rootSystemMetadata/lib/DefaultRootSystemMetadataService.cjs.js.map +1 -1
  83. package/dist/entrypoints/rootSystemMetadata/rootSystemMetadataServiceFactory.cjs.js.map +1 -1
  84. package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js.map +1 -1
  85. package/dist/entrypoints/scheduler/database/tables.cjs.js.map +1 -1
  86. package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js.map +1 -1
  87. package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js.map +1 -1
  88. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -1
  89. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js.map +1 -1
  90. package/dist/entrypoints/scheduler/lib/TaskStatePoller.cjs.js.map +1 -1
  91. package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js.map +1 -1
  92. package/dist/entrypoints/scheduler/lib/types.cjs.js.map +1 -1
  93. package/dist/entrypoints/scheduler/lib/util.cjs.js.map +1 -1
  94. package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js.map +1 -1
  95. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -1
  96. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -1
  97. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -1
  98. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -1
  99. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -1
  100. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
  101. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
  102. package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js.map +1 -1
  103. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -1
  104. package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js.map +1 -1
  105. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
  106. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -1
  107. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -1
  108. package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -1
  109. package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js.map +1 -1
  110. package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js.map +1 -1
  111. package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -1
  112. package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map +1 -1
  113. package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map +1 -1
  114. package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js.map +1 -1
  115. package/dist/entrypoints/urlReader/lib/tree/util.cjs.js.map +1 -1
  116. package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -1
  117. package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js.map +1 -1
  118. package/dist/entrypoints/userInfo/CachedUserInfoService.cjs.js.map +1 -1
  119. package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -1
  120. package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js.map +1 -1
  121. package/dist/lib/RateLimitStoreFactory.cjs.js.map +1 -1
  122. package/dist/lib/escapeRegExp.cjs.js.map +1 -1
  123. package/dist/lib/rateLimitMiddleware.cjs.js.map +1 -1
  124. package/dist/package.json.cjs.js +1 -1
  125. package/dist/package.json.cjs.js.map +1 -1
  126. package/package.json +17 -17
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs.js","sources":["../../../src/entrypoints/auditor/utils.ts"],"sourcesContent":["/*\n * Copyright 2025 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 type { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { z } from 'zod/v4';\n\nconst CONFIG_ROOT_KEY = 'backend.auditor';\n\nconst logLevel = z.enum(['debug', 'info', 'warn', 'error']);\n\nconst severityLogLevelMappingsSchema = z.object({\n low: logLevel.default('debug'),\n medium: logLevel.default('info'),\n high: logLevel.default('info'),\n critical: logLevel.default('info'),\n});\n\ntype SeverityLogLevelMappings = z.infer<typeof severityLogLevelMappingsSchema>;\n\n/**\n * Gets the `backend.auditor.severityLogLevelMappings` configuration.\n *\n * @param config - The root Backstage {@link @backstage/config#Config} object.\n * @returns The validated severity-to-log-level mappings.\n * @throws error - {@link @backstage/errors#InputError} if the mapping configuration is invalid.\n */\nexport function getSeverityLogLevelMappings(\n config: Config,\n): SeverityLogLevelMappings {\n const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY);\n\n const input = {\n low: auditorConfig?.getOptionalString('severityLogLevelMappings.low'),\n medium: auditorConfig?.getOptionalString('severityLogLevelMappings.medium'),\n high: auditorConfig?.getOptionalString('severityLogLevelMappings.high'),\n critical: auditorConfig?.getOptionalString(\n 'severityLogLevelMappings.critical',\n ),\n };\n\n const parsed = severityLogLevelMappingsSchema.safeParse(input);\n\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n const key = issue.path[0] as keyof typeof input;\n const receivedValue = input[key];\n throw new InputError(\n `The configuration value for '${CONFIG_ROOT_KEY}.severityLogLevelMappings.${key}' was given an invalid value: '${receivedValue}'. Expected one of the following valid values: '${logLevel.options.join(\n ', ',\n )}'.`,\n );\n }\n\n return parsed.data;\n}\n"],"names":["z","InputError"],"mappings":";;;;;AAoBA,MAAM,eAAA,GAAkB,iBAAA;AAExB,MAAM,QAAA,GAAWA,KAAE,IAAA,CAAK,CAAC,SAAS,MAAA,EAAQ,MAAA,EAAQ,OAAO,CAAC,CAAA;AAE1D,MAAM,8BAAA,GAAiCA,KAAE,MAAA,CAAO;AAAA,EAC9C,GAAA,EAAK,QAAA,CAAS,OAAA,CAAQ,OAAO,CAAA;AAAA,EAC7B,MAAA,EAAQ,QAAA,CAAS,OAAA,CAAQ,MAAM,CAAA;AAAA,EAC/B,IAAA,EAAM,QAAA,CAAS,OAAA,CAAQ,MAAM,CAAA;AAAA,EAC7B,QAAA,EAAU,QAAA,CAAS,OAAA,CAAQ,MAAM;AACnC,CAAC,CAAA;AAWM,SAAS,4BACd,MAAA,EAC0B;AAC1B,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,iBAAA,CAAkB,eAAe,CAAA;AAE9D,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,GAAA,EAAK,aAAA,EAAe,iBAAA,CAAkB,8BAA8B,CAAA;AAAA,IACpE,MAAA,EAAQ,aAAA,EAAe,iBAAA,CAAkB,iCAAiC,CAAA;AAAA,IAC1E,IAAA,EAAM,aAAA,EAAe,iBAAA,CAAkB,+BAA+B,CAAA;AAAA,IACtE,UAAU,aAAA,EAAe,iBAAA;AAAA,MACvB;AAAA;AACF,GACF;AAEA,EAAA,MAAM,MAAA,GAAS,8BAAA,CAA+B,SAAA,CAAU,KAAK,CAAA;AAE7D,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA;AACnC,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA;AACxB,IAAA,MAAM,aAAA,GAAgB,MAAM,GAAG,CAAA;AAC/B,IAAA,MAAM,IAAIC,iBAAA;AAAA,MACR,CAAA,6BAAA,EAAgC,eAAe,CAAA,0BAAA,EAA6B,GAAG,kCAAkC,aAAa,CAAA,gDAAA,EAAmD,SAAS,OAAA,CAAQ,IAAA;AAAA,QAChM;AAAA,OACD,CAAA,EAAA;AAAA,KACH;AAAA,EACF;AAEA,EAAA,OAAO,MAAA,CAAO,IAAA;AAChB;;;;"}
1
+ {"version":3,"file":"utils.cjs.js","sources":["../../../src/entrypoints/auditor/utils.ts"],"sourcesContent":["/*\n * Copyright 2025 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 type { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { z } from 'zod/v4';\n\nconst CONFIG_ROOT_KEY = 'backend.auditor';\n\nconst logLevel = z.enum(['debug', 'info', 'warn', 'error']);\n\nconst severityLogLevelMappingsSchema = z.object({\n low: logLevel.default('debug'),\n medium: logLevel.default('info'),\n high: logLevel.default('info'),\n critical: logLevel.default('info'),\n});\n\ntype SeverityLogLevelMappings = z.infer<typeof severityLogLevelMappingsSchema>;\n\n/**\n * Gets the `backend.auditor.severityLogLevelMappings` configuration.\n *\n * @param config - The root Backstage {@link @backstage/config#Config} object.\n * @returns The validated severity-to-log-level mappings.\n * @throws error - {@link @backstage/errors#InputError} if the mapping configuration is invalid.\n */\nexport function getSeverityLogLevelMappings(\n config: Config,\n): SeverityLogLevelMappings {\n const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY);\n\n const input = {\n low: auditorConfig?.getOptionalString('severityLogLevelMappings.low'),\n medium: auditorConfig?.getOptionalString('severityLogLevelMappings.medium'),\n high: auditorConfig?.getOptionalString('severityLogLevelMappings.high'),\n critical: auditorConfig?.getOptionalString(\n 'severityLogLevelMappings.critical',\n ),\n };\n\n const parsed = severityLogLevelMappingsSchema.safeParse(input);\n\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n const key = issue.path[0] as keyof typeof input;\n const receivedValue = input[key];\n throw new InputError(\n `The configuration value for '${CONFIG_ROOT_KEY}.severityLogLevelMappings.${key}' was given an invalid value: '${receivedValue}'. Expected one of the following valid values: '${logLevel.options.join(\n ', ',\n )}'.`,\n );\n }\n\n return parsed.data;\n}\n"],"names":["z","InputError"],"mappings":";;;;;AAoBA,MAAM,eAAA,GAAkB,iBAAA;AAExB,MAAM,QAAA,GAAWA,KAAE,IAAA,CAAK,CAAC,SAAS,MAAA,EAAQ,MAAA,EAAQ,OAAO,CAAC,CAAA;AAE1D,MAAM,8BAAA,GAAiCA,KAAE,MAAA,CAAO;AAAA,EAC9C,GAAA,EAAK,QAAA,CAAS,OAAA,CAAQ,OAAO,CAAA;AAAA,EAC7B,MAAA,EAAQ,QAAA,CAAS,OAAA,CAAQ,MAAM,CAAA;AAAA,EAC/B,IAAA,EAAM,QAAA,CAAS,OAAA,CAAQ,MAAM,CAAA;AAAA,EAC7B,QAAA,EAAU,QAAA,CAAS,OAAA,CAAQ,MAAM;AACnC,CAAC,CAAA;AAWM,SAAS,4BACd,MAAA,EAC0B;AAC1B,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,iBAAA,CAAkB,eAAe,CAAA;AAE9D,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,GAAA,EAAK,aAAA,EAAe,iBAAA,CAAkB,8BAA8B,CAAA;AAAA,IACpE,MAAA,EAAQ,aAAA,EAAe,iBAAA,CAAkB,iCAAiC,CAAA;AAAA,IAC1E,IAAA,EAAM,aAAA,EAAe,iBAAA,CAAkB,+BAA+B,CAAA;AAAA,IACtE,UAAU,aAAA,EAAe,iBAAA;AAAA,MACvB;AAAA;AACF,GACF;AAEA,EAAA,MAAM,MAAA,GAAS,8BAAA,CAA+B,SAAA,CAAU,KAAK,CAAA;AAE7D,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA;AACnC,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA;AACxB,IAAA,MAAM,aAAA,GAAgB,MAAM,GAAG,CAAA;AAC/B,IAAA,MAAM,IAAIC,iBAAA;AAAA,MACR,CAAA,6BAAA,EAAgC,eAAe,CAAA,0BAAA,EAA6B,GAAG,kCAAkC,aAAa,CAAA,gDAAA,EAAmD,SAAS,OAAA,CAAQ,IAAA;AAAA,QAChM;AAAA,OACD,CAAA,EAAA;AAAA,KACH;AAAA,EACF;AAEA,EAAA,OAAO,MAAA,CAAO,IAAA;AAChB;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalAuthTokenHandler } from './external/ExternalAuthTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n private readonly userTokenHandler: UserTokenHandler;\n private readonly pluginTokenHandler: PluginTokenHandler;\n private readonly externalTokenHandler: ExternalAuthTokenHandler;\n private readonly pluginId: string;\n private readonly disableDefaultAuthPolicy: boolean;\n private readonly pluginKeySource: PluginKeySource;\n\n constructor(\n userTokenHandler: UserTokenHandler,\n pluginTokenHandler: PluginTokenHandler,\n externalTokenHandler: ExternalAuthTokenHandler,\n pluginId: string,\n disableDefaultAuthPolicy: boolean,\n pluginKeySource: PluginKeySource,\n ) {\n this.userTokenHandler = userTokenHandler;\n this.pluginTokenHandler = pluginTokenHandler;\n this.externalTokenHandler = externalTokenHandler;\n this.pluginId = pluginId;\n this.disableDefaultAuthPolicy = disableDefaultAuthPolicy;\n this.pluginKeySource = pluginKeySource;\n }\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n externalResult.allAccessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existence.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service': {\n const servicePrincipal =\n internalForward.principal as BackstageServicePrincipal;\n if (servicePrincipal.accessRestrictions) {\n const targetRestrictions =\n internalForward.allAccessRestrictions?.get(targetPluginId);\n if (!targetRestrictions) {\n throw new NotAllowedError(\n `Access to target plugin '${targetPluginId}' is not included in token's access restrictions`,\n );\n }\n if (\n targetRestrictions.permissionNames?.length ||\n targetRestrictions.permissionAttributes\n ) {\n throw new NotAllowedError(\n `Access to target plugin '${targetPluginId}' is restricted and cannot be delegated`,\n );\n }\n }\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n }\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","NotAllowedError","decodeJwt"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalAuthTokenHandler } from './external/ExternalAuthTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n private readonly userTokenHandler: UserTokenHandler;\n private readonly pluginTokenHandler: PluginTokenHandler;\n private readonly externalTokenHandler: ExternalAuthTokenHandler;\n private readonly pluginId: string;\n private readonly disableDefaultAuthPolicy: boolean;\n private readonly pluginKeySource: PluginKeySource;\n\n constructor(\n userTokenHandler: UserTokenHandler,\n pluginTokenHandler: PluginTokenHandler,\n externalTokenHandler: ExternalAuthTokenHandler,\n pluginId: string,\n disableDefaultAuthPolicy: boolean,\n pluginKeySource: PluginKeySource,\n ) {\n this.userTokenHandler = userTokenHandler;\n this.pluginTokenHandler = pluginTokenHandler;\n this.externalTokenHandler = externalTokenHandler;\n this.pluginId = pluginId;\n this.disableDefaultAuthPolicy = disableDefaultAuthPolicy;\n this.pluginKeySource = pluginKeySource;\n }\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n externalResult.allAccessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existence.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service': {\n const servicePrincipal =\n internalForward.principal as BackstageServicePrincipal;\n if (servicePrincipal.accessRestrictions) {\n const targetRestrictions =\n internalForward.allAccessRestrictions?.get(targetPluginId);\n if (!targetRestrictions) {\n throw new NotAllowedError(\n `Access to target plugin '${targetPluginId}' is not included in token's access restrictions`,\n );\n }\n if (\n targetRestrictions.permissionNames?.length ||\n targetRestrictions.permissionAttributes\n ) {\n throw new NotAllowedError(\n `Access to target plugin '${targetPluginId}' is restricted and cannot be delegated`,\n );\n }\n }\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n }\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","NotAllowedError","decodeJwt"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"JwksClient.cjs.js","sources":["../../../src/entrypoints/auth/JwksClient.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 createRemoteJWKSet,\n errors,\n FlattenedJWSInput,\n GetKeyFunction,\n JWSHeaderParameters,\n} from 'jose';\n\nconst FORCED_RELOAD_LIMIT = 10;\nconst FORCED_RELOAD_WINDOW_MS = 60_000;\nconst KEY_STORE_CACHE_LIMIT = 100;\n\ntype RemoteJWKSet = ReturnType<typeof createRemoteJWKSet>;\n\nclass JwksKeyStore {\n readonly #resolver: RemoteJWKSet;\n readonly #tryUseForcedReload: () => boolean;\n\n constructor(endpoint: URL, tryUseForcedReload: () => boolean) {\n this.#resolver = createRemoteJWKSet(endpoint);\n this.#tryUseForcedReload = tryUseForcedReload;\n }\n\n getKey: GetKeyFunction<JWSHeaderParameters, FlattenedJWSInput> = async (\n protectedHeader,\n token,\n ) => {\n const coolingDown = this.#resolver.coolingDown;\n\n try {\n return await this.#resolver(protectedHeader, token);\n } catch (error) {\n if (!(error instanceof errors.JWKSNoMatchingKey)) {\n throw error;\n }\n\n if (!coolingDown || !(await this.#reload())) {\n throw error;\n }\n return this.#resolver(protectedHeader, token);\n }\n };\n\n async #reload(): Promise<boolean> {\n if (!this.#resolver.reloading && !this.#tryUseForcedReload()) {\n return false;\n }\n await this.#resolver.reload();\n return true;\n }\n}\n\nexport class JwksClient {\n #keyStores = new Map<string, JwksKeyStore>();\n #forcedReloads: number[] = [];\n\n private readonly getEndpoint: () => Promise<URL>;\n\n constructor(getEndpoint: () => Promise<URL>) {\n this.getEndpoint = getEndpoint;\n }\n\n get getKey() {\n return this.#getKey;\n }\n\n #getKey: GetKeyFunction<JWSHeaderParameters, FlattenedJWSInput> = async (\n protectedHeader,\n token,\n ) => {\n const keyStore = await this.#getKeyStore();\n return keyStore.getKey(protectedHeader, token);\n };\n\n async #getKeyStore(): Promise<JwksKeyStore> {\n const endpoint = await this.getEndpoint();\n let keyStore = this.#keyStores.get(endpoint.href);\n if (keyStore) {\n // Reinsert entries to keep the least recently used endpoint first.\n this.#keyStores.delete(endpoint.href);\n } else {\n keyStore = new JwksKeyStore(endpoint, () => this.#tryUseForcedReload());\n }\n this.#keyStores.set(endpoint.href, keyStore);\n if (this.#keyStores.size > KEY_STORE_CACHE_LIMIT) {\n this.#keyStores.delete(this.#keyStores.keys().next().value!);\n }\n return keyStore;\n }\n\n #tryUseForcedReload(): boolean {\n const windowStart = Date.now() - FORCED_RELOAD_WINDOW_MS;\n this.#forcedReloads = this.#forcedReloads.filter(\n timestamp => timestamp > windowStart,\n );\n if (this.#forcedReloads.length >= FORCED_RELOAD_LIMIT) {\n return false;\n }\n this.#forcedReloads.push(Date.now());\n return true;\n }\n}\n"],"names":["createRemoteJWKSet","errors"],"mappings":"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"}
1
+ {"version":3,"file":"JwksClient.cjs.js","sources":["../../../src/entrypoints/auth/JwksClient.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 createRemoteJWKSet,\n errors,\n FlattenedJWSInput,\n GetKeyFunction,\n JWSHeaderParameters,\n} from 'jose';\n\nconst FORCED_RELOAD_LIMIT = 10;\nconst FORCED_RELOAD_WINDOW_MS = 60_000;\nconst KEY_STORE_CACHE_LIMIT = 100;\n\ntype RemoteJWKSet = ReturnType<typeof createRemoteJWKSet>;\n\nclass JwksKeyStore {\n readonly #resolver: RemoteJWKSet;\n readonly #tryUseForcedReload: () => boolean;\n\n constructor(endpoint: URL, tryUseForcedReload: () => boolean) {\n this.#resolver = createRemoteJWKSet(endpoint);\n this.#tryUseForcedReload = tryUseForcedReload;\n }\n\n getKey: GetKeyFunction<JWSHeaderParameters, FlattenedJWSInput> = async (\n protectedHeader,\n token,\n ) => {\n const coolingDown = this.#resolver.coolingDown;\n\n try {\n return await this.#resolver(protectedHeader, token);\n } catch (error) {\n if (!(error instanceof errors.JWKSNoMatchingKey)) {\n throw error;\n }\n\n if (!coolingDown || !(await this.#reload())) {\n throw error;\n }\n return this.#resolver(protectedHeader, token);\n }\n };\n\n async #reload(): Promise<boolean> {\n if (!this.#resolver.reloading && !this.#tryUseForcedReload()) {\n return false;\n }\n await this.#resolver.reload();\n return true;\n }\n}\n\nexport class JwksClient {\n #keyStores = new Map<string, JwksKeyStore>();\n #forcedReloads: number[] = [];\n\n private readonly getEndpoint: () => Promise<URL>;\n\n constructor(getEndpoint: () => Promise<URL>) {\n this.getEndpoint = getEndpoint;\n }\n\n get getKey() {\n return this.#getKey;\n }\n\n #getKey: GetKeyFunction<JWSHeaderParameters, FlattenedJWSInput> = async (\n protectedHeader,\n token,\n ) => {\n const keyStore = await this.#getKeyStore();\n return keyStore.getKey(protectedHeader, token);\n };\n\n async #getKeyStore(): Promise<JwksKeyStore> {\n const endpoint = await this.getEndpoint();\n let keyStore = this.#keyStores.get(endpoint.href);\n if (keyStore) {\n // Reinsert entries to keep the least recently used endpoint first.\n this.#keyStores.delete(endpoint.href);\n } else {\n keyStore = new JwksKeyStore(endpoint, () => this.#tryUseForcedReload());\n }\n this.#keyStores.set(endpoint.href, keyStore);\n if (this.#keyStores.size > KEY_STORE_CACHE_LIMIT) {\n this.#keyStores.delete(this.#keyStores.keys().next().value!);\n }\n return keyStore;\n }\n\n #tryUseForcedReload(): boolean {\n const windowStart = Date.now() - FORCED_RELOAD_WINDOW_MS;\n this.#forcedReloads = this.#forcedReloads.filter(\n timestamp => timestamp > windowStart,\n );\n if (this.#forcedReloads.length >= FORCED_RELOAD_LIMIT) {\n return false;\n }\n this.#forcedReloads.push(Date.now());\n return true;\n }\n}\n"],"names":["createRemoteJWKSet","errors"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport {\n ExternalAuthTokenHandler,\n externalTokenHandlersServiceRef,\n} from './external/ExternalAuthTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef,\n externalTokenHandlers: externalTokenHandlersServiceRef,\n },\n async factory({\n config,\n discovery,\n plugin,\n logger,\n database,\n pluginTokenHandlerDecorator,\n externalTokenHandlers,\n }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n logger,\n });\n\n const pluginTokens = pluginTokenHandlerDecorator(\n DefaultPluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n }),\n );\n\n const externalTokens = ExternalAuthTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n externalTokenHandlers,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceRef","createServiceFactory","coreServices","externalTokenHandlersServiceRef","createPluginKeySource","UserTokenHandler","DefaultPluginTokenHandler","ExternalAuthTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAqCO,MAAM,wCAAwCA,iCAAA,CAEnD;AAAA,EACA,EAAA,EAAI,uCAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAA,CAAqB;AAAA,IACnB,OAAA;AAAA,IACA,MAAM,EAAC;AAAA,IACP,SAAS,YAAY;AACnB,MAAA,OAAO,CAAA,IAAA,KAAQ,IAAA;AAAA,IACjB;AAAA,GACD;AACL,CAAC;AAWM,MAAM,qBAAqBA,qCAAA,CAAqB;AAAA,EACrD,SAASC,6BAAA,CAAa,IAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,QAAQA,6BAAA,CAAa,UAAA;AAAA,IACrB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,WAAWA,6BAAA,CAAa,SAAA;AAAA,IACxB,QAAQA,6BAAA,CAAa,cAAA;AAAA,IACrB,UAAUA,6BAAA,CAAa,QAAA;AAAA,IACvB,2BAAA,EAA6B,qCAAA;AAAA,IAC7B,qBAAA,EAAuBC;AAAA,GACzB;AAAA,EACA,MAAM,OAAA,CAAQ;AAAA,IACZ,MAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,2BAAA;AAAA,IACA;AAAA,GACF,EAAG;AACD,IAAA,MAAM,2BACJ,MAAA,CAAO,kBAAA;AAAA,MACL;AAAA,KACF,IAAK,KAAA;AAEP,IAAA,MAAM,WAAA,GAAc,EAAE,KAAA,EAAO,CAAA,EAAE;AAE/B,IAAA,MAAM,SAAA,GAAY,MAAMC,2CAAA,CAAsB;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAA,GAAaC,kCAAiB,MAAA,CAAO;AAAA,MACzC,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,YAAA,GAAe,2BAAA;AAAA,MACnBC,6CAA0B,MAAA,CAAO;AAAA,QAC/B,WAAA,EAAa,OAAO,KAAA,EAAM;AAAA,QAC1B,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAEA,IAAA,MAAM,cAAA,GAAiBC,kDAAyB,MAAA,CAAO;AAAA,MACrD,WAAA,EAAa,OAAO,KAAA,EAAM;AAAA,MAC1B,MAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAA,EAAM;AAAA,MACb,wBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF,CAAC;;;;;"}
1
+ {"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport {\n ExternalAuthTokenHandler,\n externalTokenHandlersServiceRef,\n} from './external/ExternalAuthTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef,\n externalTokenHandlers: externalTokenHandlersServiceRef,\n },\n async factory({\n config,\n discovery,\n plugin,\n logger,\n database,\n pluginTokenHandlerDecorator,\n externalTokenHandlers,\n }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n logger,\n });\n\n const pluginTokens = pluginTokenHandlerDecorator(\n DefaultPluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n }),\n );\n\n const externalTokens = ExternalAuthTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n externalTokenHandlers,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceRef","createServiceFactory","coreServices","externalTokenHandlersServiceRef","createPluginKeySource","UserTokenHandler","DefaultPluginTokenHandler","ExternalAuthTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAqCO,MAAM,wCAAwCA,iCAAA,CAEnD;AAAA,EACA,EAAA,EAAI,uCAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAA,CAAqB;AAAA,IACnB,OAAA;AAAA,IACA,MAAM,EAAC;AAAA,IACP,SAAS,YAAY;AACnB,MAAA,OAAO,CAAA,IAAA,KAAQ,IAAA;AAAA,IACjB;AAAA,GACD;AACL,CAAC;AAWM,MAAM,qBAAqBA,qCAAA,CAAqB;AAAA,EACrD,SAASC,6BAAA,CAAa,IAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,QAAQA,6BAAA,CAAa,UAAA;AAAA,IACrB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,WAAWA,6BAAA,CAAa,SAAA;AAAA,IACxB,QAAQA,6BAAA,CAAa,cAAA;AAAA,IACrB,UAAUA,6BAAA,CAAa,QAAA;AAAA,IACvB,2BAAA,EAA6B,qCAAA;AAAA,IAC7B,qBAAA,EAAuBC;AAAA,GACzB;AAAA,EACA,MAAM,OAAA,CAAQ;AAAA,IACZ,MAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,2BAAA;AAAA,IACA;AAAA,GACF,EAAG;AACD,IAAA,MAAM,2BACJ,MAAA,CAAO,kBAAA;AAAA,MACL;AAAA,KACF,IAAK,KAAA;AAEP,IAAA,MAAM,WAAA,GAAc,EAAE,KAAA,EAAO,CAAA,EAAE;AAE/B,IAAA,MAAM,SAAA,GAAY,MAAMC,2CAAA,CAAsB;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAA,GAAaC,kCAAiB,MAAA,CAAO;AAAA,MACzC,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,YAAA,GAAe,2BAAA;AAAA,MACnBC,6CAA0B,MAAA,CAAO;AAAA,QAC/B,WAAA,EAAa,OAAO,KAAA,EAAM;AAAA,QAC1B,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAEA,IAAA,MAAM,cAAA,GAAiBC,kDAAyB,MAAA,CAAO;AAAA,MACrD,WAAA,EAAa,OAAO,KAAA,EAAM;AAAA,MAC1B,MAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAA,EAAM;AAAA,MACb,wBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF,CAAC;;;"}
@@ -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 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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"}
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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.cjs.js","sources":["../../../../src/entrypoints/auth/external/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 { Config } from '@backstage/config';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\n\n/**\n * Parses and returns the `accessRestrictions` configuration from an\n * `externalAccess` entry, or undefined if there wasn't one.\n *\n * @internal\n */\nexport function readAccessRestrictionsFromConfig(\n externalAccessEntryConfig: Config,\n): AccessRestrictionsMap | undefined {\n const configs =\n externalAccessEntryConfig.getOptionalConfigArray('accessRestrictions') ??\n [];\n\n const result: AccessRestrictionsMap = new Map();\n for (const config of configs) {\n const validKeys = ['plugin', 'permission', 'permissionAttribute'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`,\n );\n }\n }\n\n const pluginId = config.getString('plugin');\n const permissionNames = readPermissionNames(config);\n const permissionAttributes = readPermissionAttributes(config);\n\n if (result.has(pluginId)) {\n throw new Error(\n `Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`,\n );\n }\n\n result.set(pluginId, {\n ...(permissionNames ? { permissionNames } : {}),\n ...(permissionAttributes ? { permissionAttributes } : {}),\n });\n }\n\n return result.size ? result : undefined;\n}\n\n/**\n * Reads a config value as a string or an array of strings, and deduplicates and\n * splits by comma/space into a string array. Can also validate against a known\n * set of values. Returns undefined if the key didn't exist or if the array\n * would have ended up being empty.\n *\n * @internal\n */\nexport function readStringOrStringArrayFromConfig<T extends string>(\n root: Config,\n key: string,\n validValues?: readonly T[],\n): T[] | undefined {\n if (!root.has(key)) {\n return undefined;\n }\n\n const rawValues = Array.isArray(root.get(key))\n ? root.getStringArray(key)\n : [root.getString(key)];\n\n const values = [\n ...new Set(\n rawValues\n .map(v => v.split(/[ ,]/))\n .flat()\n .filter(Boolean),\n ),\n ];\n\n if (!values.length) {\n return undefined;\n }\n\n if (validValues?.length) {\n for (const value of values) {\n if (!validValues.includes(value as T)) {\n const valid = validValues.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`,\n );\n }\n }\n }\n\n return values as T[];\n}\n\nfunction readPermissionNames(externalAccessEntryConfig: Config) {\n return readStringOrStringArrayFromConfig(\n externalAccessEntryConfig,\n 'permission',\n );\n}\n\nfunction readPermissionAttributes(externalAccessEntryConfig: Config) {\n const config = externalAccessEntryConfig.getOptionalConfig(\n 'permissionAttribute',\n );\n if (!config) {\n return undefined;\n }\n\n const validKeys = ['action'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`,\n );\n }\n }\n\n const action = readStringOrStringArrayFromConfig(config, 'action', [\n 'create',\n 'read',\n 'update',\n 'delete',\n ]);\n\n const result = {\n ...(action ? { action } : {}),\n };\n\n return Object.keys(result).length ? result : undefined;\n}\n\n/**\n * Creates an external token handler with the provided implementation.\n *\n * This helper function simplifies the creation of external token handlers by\n * providing type safety and a consistent API. External token handlers are used\n * to validate tokens from external systems that need to authenticate with Backstage.\n *\n * See {@link https://backstage.io/docs/auth/service-to-service-auth#adding-custom-externaltokenhandler | the service-to-service auth docs}\n * for more information about implementing custom external token handlers.\n *\n * @public\n * @param handler - The external token handler implementation with type, initialize, and verifyToken methods\n * @returns The same handler instance, typed as ExternalTokenHandler<TContext>\n *\n * @example\n * ```ts\n * const customHandler = createExternalTokenHandler({\n * type: 'custom',\n * initialize({ options }) {\n * return { apiKey: options.getString('apiKey') };\n * },\n * async verifyToken(token, context) {\n * // Custom validation logic here\n * return { subject: 'custom:user' };\n * },\n * });\n * ```\n */\nexport function createExternalTokenHandler<TContext>(\n handler: ExternalTokenHandler<TContext>,\n): ExternalTokenHandler<TContext> {\n return handler;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../../../src/entrypoints/auth/external/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 { Config } from '@backstage/config';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\n\n/**\n * Parses and returns the `accessRestrictions` configuration from an\n * `externalAccess` entry, or undefined if there wasn't one.\n *\n * @internal\n */\nexport function readAccessRestrictionsFromConfig(\n externalAccessEntryConfig: Config,\n): AccessRestrictionsMap | undefined {\n const configs =\n externalAccessEntryConfig.getOptionalConfigArray('accessRestrictions') ??\n [];\n\n const result: AccessRestrictionsMap = new Map();\n for (const config of configs) {\n const validKeys = ['plugin', 'permission', 'permissionAttribute'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`,\n );\n }\n }\n\n const pluginId = config.getString('plugin');\n const permissionNames = readPermissionNames(config);\n const permissionAttributes = readPermissionAttributes(config);\n\n if (result.has(pluginId)) {\n throw new Error(\n `Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`,\n );\n }\n\n result.set(pluginId, {\n ...(permissionNames ? { permissionNames } : {}),\n ...(permissionAttributes ? { permissionAttributes } : {}),\n });\n }\n\n return result.size ? result : undefined;\n}\n\n/**\n * Reads a config value as a string or an array of strings, and deduplicates and\n * splits by comma/space into a string array. Can also validate against a known\n * set of values. Returns undefined if the key didn't exist or if the array\n * would have ended up being empty.\n *\n * @internal\n */\nexport function readStringOrStringArrayFromConfig<T extends string>(\n root: Config,\n key: string,\n validValues?: readonly T[],\n): T[] | undefined {\n if (!root.has(key)) {\n return undefined;\n }\n\n const rawValues = Array.isArray(root.get(key))\n ? root.getStringArray(key)\n : [root.getString(key)];\n\n const values = [\n ...new Set(\n rawValues\n .map(v => v.split(/[ ,]/))\n .flat()\n .filter(Boolean),\n ),\n ];\n\n if (!values.length) {\n return undefined;\n }\n\n if (validValues?.length) {\n for (const value of values) {\n if (!validValues.includes(value as T)) {\n const valid = validValues.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`,\n );\n }\n }\n }\n\n return values as T[];\n}\n\nfunction readPermissionNames(externalAccessEntryConfig: Config) {\n return readStringOrStringArrayFromConfig(\n externalAccessEntryConfig,\n 'permission',\n );\n}\n\nfunction readPermissionAttributes(externalAccessEntryConfig: Config) {\n const config = externalAccessEntryConfig.getOptionalConfig(\n 'permissionAttribute',\n );\n if (!config) {\n return undefined;\n }\n\n const validKeys = ['action'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`,\n );\n }\n }\n\n const action = readStringOrStringArrayFromConfig(config, 'action', [\n 'create',\n 'read',\n 'update',\n 'delete',\n ]);\n\n const result = {\n ...(action ? { action } : {}),\n };\n\n return Object.keys(result).length ? result : undefined;\n}\n\n/**\n * Creates an external token handler with the provided implementation.\n *\n * This helper function simplifies the creation of external token handlers by\n * providing type safety and a consistent API. External token handlers are used\n * to validate tokens from external systems that need to authenticate with Backstage.\n *\n * See {@link https://backstage.io/docs/auth/service-to-service-auth#adding-custom-externaltokenhandler | the service-to-service auth docs}\n * for more information about implementing custom external token handlers.\n *\n * @public\n * @param handler - The external token handler implementation with type, initialize, and verifyToken methods\n * @returns The same handler instance, typed as ExternalTokenHandler<TContext>\n *\n * @example\n * ```ts\n * const customHandler = createExternalTokenHandler({\n * type: 'custom',\n * initialize({ options }) {\n * return { apiKey: options.getString('apiKey') };\n * },\n * async verifyToken(token, context) {\n * // Custom validation logic here\n * return { subject: 'custom:user' };\n * },\n * });\n * ```\n */\nexport function createExternalTokenHandler<TContext>(\n handler: ExternalTokenHandler<TContext>,\n): ExternalTokenHandler<TContext> {\n return handler;\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"jwks.cjs.js","sources":["../../../../src/entrypoints/auth/external/jwks.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 { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose';\nimport {\n createExternalTokenHandler,\n readAccessRestrictionsFromConfig,\n readStringOrStringArrayFromConfig,\n} from './helpers';\nimport { AccessRestrictionsMap } from './types';\n\ntype JWKSTokenContext = {\n algorithms?: string[];\n audiences?: string[];\n issuers?: string[];\n subjectPrefix?: string;\n url: URL;\n jwks: JWTVerifyGetKey;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\nexport const jwksTokenHandler = createExternalTokenHandler<JWKSTokenContext>({\n type: 'jwks',\n initialize({ options }): JWKSTokenContext {\n if (!options.getString('url').match(/^\\S+$/)) {\n throw new Error(\n 'Illegal JWKS URL, must be a set of non-space characters',\n );\n }\n\n const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm');\n const issuers = readStringOrStringArrayFromConfig(options, 'issuer');\n const audiences = readStringOrStringArrayFromConfig(options, 'audience');\n const subjectPrefix = options.getOptionalString('subjectPrefix');\n const url = new URL(options.getString('url'));\n const jwks = createRemoteJWKSet(url);\n const allAccessRestrictions = readAccessRestrictionsFromConfig(options);\n return {\n algorithms,\n audiences,\n issuers,\n jwks,\n subjectPrefix,\n url,\n allAccessRestrictions,\n };\n },\n\n async verifyToken(token: string, context: JWKSTokenContext) {\n try {\n const {\n payload: { sub },\n } = await jwtVerify(token, context.jwks, {\n algorithms: context.algorithms,\n issuer: context.issuers,\n audience: context.audiences,\n });\n\n if (sub) {\n const prefix = context.subjectPrefix\n ? `external:${context.subjectPrefix}:`\n : 'external:';\n return {\n subject: `${prefix}${sub}`,\n };\n }\n } catch {\n return undefined;\n }\n return undefined;\n },\n});\n"],"names":["createExternalTokenHandler","readStringOrStringArrayFromConfig","createRemoteJWKSet","readAccessRestrictionsFromConfig","jwtVerify"],"mappings":";;;;;AAkCO,MAAM,mBAAmBA,kCAAA,CAA6C;AAAA,EAC3E,IAAA,EAAM,MAAA;AAAA,EACN,UAAA,CAAW,EAAE,OAAA,EAAQ,EAAqB;AACxC,IAAA,IAAI,CAAC,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAA,CAAE,KAAA,CAAM,OAAO,CAAA,EAAG;AAC5C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAAaC,yCAAA,CAAkC,OAAA,EAAS,WAAW,CAAA;AACzE,IAAA,MAAM,OAAA,GAAUA,yCAAA,CAAkC,OAAA,EAAS,QAAQ,CAAA;AACnE,IAAA,MAAM,SAAA,GAAYA,yCAAA,CAAkC,OAAA,EAAS,UAAU,CAAA;AACvE,IAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,iBAAA,CAAkB,eAAe,CAAA;AAC/D,IAAA,MAAM,MAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAC,CAAA;AAC5C,IAAA,MAAM,IAAA,GAAOC,wBAAmB,GAAG,CAAA;AACnC,IAAA,MAAM,qBAAA,GAAwBC,yCAAiC,OAAO,CAAA;AACtE,IAAA,OAAO;AAAA,MACL,UAAA;AAAA,MACA,SAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,aAAA;AAAA,MACA,GAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EAEA,MAAM,WAAA,CAAY,KAAA,EAAe,OAAA,EAA2B;AAC1D,IAAA,IAAI;AACF,MAAA,MAAM;AAAA,QACJ,OAAA,EAAS,EAAE,GAAA;AAAI,OACjB,GAAI,MAAMC,cAAA,CAAU,KAAA,EAAO,QAAQ,IAAA,EAAM;AAAA,QACvC,YAAY,OAAA,CAAQ,UAAA;AAAA,QACpB,QAAQ,OAAA,CAAQ,OAAA;AAAA,QAChB,UAAU,OAAA,CAAQ;AAAA,OACnB,CAAA;AAED,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,MAAM,SAAS,OAAA,CAAQ,aAAA,GACnB,CAAA,SAAA,EAAY,OAAA,CAAQ,aAAa,CAAA,CAAA,CAAA,GACjC,WAAA;AACJ,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,CAAA,EAAG,MAAM,CAAA,EAAG,GAAG,CAAA;AAAA,SAC1B;AAAA,MACF;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"jwks.cjs.js","sources":["../../../../src/entrypoints/auth/external/jwks.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 { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose';\nimport {\n createExternalTokenHandler,\n readAccessRestrictionsFromConfig,\n readStringOrStringArrayFromConfig,\n} from './helpers';\nimport { AccessRestrictionsMap } from './types';\n\ntype JWKSTokenContext = {\n algorithms?: string[];\n audiences?: string[];\n issuers?: string[];\n subjectPrefix?: string;\n url: URL;\n jwks: JWTVerifyGetKey;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\nexport const jwksTokenHandler = createExternalTokenHandler<JWKSTokenContext>({\n type: 'jwks',\n initialize({ options }): JWKSTokenContext {\n if (!options.getString('url').match(/^\\S+$/)) {\n throw new Error(\n 'Illegal JWKS URL, must be a set of non-space characters',\n );\n }\n\n const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm');\n const issuers = readStringOrStringArrayFromConfig(options, 'issuer');\n const audiences = readStringOrStringArrayFromConfig(options, 'audience');\n const subjectPrefix = options.getOptionalString('subjectPrefix');\n const url = new URL(options.getString('url'));\n const jwks = createRemoteJWKSet(url);\n const allAccessRestrictions = readAccessRestrictionsFromConfig(options);\n return {\n algorithms,\n audiences,\n issuers,\n jwks,\n subjectPrefix,\n url,\n allAccessRestrictions,\n };\n },\n\n async verifyToken(token: string, context: JWKSTokenContext) {\n try {\n const {\n payload: { sub },\n } = await jwtVerify(token, context.jwks, {\n algorithms: context.algorithms,\n issuer: context.issuers,\n audience: context.audiences,\n });\n\n if (sub) {\n const prefix = context.subjectPrefix\n ? `external:${context.subjectPrefix}:`\n : 'external:';\n return {\n subject: `${prefix}${sub}`,\n };\n }\n } catch {\n return undefined;\n }\n return undefined;\n },\n});\n"],"names":["createExternalTokenHandler","readStringOrStringArrayFromConfig","createRemoteJWKSet","readAccessRestrictionsFromConfig","jwtVerify"],"mappings":";;;;;AAkCO,MAAM,mBAAmBA,kCAAA,CAA6C;AAAA,EAC3E,IAAA,EAAM,MAAA;AAAA,EACN,UAAA,CAAW,EAAE,OAAA,EAAQ,EAAqB;AACxC,IAAA,IAAI,CAAC,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAA,CAAE,KAAA,CAAM,OAAO,CAAA,EAAG;AAC5C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAAaC,yCAAA,CAAkC,OAAA,EAAS,WAAW,CAAA;AACzE,IAAA,MAAM,OAAA,GAAUA,yCAAA,CAAkC,OAAA,EAAS,QAAQ,CAAA;AACnE,IAAA,MAAM,SAAA,GAAYA,yCAAA,CAAkC,OAAA,EAAS,UAAU,CAAA;AACvE,IAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,iBAAA,CAAkB,eAAe,CAAA;AAC/D,IAAA,MAAM,MAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAC,CAAA;AAC5C,IAAA,MAAM,IAAA,GAAOC,wBAAmB,GAAG,CAAA;AACnC,IAAA,MAAM,qBAAA,GAAwBC,yCAAiC,OAAO,CAAA;AACtE,IAAA,OAAO;AAAA,MACL,UAAA;AAAA,MACA,SAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,aAAA;AAAA,MACA,GAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EAEA,MAAM,WAAA,CAAY,KAAA,EAAe,OAAA,EAA2B;AAC1D,IAAA,IAAI;AACF,MAAA,MAAM;AAAA,QACJ,OAAA,EAAS,EAAE,GAAA;AAAI,OACjB,GAAI,MAAMC,cAAA,CAAU,KAAA,EAAO,QAAQ,IAAA,EAAM;AAAA,QACvC,YAAY,OAAA,CAAQ,UAAA;AAAA,QACpB,QAAQ,OAAA,CAAQ,OAAA;AAAA,QAChB,UAAU,OAAA,CAAQ;AAAA,OACnB,CAAA;AAED,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,MAAM,SAAS,OAAA,CAAQ,aAAA,GACnB,CAAA,SAAA,EAAY,OAAA,CAAQ,aAAa,CAAA,CAAA,CAAA,GACjC,WAAA;AACJ,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,CAAA,EAAG,MAAM,CAAA,EAAG,GAAG,CAAA;AAAA,SAC1B;AAAA,MACF;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAC;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"legacy.cjs.js","sources":["../../../../src/entrypoints/auth/external/legacy.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 { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose';\n\nimport { ExternalTokenHandler } from './types';\n\nexport type LegacyConfigWrapper = {\n legacy: boolean;\n config: Config;\n};\n\ntype LegacyTokenHandlerContext = {\n key: Uint8Array;\n\n subject: string;\n};\n\ntype LegacyTokenHandlerOverloaded =\n ExternalTokenHandler<LegacyTokenHandlerContext> & {\n initialize(ctx: {\n options: Config;\n legacy: true;\n }): LegacyTokenHandlerContext;\n };\n\nexport const legacyTokenHandler: LegacyTokenHandlerOverloaded = {\n type: 'legacy',\n initialize(ctx: {\n options: Config;\n legacy?: true;\n }): LegacyTokenHandlerContext {\n const secret = ctx.options.getString('secret');\n const subject = ctx.legacy\n ? 'external:backstage-plugin'\n : ctx.options.getString('subject');\n\n if (!secret.match(/^\\S+$/)) {\n throw new Error('Illegal secret, must be a valid base64 string');\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n }\n\n try {\n return {\n key: base64url.decode(secret),\n subject,\n };\n } catch {\n throw new Error('Illegal secret, must be a valid base64 string');\n }\n },\n\n async verifyToken(token: string, context: LegacyTokenHandlerContext) {\n // First do a duck typing check to see if it remotely looks like a legacy token\n try {\n // We do a fair amount of checking upfront here. Since we aren't certain\n // that it's even the right type of key that we're looking at, we can't\n // defer eg the alg check to jwtVerify, because it won't be possible to\n // discern different reasons for key verification failures from each other\n // easily\n const { alg } = decodeProtectedHeader(token);\n if (alg !== 'HS256') {\n return undefined;\n }\n const { sub, aud } = decodeJwt(token);\n if (sub !== 'backstage-server' || aud) {\n return undefined;\n }\n } catch (e) {\n // Doesn't look like a jwt at all\n return undefined;\n }\n\n try {\n await jwtVerify(token, context.key);\n return {\n subject: context.subject,\n };\n } catch (error) {\n if (error.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {\n throw error;\n }\n }\n\n // None of the signing keys matched\n return undefined;\n },\n};\n"],"names":["base64url","decodeProtectedHeader","decodeJwt","jwtVerify"],"mappings":";;;;AAwCO,MAAM,kBAAA,GAAmD;AAAA,EAC9D,IAAA,EAAM,QAAA;AAAA,EACN,WAAW,GAAA,EAGmB;AAC5B,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,QAAQ,CAAA;AAC7C,IAAA,MAAM,UAAU,GAAA,CAAI,MAAA,GAChB,8BACA,GAAA,CAAI,OAAA,CAAQ,UAAU,SAAS,CAAA;AAEnC,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,EAAG;AAC1B,MAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,IACjE,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AAClC,MAAA,MAAM,IAAI,MAAM,wDAAwD,CAAA;AAAA,IAC1E;AAEA,IAAA,IAAI;AACF,MAAA,OAAO;AAAA,QACL,GAAA,EAAKA,cAAA,CAAU,MAAA,CAAO,MAAM,CAAA;AAAA,QAC5B;AAAA,OACF;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,IACjE;AAAA,EACF,CAAA;AAAA,EAEA,MAAM,WAAA,CAAY,KAAA,EAAe,OAAA,EAAoC;AAEnE,IAAA,IAAI;AAMF,MAAA,MAAM,EAAE,GAAA,EAAI,GAAIC,0BAAA,CAAsB,KAAK,CAAA;AAC3C,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,OAAO,KAAA,CAAA;AAAA,MACT;AACA,MAAA,MAAM,EAAE,GAAA,EAAK,GAAA,EAAI,GAAIC,eAAU,KAAK,CAAA;AACpC,MAAA,IAAI,GAAA,KAAQ,sBAAsB,GAAA,EAAK;AACrC,QAAA,OAAO,KAAA,CAAA;AAAA,MACT;AAAA,IACF,SAAS,CAAA,EAAG;AAEV,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI;AACF,MAAA,MAAMC,cAAA,CAAU,KAAA,EAAO,OAAA,CAAQ,GAAG,CAAA;AAClC,MAAA,OAAO;AAAA,QACL,SAAS,OAAA,CAAQ;AAAA,OACnB;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,KAAA,CAAM,SAAS,uCAAA,EAAyC;AAC1D,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF;AAGA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;"}
1
+ {"version":3,"file":"legacy.cjs.js","sources":["../../../../src/entrypoints/auth/external/legacy.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 { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose';\n\nimport { ExternalTokenHandler } from './types';\n\nexport type LegacyConfigWrapper = {\n legacy: boolean;\n config: Config;\n};\n\ntype LegacyTokenHandlerContext = {\n key: Uint8Array;\n\n subject: string;\n};\n\ntype LegacyTokenHandlerOverloaded =\n ExternalTokenHandler<LegacyTokenHandlerContext> & {\n initialize(ctx: {\n options: Config;\n legacy: true;\n }): LegacyTokenHandlerContext;\n };\n\nexport const legacyTokenHandler: LegacyTokenHandlerOverloaded = {\n type: 'legacy',\n initialize(ctx: {\n options: Config;\n legacy?: true;\n }): LegacyTokenHandlerContext {\n const secret = ctx.options.getString('secret');\n const subject = ctx.legacy\n ? 'external:backstage-plugin'\n : ctx.options.getString('subject');\n\n if (!secret.match(/^\\S+$/)) {\n throw new Error('Illegal secret, must be a valid base64 string');\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n }\n\n try {\n return {\n key: base64url.decode(secret),\n subject,\n };\n } catch {\n throw new Error('Illegal secret, must be a valid base64 string');\n }\n },\n\n async verifyToken(token: string, context: LegacyTokenHandlerContext) {\n // First do a duck typing check to see if it remotely looks like a legacy token\n try {\n // We do a fair amount of checking upfront here. Since we aren't certain\n // that it's even the right type of key that we're looking at, we can't\n // defer eg the alg check to jwtVerify, because it won't be possible to\n // discern different reasons for key verification failures from each other\n // easily\n const { alg } = decodeProtectedHeader(token);\n if (alg !== 'HS256') {\n return undefined;\n }\n const { sub, aud } = decodeJwt(token);\n if (sub !== 'backstage-server' || aud) {\n return undefined;\n }\n } catch (e) {\n // Doesn't look like a jwt at all\n return undefined;\n }\n\n try {\n await jwtVerify(token, context.key);\n return {\n subject: context.subject,\n };\n } catch (error) {\n if (error.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {\n throw error;\n }\n }\n\n // None of the signing keys matched\n return undefined;\n },\n};\n"],"names":["base64url","decodeProtectedHeader","decodeJwt","jwtVerify"],"mappings":";;;;AAwCO,MAAM,kBAAA,GAAmD;AAAA,EAC9D,IAAA,EAAM,QAAA;AAAA,EACN,WAAW,GAAA,EAGmB;AAC5B,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,QAAQ,CAAA;AAC7C,IAAA,MAAM,UAAU,GAAA,CAAI,MAAA,GAChB,8BACA,GAAA,CAAI,OAAA,CAAQ,UAAU,SAAS,CAAA;AAEnC,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,EAAG;AAC1B,MAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,IACjE,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AAClC,MAAA,MAAM,IAAI,MAAM,wDAAwD,CAAA;AAAA,IAC1E;AAEA,IAAA,IAAI;AACF,MAAA,OAAO;AAAA,QACL,GAAA,EAAKA,cAAA,CAAU,MAAA,CAAO,MAAM,CAAA;AAAA,QAC5B;AAAA,OACF;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,IACjE;AAAA,EACF,CAAA;AAAA,EAEA,MAAM,WAAA,CAAY,KAAA,EAAe,OAAA,EAAoC;AAEnE,IAAA,IAAI;AAMF,MAAA,MAAM,EAAE,GAAA,EAAI,GAAIC,0BAAA,CAAsB,KAAK,CAAA;AAC3C,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,OAAO,KAAA,CAAA;AAAA,MACT;AACA,MAAA,MAAM,EAAE,GAAA,EAAK,GAAA,EAAI,GAAIC,eAAU,KAAK,CAAA;AACpC,MAAA,IAAI,GAAA,KAAQ,sBAAsB,GAAA,EAAK;AACrC,QAAA,OAAO,KAAA,CAAA;AAAA,MACT;AAAA,IACF,SAAS,CAAA,EAAG;AAEV,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI;AACF,MAAA,MAAMC,cAAA,CAAU,KAAA,EAAO,OAAA,CAAQ,GAAG,CAAA;AAClC,MAAA,OAAO;AAAA,QACL,SAAS,OAAA,CAAQ;AAAA,OACnB;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,KAAA,CAAM,SAAS,uCAAA,EAAyC;AAC1D,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF;AAGA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"static.cjs.js","sources":["../../../../src/entrypoints/auth/external/static.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 { createExternalTokenHandler } from './helpers';\n\nconst MIN_TOKEN_LENGTH = 8;\n\nexport const staticTokenHandler = createExternalTokenHandler<{\n token: string;\n subject: string;\n}>({\n type: 'static',\n initialize(ctx: { options: Config }): { token: string; subject: string } {\n const token = ctx.options.getString('token');\n const subject = ctx.options.getString('subject');\n\n if (!token.match(/^\\S+$/)) {\n throw new Error('Illegal token, must be a set of non-space characters');\n } else if (token.length < MIN_TOKEN_LENGTH) {\n throw new Error(\n `Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`,\n );\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n }\n\n return { token, subject };\n },\n async verifyToken(\n token: string,\n context: { token: string; subject: string },\n ) {\n if (token === context.token) {\n return { subject: context.subject };\n }\n return undefined;\n },\n});\n"],"names":["createExternalTokenHandler"],"mappings":";;;;AAmBA,MAAM,gBAAA,GAAmB,CAAA;AAElB,MAAM,qBAAqBA,kCAAA,CAG/B;AAAA,EACD,IAAA,EAAM,QAAA;AAAA,EACN,WAAW,GAAA,EAA8D;AACvE,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,OAAO,CAAA;AAC3C,IAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,SAAS,CAAA;AAE/C,IAAA,IAAI,CAAC,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA,EAAG;AACzB,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE,CAAA,MAAA,IAAW,KAAA,CAAM,MAAA,GAAS,gBAAA,EAAkB;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,mCAAmC,gBAAgB,CAAA,kBAAA;AAAA,OACrD;AAAA,IACF,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AAClC,MAAA,MAAM,IAAI,MAAM,wDAAwD,CAAA;AAAA,IAC1E;AAEA,IAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAAA,EAC1B,CAAA;AAAA,EACA,MAAM,WAAA,CACJ,KAAA,EACA,OAAA,EACA;AACA,IAAA,IAAI,KAAA,KAAU,QAAQ,KAAA,EAAO;AAC3B,MAAA,OAAO,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ;AAAA,IACpC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"static.cjs.js","sources":["../../../../src/entrypoints/auth/external/static.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 { createExternalTokenHandler } from './helpers';\n\nconst MIN_TOKEN_LENGTH = 8;\n\nexport const staticTokenHandler = createExternalTokenHandler<{\n token: string;\n subject: string;\n}>({\n type: 'static',\n initialize(ctx: { options: Config }): { token: string; subject: string } {\n const token = ctx.options.getString('token');\n const subject = ctx.options.getString('subject');\n\n if (!token.match(/^\\S+$/)) {\n throw new Error('Illegal token, must be a set of non-space characters');\n } else if (token.length < MIN_TOKEN_LENGTH) {\n throw new Error(\n `Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`,\n );\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n }\n\n return { token, subject };\n },\n async verifyToken(\n token: string,\n context: { token: string; subject: string },\n ) {\n if (token === context.token) {\n return { subject: context.subject };\n }\n return undefined;\n },\n});\n"],"names":["createExternalTokenHandler"],"mappings":";;;;AAmBA,MAAM,gBAAA,GAAmB,CAAA;AAElB,MAAM,qBAAqBA,kCAAA,CAG/B;AAAA,EACD,IAAA,EAAM,QAAA;AAAA,EACN,WAAW,GAAA,EAA8D;AACvE,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,OAAO,CAAA;AAC3C,IAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,SAAA,CAAU,SAAS,CAAA;AAE/C,IAAA,IAAI,CAAC,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA,EAAG;AACzB,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE,CAAA,MAAA,IAAW,KAAA,CAAM,MAAA,GAAS,gBAAA,EAAkB;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,mCAAmC,gBAAgB,CAAA,kBAAA;AAAA,OACrD;AAAA,IACF,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AAClC,MAAA,MAAM,IAAI,MAAM,wDAAwD,CAAA;AAAA,IAC1E;AAEA,IAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAAA,EAC1B,CAAA;AAAA,EACA,MAAM,WAAA,CACJ,KAAA,EACA,OAAA,EACA;AACA,IAAA,IAAI,KAAA,KAAU,QAAQ,KAAA,EAAO;AAC3B,MAAA,OAAO,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ;AAAA,IACpC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAC;;"}
@@ -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 { 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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"}
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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"}
@@ -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\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 // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (\n !this.jwksMap.has(pluginId) &&\n !(await this.isTargetPluginSupported(pluginId))\n ) {\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 // Concurrent callers may have populated the map while we checked support.\n // Reuse their client so that key fetches and refreshes stay deduplicated.\n const existingClient = this.jwksMap.get(pluginId);\n if (existingClient) {\n return existingClient;\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 // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (\n !this.jwksMap.has(pluginId) &&\n !(await this.isTargetPluginSupported(pluginId))\n ) {\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 // Concurrent callers may have populated the map while we checked support.\n // Reuse their client so that key fetches and refreshes stay deduplicated.\n const existingClient = this.jwksMap.get(pluginId);\n if (existingClient) {\n return existingClient;\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 +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 readonly client: Knex;\n private readonly logger: LoggerService;\n\n private constructor(client: Knex, logger: LoggerService) {\n this.client = client;\n this.logger = logger;\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":";;;;;AA0BA,MAAM,gBAAA,GAAmB,gDAAA;AAGlB,MAAM,KAAA,GAAQ;AAQd,SAAS,wBAAwB,IAAA,EAA2B;AACjE,EAAA,MAAM,aAAA,GAAgBA,mCAAA;AAAA,IACpB,6BAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO,IAAA,CAAK,QAAQ,MAAA,CAAO;AAAA,IACzB,SAAA,EAAW,aAAA;AAAA,IACX,SAAA,EAAW;AAAA,GACZ,CAAA;AACH;AAGO,MAAM,gBAAA,CAAqC;AAAA,EAChD,aAAa,OAAO,OAAA,EAGjB;AACD,IAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAO,GAAI,OAAA;AAE7B,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,SAAA,EAAU;AACxC,IAAA,IAAI,CAAC,QAAA,CAAS,UAAA,EAAY,IAAA,EAAM;AAC9B,MAAA,MAAM,wBAAwB,MAAM,CAAA;AAAA,IACtC;AACA,IAAA,OAAO,IAAI,gBAAA,CAAiB,MAAA,EAAQ,MAAM,CAAA;AAAA,EAC5C;AAAA,EAEiB,MAAA;AAAA,EACA,MAAA;AAAA,EAET,WAAA,CAAY,QAAc,MAAA,EAAuB;AACvD,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,MAAM,OAAO,OAAA,EAIV;AACD,IAAA,MAAM,IAAA,CAAK,MAAA,CAAY,KAAK,CAAA,CAAE,MAAA,CAAO;AAAA,MACnC,EAAA,EAAI,QAAQ,GAAA,CAAI,GAAA;AAAA,MAChB,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AAAA,MAC/B,UAAA,EAAY,OAAA,CAAQ,SAAA,CAAU,WAAA;AAAY,KAC3C,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,QAAA,GAAW;AACf,IAAA,MAAM,OAAO,MAAM,IAAA,CAAK,MAAA,CAAY,KAAK,EAAE,MAAA,EAAO;AAClD,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,MAAQ;AAAA,MAC5B,IAAI,GAAA,CAAI,EAAA;AAAA,MACR,GAAA,EAAK,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAAA,MACvB,SAAA,EAAW,IAAI,IAAA,CAAK,GAAA,CAAI,UAAU;AAAA,KACpC,CAAE,CAAA;AAEF,IAAA,MAAM,YAAY,EAAC;AACnB,IAAA,MAAM,cAAc,EAAC;AAErB,IAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,MAAA,IAAIC,eAAS,UAAA,CAAW,GAAA,CAAI,SAAS,CAAA,GAAIA,cAAA,CAAS,OAAM,EAAG;AACzD,QAAA,WAAA,CAAY,KAAK,GAAG,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,KAAK,GAAG,CAAA;AAAA,MACpB;AAAA,IACF;AAGA,IAAA,IAAI,WAAA,CAAY,SAAS,CAAA,EAAG;AAC1B,MAAA,MAAM,IAAA,GAAO,YAAY,GAAA,CAAI,CAAC,EAAE,GAAA,EAAI,KAAM,IAAI,GAAG,CAAA;AAEjD,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,CAAA,uCAAA,EAA0C,IAAA,CAAK,IAAA,CAAK,MAAM,CAAC,CAAA,CAAA;AAAA,OAC7D;AAGA,MAAA,IAAA,CAAK,MAAA,CAAY,KAAK,CAAA,CACnB,MAAA,EAAO,CACP,QAAQ,IAAA,EAAM,IAAI,CAAA,CAClB,KAAA,CAAM,CAAA,KAAA,KAAS;AACd,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,UACV,8CAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF,CAAC,CAAA;AAAA,IACL;AAEA,IAAA,OAAO,EAAE,MAAM,SAAA,EAAU;AAAA,EAC3B;AACF;;;;;;"}
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 readonly client: Knex;\n private readonly logger: LoggerService;\n\n private constructor(client: Knex, logger: LoggerService) {\n this.client = client;\n this.logger = logger;\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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"}
@@ -1 +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 { randomUUID as uuid } from 'node:crypto';\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 private readonly keyStore: KeyStore;\n private readonly logger: LoggerService;\n private readonly keyDurationSeconds: number;\n private readonly algorithm: string;\n\n constructor(\n keyStore: KeyStore,\n logger: LoggerService,\n keyDurationSeconds: number,\n algorithm: string,\n ) {\n this.keyStore = keyStore;\n this.logger = logger;\n this.keyDurationSeconds = keyDurationSeconds;\n this.algorithm = algorithm;\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 unknown 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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"}
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 { randomUUID as uuid } from 'node:crypto';\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 private readonly keyStore: KeyStore;\n private readonly logger: LoggerService;\n private readonly keyDurationSeconds: number;\n private readonly algorithm: string;\n\n constructor(\n keyStore: KeyStore,\n logger: LoggerService,\n keyDurationSeconds: number,\n algorithm: string,\n ) {\n this.keyStore = keyStore;\n this.logger = logger;\n this.keyDurationSeconds = keyDurationSeconds;\n this.algorithm = algorithm;\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 unknown 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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"}
@@ -1 +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 'node:fs';\nimport { JWK, exportJWK, importPKCS8, importSPKI } from 'jose';\nimport { KeyLike } from 'jose';\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 readonly keyPairs: KeyPair[];\n private readonly keyDurationSeconds: number;\n\n private constructor(keyPairs: KeyPair[], keyDurationSeconds: number) {\n this.keyPairs = keyPairs;\n this.keyDurationSeconds = keyDurationSeconds;\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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"}
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 'node:fs';\nimport { JWK, exportJWK, importPKCS8, importSPKI } from 'jose';\nimport { KeyLike } from 'jose';\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 readonly keyPairs: KeyPair[];\n private readonly keyDurationSeconds: number;\n\n private constructor(keyPairs: KeyPair[], keyDurationSeconds: number) {\n this.keyPairs = keyPairs;\n this.keyDurationSeconds = keyDurationSeconds;\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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"}
@@ -1 +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,eAAA,GAAkB,6BAAA;AAExB,eAAsB,sBAAsB,OAAA,EAMf;AAC3B,EAAA,MAAM,cAAA,GAAiB,OAAA,CAAQ,MAAA,CAAO,iBAAA,CAAkB,eAAe,CAAA;AACvE,EAAA,MAAM,IAAA,GAAO,cAAA,EAAgB,iBAAA,CAAkB,MAAM,CAAA,IAAK,UAAA;AAE1D,EAAA,IAAI,CAAC,cAAA,IAAkB,IAAA,KAAS,UAAA,EAAY;AAC1C,IAAA,OAAOA,gDAAwB,MAAA,CAAO;AAAA,MACpC,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,WAAW,OAAA,CAAQ;AAAA,KACpB,CAAA;AAAA,EACH,CAAA,MAAA,IAAW,SAAS,QAAA,EAAU;AAC5B,IAAA,OAAOC,wDAA4B,MAAA,CAAO;AAAA,MACxC,YAAA,EAAc,cAAA;AAAA,MACd,aAAa,OAAA,CAAQ;AAAA,KACtB,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA,yBAAA,EAA4B,eAAe,CAAA,OAAA,EAAU,IAAI,CAAA,uCAAA;AAAA,GAC3D;AACF;;;;"}
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,eAAA,GAAkB,6BAAA;AAExB,eAAsB,sBAAsB,OAAA,EAMf;AAC3B,EAAA,MAAM,cAAA,GAAiB,OAAA,CAAQ,MAAA,CAAO,iBAAA,CAAkB,eAAe,CAAA;AACvE,EAAA,MAAM,IAAA,GAAO,cAAA,EAAgB,iBAAA,CAAkB,MAAM,CAAA,IAAK,UAAA;AAE1D,EAAA,IAAI,CAAC,cAAA,IAAkB,IAAA,KAAS,UAAA,EAAY;AAC1C,IAAA,OAAOA,gDAAwB,MAAA,CAAO;AAAA,MACpC,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,WAAW,OAAA,CAAQ;AAAA,KACpB,CAAA;AAAA,EACH,CAAA,MAAA,IAAW,SAAS,QAAA,EAAU;AAC5B,IAAA,OAAOC,wDAA4B,MAAA,CAAO;AAAA,MACxC,YAAA,EAAc,cAAA;AAAA,MACd,aAAa,OAAA,CAAQ;AAAA,KACtB,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA,yBAAA,EAA4B,eAAe,CAAA,OAAA,EAAU,IAAI,CAAA,uCAAA;AAAA,GAC3D;AACF;;"}