@backstage/backend-defaults 0.16.1-next.1 → 0.16.1-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/config.d.ts +1 -1
  3. package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js +1 -1
  4. package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js.map +1 -1
  5. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +4 -2
  6. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
  7. package/dist/entrypoints/discovery/HostDiscovery.cjs.js +14 -0
  8. package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -1
  9. package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js +2 -5
  10. package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js.map +1 -1
  11. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js +2 -2
  12. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -1
  13. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js +2 -2
  14. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -1
  15. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js +2 -3
  16. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -1
  17. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +2 -2
  18. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -1
  19. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +2 -2
  20. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -1
  21. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +2 -2
  22. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
  23. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +2 -2
  24. package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
  25. package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js +2 -2
  26. package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js.map +1 -1
  27. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +2 -2
  28. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -1
  29. package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js +2 -2
  30. package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js.map +1 -1
  31. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +2 -2
  32. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
  33. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js +2 -2
  34. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -1
  35. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js +2 -2
  36. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -1
  37. package/dist/package.json.cjs.js +1 -1
  38. package/package.json +16 -16
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @backstage/backend-defaults
2
2
 
3
+ ## 0.16.1-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 482ceed: Migrated from `assertError` to `toError` for error handling.
8
+ - 308c672: `HostDiscovery` now logs a warning when `backend.baseUrl` is set to a localhost address while `NODE_ENV` is `production`, and when `backend.baseUrl` is not a valid URL.
9
+ - 85c5a46: DefaultActionsRegistryService: add json middleware to /.backstage/actions/ routes only
10
+ - f14df56: Added experimental support for using `embedded-postgres` as the database for local development. Set `backend.database.client` to `embedded-postgres` in your app config to enable this. The `embedded-postgres` package must be installed as an explicit dependency in your project.
11
+ - Updated dependencies
12
+ - @backstage/errors@1.3.0-next.0
13
+ - @backstage/plugin-auth-node@0.7.0-next.2
14
+ - @backstage/backend-app-api@1.6.1-next.2
15
+ - @backstage/cli-node@0.3.1-next.1
16
+ - @backstage/config-loader@1.10.10-next.1
17
+ - @backstage/integration@2.0.1-next.0
18
+ - @backstage/backend-plugin-api@1.9.0-next.2
19
+ - @backstage/config@1.3.7-next.0
20
+ - @backstage/integration-aws-node@0.1.21-next.0
21
+ - @backstage/plugin-events-node@0.4.21-next.2
22
+ - @backstage/plugin-permission-common@0.9.8-next.0
23
+ - @backstage/plugin-permission-node@0.10.12-next.2
24
+
3
25
  ## 0.16.1-next.1
4
26
 
5
27
  ### Patch Changes
package/config.d.ts CHANGED
@@ -585,7 +585,7 @@ export interface Config {
585
585
  /** Database connection configuration, select base database type using the `client` field */
586
586
  database: {
587
587
  /** Default database client to use */
588
- client: 'better-sqlite3' | 'sqlite3' | 'pg';
588
+ client: 'better-sqlite3' | 'sqlite3' | 'pg' | 'embedded-postgres';
589
589
  /**
590
590
  * Base database connection string, or object with individual connection properties
591
591
  * @visibility secret
@@ -47,7 +47,7 @@ class DefaultActionsRegistryService {
47
47
  }
48
48
  createRouter() {
49
49
  const router = Router__default.default();
50
- router.use(express.json());
50
+ router.use("/.backstage/actions/", express.json());
51
51
  router.get("/.backstage/actions/v1/actions", async (req, res) => {
52
52
  const credentials = await this.httpAuth.credentials(req);
53
53
  const entries = Array.from(this.actions.entries());
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.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 {\n AuthService,\n BackstageCredentials,\n HttpAuthService,\n LoggerService,\n PermissionsRegistryService,\n PermissionsService,\n PluginMetadataService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod/v3';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\n\ntype ActionEntry = [string, ActionsRegistryActionOptions<any, any>];\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly metadata: PluginMetadataService;\n private readonly permissions: PermissionsService;\n private readonly permissionsRegistry: PermissionsRegistryService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n metadata: PluginMetadataService,\n permissions: PermissionsService,\n permissionsRegistry: PermissionsRegistryService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.metadata = metadata;\n this.permissions = permissions;\n this.permissionsRegistry = permissionsRegistry;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n metadata: PluginMetadataService;\n permissions: PermissionsService;\n permissionsRegistry: PermissionsRegistryService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(\n logger,\n httpAuth,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n );\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use(json());\n\n router.get('/.backstage/actions/v1/actions', async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n const entries = Array.from(this.actions.entries());\n\n const allowedActions = await this.filterByPermissions(\n entries,\n credentials,\n );\n\n return res.json({\n actions: allowedActions.map(([id, action]) => ({\n id,\n name: action.name,\n title: action.title,\n description: action.description,\n pluginId: this.metadata.getId(),\n attributes: {\n // Inspired by the @modelcontextprotocol/sdk defaults for the hints.\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812\n destructive: action.attributes?.destructive ?? true,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n },\n examples: action.examples,\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n },\n })),\n });\n });\n\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by an authenticated principal, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n if (action.visibilityPermission) {\n const [decision] = await this.permissions.authorize(\n [{ permission: action.visibilityPermission }],\n { credentials },\n );\n if (decision.result !== AuthorizeResult.ALLOW) {\n throw new NotFoundError(\n `Action \"${req.params.actionId}\" not found`,\n );\n }\n }\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(req.body)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n },\n );\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n >(options: ActionsRegistryActionOptions<TInputSchema, TOutputSchema>): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n if (options.visibilityPermission) {\n this.permissionsRegistry.addPermissions([options.visibilityPermission]);\n }\n\n this.actions.set(id, options);\n }\n\n private async filterByPermissions(\n entries: ActionEntry[],\n credentials: BackstageCredentials,\n ): Promise<ActionEntry[]> {\n const permissionedEntries = entries.filter(\n ([_, action]) => action.visibilityPermission,\n );\n\n if (permissionedEntries.length === 0) {\n return entries;\n }\n\n const decisions = await this.permissions.authorize(\n permissionedEntries.map(([_, action]) => ({\n permission: action.visibilityPermission!,\n })),\n { credentials },\n );\n\n const deniedIds = new Set(\n permissionedEntries\n .filter((_, index) => decisions[index].result !== AuthorizeResult.ALLOW)\n .map(([id]) => id),\n );\n\n return entries.filter(([id]) => !deniedIds.has(id));\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","AuthorizeResult","InputError"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.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 {\n AuthService,\n BackstageCredentials,\n HttpAuthService,\n LoggerService,\n PermissionsRegistryService,\n PermissionsService,\n PluginMetadataService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod/v3';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\n\ntype ActionEntry = [string, ActionsRegistryActionOptions<any, any>];\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly metadata: PluginMetadataService;\n private readonly permissions: PermissionsService;\n private readonly permissionsRegistry: PermissionsRegistryService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n metadata: PluginMetadataService,\n permissions: PermissionsService,\n permissionsRegistry: PermissionsRegistryService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.metadata = metadata;\n this.permissions = permissions;\n this.permissionsRegistry = permissionsRegistry;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n metadata: PluginMetadataService;\n permissions: PermissionsService;\n permissionsRegistry: PermissionsRegistryService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(\n logger,\n httpAuth,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n );\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use('/.backstage/actions/', json());\n\n router.get('/.backstage/actions/v1/actions', async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n const entries = Array.from(this.actions.entries());\n\n const allowedActions = await this.filterByPermissions(\n entries,\n credentials,\n );\n\n return res.json({\n actions: allowedActions.map(([id, action]) => ({\n id,\n name: action.name,\n title: action.title,\n description: action.description,\n pluginId: this.metadata.getId(),\n attributes: {\n // Inspired by the @modelcontextprotocol/sdk defaults for the hints.\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812\n destructive: action.attributes?.destructive ?? true,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n },\n examples: action.examples,\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n },\n })),\n });\n });\n\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by an authenticated principal, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n if (action.visibilityPermission) {\n const [decision] = await this.permissions.authorize(\n [{ permission: action.visibilityPermission }],\n { credentials },\n );\n if (decision.result !== AuthorizeResult.ALLOW) {\n throw new NotFoundError(\n `Action \"${req.params.actionId}\" not found`,\n );\n }\n }\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(req.body)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n },\n );\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n >(options: ActionsRegistryActionOptions<TInputSchema, TOutputSchema>): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n if (options.visibilityPermission) {\n this.permissionsRegistry.addPermissions([options.visibilityPermission]);\n }\n\n this.actions.set(id, options);\n }\n\n private async filterByPermissions(\n entries: ActionEntry[],\n credentials: BackstageCredentials,\n ): Promise<ActionEntry[]> {\n const permissionedEntries = entries.filter(\n ([_, action]) => action.visibilityPermission,\n );\n\n if (permissionedEntries.length === 0) {\n return entries;\n }\n\n const decisions = await this.permissions.authorize(\n permissionedEntries.map(([_, action]) => ({\n permission: action.visibilityPermission!,\n })),\n { credentials },\n );\n\n const deniedIds = new Set(\n permissionedEntries\n .filter((_, index) => decisions[index].result !== AuthorizeResult.ALLOW)\n .map(([id]) => id),\n );\n\n return entries.filter(([id]) => !deniedIds.has(id));\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","AuthorizeResult","InputError"],"mappings":"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"}
@@ -118,8 +118,10 @@ class DefaultPluginTokenHandler {
118
118
  this.supportedTargetPlugins.add(targetPluginId);
119
119
  return true;
120
120
  } catch (error) {
121
- errors.assertError(error);
122
- this.logger.error("Unexpected failure for target JWKS check", error);
121
+ this.logger.error(
122
+ "Unexpected failure for target JWKS check",
123
+ errors.toError(error)
124
+ );
123
125
  return false;
124
126
  } finally {
125
127
  this.targetPluginInflightChecks.delete(targetPluginId);
@@ -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 { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.io/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private readonly logger: LoggerService;\n private readonly ownPluginId: string;\n private readonly keySource: PluginKeySource;\n private readonly algorithm: string;\n private readonly keyDurationSeconds: number;\n private readonly discovery: DiscoveryService;\n\n private constructor(\n logger: LoggerService,\n ownPluginId: string,\n keySource: PluginKeySource,\n algorithm: string,\n keyDurationSeconds: number,\n discovery: DiscoveryService,\n ) {\n this.logger = logger;\n this.ownPluginId = ownPluginId;\n this.keySource = keySource;\n this.algorithm = algorithm;\n this.keyDurationSeconds = keyDurationSeconds;\n this.discovery = discovery;\n }\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
1
+ {"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { AuthenticationError, toError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.io/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private readonly logger: LoggerService;\n private readonly ownPluginId: string;\n private readonly keySource: PluginKeySource;\n private readonly algorithm: string;\n private readonly keyDurationSeconds: number;\n private readonly discovery: DiscoveryService;\n\n private constructor(\n logger: LoggerService,\n ownPluginId: string,\n keySource: PluginKeySource,\n algorithm: string,\n keyDurationSeconds: number,\n discovery: DiscoveryService,\n ) {\n this.logger = logger;\n this.ownPluginId = ownPluginId;\n this.keySource = keySource;\n this.algorithm = algorithm;\n this.keyDurationSeconds = keyDurationSeconds;\n this.discovery = discovery;\n }\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n this.logger.error(\n 'Unexpected failure for target JWKS check',\n toError(error),\n );\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","toError","JwksClient"],"mappings":"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"}
@@ -16,6 +16,20 @@ class HostDiscovery {
16
16
  throw new Error("Not initialized");
17
17
  };
18
18
  static fromConfig(config, options) {
19
+ const baseUrl = config.getString("backend.baseUrl");
20
+ try {
21
+ const { hostname } = new URL(baseUrl);
22
+ const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "::";
23
+ if (isLocalhost && process.env.NODE_ENV === "production") {
24
+ options?.logger?.warn(
25
+ `backend.baseUrl is set to a localhost URL and NODE_ENV is '${process.env.NODE_ENV}'. This is likely a misconfiguration \u2014 localhost URLs are not reachable by other services in a deployed environment. Prefer setting it to a routable URL that can be resolved and reached both by your app and by other plugin deployments / services.`
26
+ );
27
+ }
28
+ } catch {
29
+ options?.logger?.warn(
30
+ `backend.baseUrl config value '${baseUrl}' does not appear to be a valid URL.`
31
+ );
32
+ }
19
33
  const discovery = new HostDiscovery(new SrvResolvers.SrvResolvers());
20
34
  discovery.#updateResolvers(config, options?.defaultEndpoints);
21
35
  config.subscribe?.(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\nimport { SrvResolvers } from './SrvResolvers';\nimport { trimEnd } from 'lodash';\nimport { getEndpoints } from './parsing';\n\ntype Resolver = (pluginId: string) => Promise<string>;\n\n/**\n * A list of target base URLs and their associated plugins.\n *\n * @public\n */\nexport interface HostDiscoveryEndpoint {\n /**\n * The target base URL to use for the given set of plugins. Note that this\n * needs to be a full URL _including_ the protocol and path parts that fully\n * address the root of a plugin's API endpoints.\n *\n * @remarks\n *\n * Can be either a single URL or an object where you can explicitly give a\n * dedicated URL for internal (as seen from the backend) and/or external (as\n * seen from the frontend) lookups.\n *\n * The default behavior is to use the backend base URL for external lookups,\n * and a URL formed from the `.listen` and `.https` configs for internal\n * lookups. Adding discovery endpoints as described here overrides one or both\n * of those behaviors for a given set of plugins.\n *\n * URLs can be in the form of a regular HTTP or HTTPS URL if you are using\n * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if\n * you add `+src` to the protocol part then the hostname is treated as an SRV\n * record name and resolved. For example, if you pass in\n * `http+srv://<record>/path` then the record part is resolved into an\n * actual host and port (with random weighted choice as usual when there is\n * more than one match).\n *\n * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them\n * will have them replaced with the plugin ID.\n *\n * Example URLs:\n *\n * - `https://internal.example.com/secure/api/{{ pluginId }}`\n * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`\n * (can only be used in the `internal` key)\n */\n target:\n | string\n | {\n internal?: string;\n external?: string;\n };\n\n /**\n * Array of plugins which use that target base URL.\n *\n * The special value `*` can be used to match all plugins.\n */\n plugins: string[];\n}\n\n/**\n * Options for the {@link HostDiscovery} class.\n *\n * @public\n */\nexport interface HostDiscoveryOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * A default set of endpoints to use.\n *\n * @remarks\n *\n * These endpoints have lower priority than any that are defined in\n * app-config, but higher priority than the fallback ones.\n *\n * This parameter is usedful for example if you want to provide a shared\n * library of core services to your plugin developers, which is set up for the\n * default behaviors in your org. This alleviates the need for replicating any\n * given set of endpoint config in every backend that you deploy.\n */\n defaultEndpoints?: HostDiscoveryEndpoint[];\n}\n\n/**\n * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * @public\n * @remarks\n *\n * Configuration is read from the `backend` config section, specifically the\n * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`\n * config for the internal one. The fixed base path for these is `/api`, meaning\n * for example the default full internal path for the `catalog` plugin typically\n * will be `http://localhost:7007/api/catalog`.\n *\n * Those defaults can be overridden by providing a target and corresponding\n * plugins in `discovery.endpoints`, e.g.:\n *\n * ```yaml\n * discovery:\n * endpoints:\n * # Set a static internal and external base URL for a plugin\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * # Sets a dynamic internal and external base URL pattern for two plugins\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * # Sets a dynamic base URL pattern for only the internal resolution for all\n * # other plugins, while leaving the external resolution unaffected\n * - target:\n * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}\n * plugins: [*]\n * ```\n */\nexport class HostDiscovery implements DiscoveryService {\n #srvResolver: SrvResolvers;\n #internalResolvers: Map<string, Resolver> = new Map();\n #externalResolvers: Map<string, Resolver> = new Map();\n #internalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n #externalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n\n static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions) {\n const discovery = new HostDiscovery(new SrvResolvers());\n\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n config.subscribe?.(() => {\n try {\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n } catch (e) {\n options?.logger.error(`Failed to update discovery service: ${e}`);\n }\n });\n\n return discovery;\n }\n\n private constructor(srvResolver: SrvResolvers) {\n this.#srvResolver = srvResolver;\n this.#internalResolvers = new Map();\n this.#externalResolvers = new Map();\n this.#internalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n this.#externalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#internalResolvers.get(pluginId) ??\n this.#internalResolvers.get('*') ??\n this.#internalFallbackResolver;\n return await resolver(pluginId);\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#externalResolvers.get(pluginId) ??\n this.#externalResolvers.get('*') ??\n this.#externalFallbackResolver;\n return await resolver(pluginId);\n }\n\n #updateResolvers(config: Config, defaultEndpoints?: HostDiscoveryEndpoint[]) {\n this.#updateFallbackResolvers(config);\n this.#updatePluginResolvers(config, defaultEndpoints);\n }\n\n #updateFallbackResolvers(config: Config) {\n const backendBaseUrl = trimEnd(config.getString('backend.baseUrl'), '/');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n this.#internalFallbackResolver = this.#makeResolver(\n `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,\n false,\n );\n this.#externalFallbackResolver = this.#makeResolver(\n `${backendBaseUrl}/api/{{pluginId}}`,\n false,\n );\n }\n\n #updatePluginResolvers(\n config: Config,\n defaultEndpoints?: HostDiscoveryEndpoint[],\n ) {\n // Start out with the default endpoints, if any\n const endpoints = defaultEndpoints?.slice() ?? [];\n\n // Allow config to override the default endpoints\n endpoints.push(...getEndpoints(config));\n\n // Build up a new set of resolvers\n const internalResolvers: Map<string, Resolver> = new Map();\n const externalResolvers: Map<string, Resolver> = new Map();\n for (const { target, plugins } of endpoints) {\n let internalResolver: Resolver | undefined;\n let externalResolver: Resolver | undefined;\n\n if (typeof target === 'string') {\n internalResolver = externalResolver = this.#makeResolver(target, false);\n } else {\n if (target.internal) {\n internalResolver = this.#makeResolver(target.internal, true);\n }\n if (target.external) {\n externalResolver = this.#makeResolver(target.external, false);\n }\n }\n\n if (internalResolver) {\n for (const pluginId of plugins) {\n internalResolvers.set(pluginId, internalResolver);\n }\n }\n if (externalResolver) {\n for (const pluginId of plugins) {\n externalResolvers.set(pluginId, externalResolver);\n }\n }\n }\n\n // Only persist if no errors were thrown above\n this.#internalResolvers = internalResolvers;\n this.#externalResolvers = externalResolvers;\n }\n\n #makeResolver(urlPattern: string, allowSrv: boolean): Resolver {\n const withPluginId = (pluginId: string, url: string) => {\n return url.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n };\n\n if (!this.#srvResolver.isSrvUrl(urlPattern)) {\n return async pluginId => withPluginId(pluginId, urlPattern);\n }\n\n if (!allowSrv) {\n throw new Error(\n `SRV resolver URLs cannot be used in the target for external endpoints`,\n );\n }\n\n const lazyResolvers = new Map<string, () => Promise<string>>();\n return async pluginId => {\n let lazyResolver = lazyResolvers.get(pluginId);\n if (!lazyResolver) {\n lazyResolver = this.#srvResolver.getResolver(\n withPluginId(pluginId, urlPattern),\n );\n lazyResolvers.set(pluginId, lazyResolver);\n }\n return await lazyResolver();\n };\n }\n}\n"],"names":["SrvResolvers","config","trimEnd","readHttpServerOptions","getEndpoints"],"mappings":"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"}
1
+ {"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\nimport { SrvResolvers } from './SrvResolvers';\nimport { trimEnd } from 'lodash';\nimport { getEndpoints } from './parsing';\n\ntype Resolver = (pluginId: string) => Promise<string>;\n\n/**\n * A list of target base URLs and their associated plugins.\n *\n * @public\n */\nexport interface HostDiscoveryEndpoint {\n /**\n * The target base URL to use for the given set of plugins. Note that this\n * needs to be a full URL _including_ the protocol and path parts that fully\n * address the root of a plugin's API endpoints.\n *\n * @remarks\n *\n * Can be either a single URL or an object where you can explicitly give a\n * dedicated URL for internal (as seen from the backend) and/or external (as\n * seen from the frontend) lookups.\n *\n * The default behavior is to use the backend base URL for external lookups,\n * and a URL formed from the `.listen` and `.https` configs for internal\n * lookups. Adding discovery endpoints as described here overrides one or both\n * of those behaviors for a given set of plugins.\n *\n * URLs can be in the form of a regular HTTP or HTTPS URL if you are using\n * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if\n * you add `+src` to the protocol part then the hostname is treated as an SRV\n * record name and resolved. For example, if you pass in\n * `http+srv://<record>/path` then the record part is resolved into an\n * actual host and port (with random weighted choice as usual when there is\n * more than one match).\n *\n * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them\n * will have them replaced with the plugin ID.\n *\n * Example URLs:\n *\n * - `https://internal.example.com/secure/api/{{ pluginId }}`\n * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`\n * (can only be used in the `internal` key)\n */\n target:\n | string\n | {\n internal?: string;\n external?: string;\n };\n\n /**\n * Array of plugins which use that target base URL.\n *\n * The special value `*` can be used to match all plugins.\n */\n plugins: string[];\n}\n\n/**\n * Options for the {@link HostDiscovery} class.\n *\n * @public\n */\nexport interface HostDiscoveryOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * A default set of endpoints to use.\n *\n * @remarks\n *\n * These endpoints have lower priority than any that are defined in\n * app-config, but higher priority than the fallback ones.\n *\n * This parameter is usedful for example if you want to provide a shared\n * library of core services to your plugin developers, which is set up for the\n * default behaviors in your org. This alleviates the need for replicating any\n * given set of endpoint config in every backend that you deploy.\n */\n defaultEndpoints?: HostDiscoveryEndpoint[];\n}\n\n/**\n * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * @public\n * @remarks\n *\n * Configuration is read from the `backend` config section, specifically the\n * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`\n * config for the internal one. The fixed base path for these is `/api`, meaning\n * for example the default full internal path for the `catalog` plugin typically\n * will be `http://localhost:7007/api/catalog`.\n *\n * Those defaults can be overridden by providing a target and corresponding\n * plugins in `discovery.endpoints`, e.g.:\n *\n * ```yaml\n * discovery:\n * endpoints:\n * # Set a static internal and external base URL for a plugin\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * # Sets a dynamic internal and external base URL pattern for two plugins\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * # Sets a dynamic base URL pattern for only the internal resolution for all\n * # other plugins, while leaving the external resolution unaffected\n * - target:\n * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}\n * plugins: [*]\n * ```\n */\nexport class HostDiscovery implements DiscoveryService {\n #srvResolver: SrvResolvers;\n #internalResolvers: Map<string, Resolver> = new Map();\n #externalResolvers: Map<string, Resolver> = new Map();\n #internalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n #externalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n\n static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions) {\n // The getExternalBaseUrl implementation relies on the backend base URL\n // being a valid, non-local URL that others will be able to route to.\n const baseUrl = config.getString('backend.baseUrl');\n try {\n const { hostname } = new URL(baseUrl);\n const isLocalhost =\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '::1' ||\n hostname === '::';\n if (isLocalhost && process.env.NODE_ENV === 'production') {\n options?.logger?.warn(\n `backend.baseUrl is set to a localhost URL and NODE_ENV is '${process.env.NODE_ENV}'. This is likely a misconfiguration — localhost URLs are not reachable by other services in a deployed environment. Prefer setting it to a routable URL that can be resolved and reached both by your app and by other plugin deployments / services.`,\n );\n }\n } catch {\n options?.logger?.warn(\n `backend.baseUrl config value '${baseUrl}' does not appear to be a valid URL.`,\n );\n }\n\n const discovery = new HostDiscovery(new SrvResolvers());\n\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n config.subscribe?.(() => {\n try {\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n } catch (e) {\n options?.logger.error(`Failed to update discovery service: ${e}`);\n }\n });\n\n return discovery;\n }\n\n private constructor(srvResolver: SrvResolvers) {\n this.#srvResolver = srvResolver;\n this.#internalResolvers = new Map();\n this.#externalResolvers = new Map();\n this.#internalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n this.#externalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#internalResolvers.get(pluginId) ??\n this.#internalResolvers.get('*') ??\n this.#internalFallbackResolver;\n return await resolver(pluginId);\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#externalResolvers.get(pluginId) ??\n this.#externalResolvers.get('*') ??\n this.#externalFallbackResolver;\n return await resolver(pluginId);\n }\n\n #updateResolvers(config: Config, defaultEndpoints?: HostDiscoveryEndpoint[]) {\n this.#updateFallbackResolvers(config);\n this.#updatePluginResolvers(config, defaultEndpoints);\n }\n\n #updateFallbackResolvers(config: Config) {\n const backendBaseUrl = trimEnd(config.getString('backend.baseUrl'), '/');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n this.#internalFallbackResolver = this.#makeResolver(\n `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,\n false,\n );\n this.#externalFallbackResolver = this.#makeResolver(\n `${backendBaseUrl}/api/{{pluginId}}`,\n false,\n );\n }\n\n #updatePluginResolvers(\n config: Config,\n defaultEndpoints?: HostDiscoveryEndpoint[],\n ) {\n // Start out with the default endpoints, if any\n const endpoints = defaultEndpoints?.slice() ?? [];\n\n // Allow config to override the default endpoints\n endpoints.push(...getEndpoints(config));\n\n // Build up a new set of resolvers\n const internalResolvers: Map<string, Resolver> = new Map();\n const externalResolvers: Map<string, Resolver> = new Map();\n for (const { target, plugins } of endpoints) {\n let internalResolver: Resolver | undefined;\n let externalResolver: Resolver | undefined;\n\n if (typeof target === 'string') {\n internalResolver = externalResolver = this.#makeResolver(target, false);\n } else {\n if (target.internal) {\n internalResolver = this.#makeResolver(target.internal, true);\n }\n if (target.external) {\n externalResolver = this.#makeResolver(target.external, false);\n }\n }\n\n if (internalResolver) {\n for (const pluginId of plugins) {\n internalResolvers.set(pluginId, internalResolver);\n }\n }\n if (externalResolver) {\n for (const pluginId of plugins) {\n externalResolvers.set(pluginId, externalResolver);\n }\n }\n }\n\n // Only persist if no errors were thrown above\n this.#internalResolvers = internalResolvers;\n this.#externalResolvers = externalResolvers;\n }\n\n #makeResolver(urlPattern: string, allowSrv: boolean): Resolver {\n const withPluginId = (pluginId: string, url: string) => {\n return url.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n };\n\n if (!this.#srvResolver.isSrvUrl(urlPattern)) {\n return async pluginId => withPluginId(pluginId, urlPattern);\n }\n\n if (!allowSrv) {\n throw new Error(\n `SRV resolver URLs cannot be used in the target for external endpoints`,\n );\n }\n\n const lazyResolvers = new Map<string, () => Promise<string>>();\n return async pluginId => {\n let lazyResolver = lazyResolvers.get(pluginId);\n if (!lazyResolver) {\n lazyResolver = this.#srvResolver.getResolver(\n withPluginId(pluginId, urlPattern),\n );\n lazyResolvers.set(pluginId, lazyResolver);\n }\n return await lazyResolver();\n };\n }\n}\n"],"names":["SrvResolvers","config","trimEnd","readHttpServerOptions","getEndpoints"],"mappings":"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"}
@@ -11,11 +11,8 @@ function handleBadError(error, logger) {
11
11
  return newError;
12
12
  }
13
13
  function applyInternalErrorFilter(error, logger) {
14
- try {
15
- errors.assertError(error);
16
- } catch (assertionError) {
17
- errors.assertError(assertionError);
18
- return handleBadError(assertionError, logger);
14
+ if (!errors.isError(error)) {
15
+ return handleBadError(errors.toError(error), logger);
19
16
  }
20
17
  const constructorName = error.constructor.name;
21
18
  if (constructorName === "DatabaseError") {
@@ -1 +1 @@
1
- {"version":3,"file":"applyInternalErrorFilter.cjs.js","sources":["../../../../src/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { assertError } from '@backstage/errors';\nimport { randomBytes } from 'node:crypto';\n\nfunction handleBadError(error: Error, logger: LoggerService) {\n const logId = randomBytes(10).toString('hex');\n logger\n .child({ logId })\n .error(`Filtered internal error with logId=${logId} from response`, error);\n const newError = new Error(`An internal error occurred logId=${logId}`);\n delete newError.stack; // Trim the stack since it's not particularly useful\n return newError;\n}\n\n/**\n * Filters out certain known error types that should never be returned in responses.\n *\n * @internal\n */\nexport function applyInternalErrorFilter(\n error: unknown,\n logger: LoggerService,\n): Error {\n try {\n assertError(error);\n } catch (assertionError: unknown) {\n assertError(assertionError);\n return handleBadError(assertionError, logger);\n }\n\n const constructorName = error.constructor.name;\n\n // DatabaseError are thrown by the pg-protocol module\n if (constructorName === 'DatabaseError') {\n return handleBadError(error, logger);\n }\n\n return error;\n}\n"],"names":["randomBytes","assertError"],"mappings":";;;;;AAoBA,SAAS,cAAA,CAAe,OAAc,MAAA,EAAuB;AAC3D,EAAA,MAAM,KAAA,GAAQA,uBAAA,CAAY,EAAE,CAAA,CAAE,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAA,CACG,KAAA,CAAM,EAAE,KAAA,EAAO,EACf,KAAA,CAAM,CAAA,mCAAA,EAAsC,KAAK,CAAA,cAAA,CAAA,EAAkB,KAAK,CAAA;AAC3E,EAAA,MAAM,QAAA,GAAW,IAAI,KAAA,CAAM,CAAA,iCAAA,EAAoC,KAAK,CAAA,CAAE,CAAA;AACtE,EAAA,OAAO,QAAA,CAAS,KAAA;AAChB,EAAA,OAAO,QAAA;AACT;AAOO,SAAS,wBAAA,CACd,OACA,MAAA,EACO;AACP,EAAA,IAAI;AACF,IAAAC,kBAAA,CAAY,KAAK,CAAA;AAAA,EACnB,SAAS,cAAA,EAAyB;AAChC,IAAAA,kBAAA,CAAY,cAAc,CAAA;AAC1B,IAAA,OAAO,cAAA,CAAe,gBAAgB,MAAM,CAAA;AAAA,EAC9C;AAEA,EAAA,MAAM,eAAA,GAAkB,MAAM,WAAA,CAAY,IAAA;AAG1C,EAAA,IAAI,oBAAoB,eAAA,EAAiB;AACvC,IAAA,OAAO,cAAA,CAAe,OAAO,MAAM,CAAA;AAAA,EACrC;AAEA,EAAA,OAAO,KAAA;AACT;;;;"}
1
+ {"version":3,"file":"applyInternalErrorFilter.cjs.js","sources":["../../../../src/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { isError, toError } from '@backstage/errors';\nimport { randomBytes } from 'node:crypto';\n\nfunction handleBadError(error: Error, logger: LoggerService) {\n const logId = randomBytes(10).toString('hex');\n logger\n .child({ logId })\n .error(`Filtered internal error with logId=${logId} from response`, error);\n const newError = new Error(`An internal error occurred logId=${logId}`);\n delete newError.stack; // Trim the stack since it's not particularly useful\n return newError;\n}\n\n/**\n * Filters out certain known error types that should never be returned in responses.\n *\n * @internal\n */\nexport function applyInternalErrorFilter(\n error: unknown,\n logger: LoggerService,\n): Error {\n if (!isError(error)) {\n return handleBadError(toError(error), logger);\n }\n\n const constructorName = error.constructor.name;\n\n // DatabaseError are thrown by the pg-protocol module\n if (constructorName === 'DatabaseError') {\n return handleBadError(error, logger);\n }\n\n return error;\n}\n"],"names":["randomBytes","isError","toError"],"mappings":";;;;;AAoBA,SAAS,cAAA,CAAe,OAAc,MAAA,EAAuB;AAC3D,EAAA,MAAM,KAAA,GAAQA,uBAAA,CAAY,EAAE,CAAA,CAAE,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAA,CACG,KAAA,CAAM,EAAE,KAAA,EAAO,EACf,KAAA,CAAM,CAAA,mCAAA,EAAsC,KAAK,CAAA,cAAA,CAAA,EAAkB,KAAK,CAAA;AAC3E,EAAA,MAAM,QAAA,GAAW,IAAI,KAAA,CAAM,CAAA,iCAAA,EAAoC,KAAK,CAAA,CAAE,CAAA;AACtE,EAAA,OAAO,QAAA,CAAS,KAAA;AAChB,EAAA,OAAO,QAAA;AACT;AAOO,SAAS,wBAAA,CACd,OACA,MAAA,EACO;AACP,EAAA,IAAI,CAACC,cAAA,CAAQ,KAAK,CAAA,EAAG;AACnB,IAAA,OAAO,cAAA,CAAeC,cAAA,CAAQ,KAAK,CAAA,EAAG,MAAM,CAAA;AAAA,EAC9C;AAEA,EAAA,MAAM,eAAA,GAAkB,MAAM,WAAA,CAAY,IAAA;AAG1C,EAAA,IAAI,oBAAoB,eAAA,EAAiB;AACvC,IAAA,OAAO,cAAA,CAAe,OAAO,MAAM,CAAA;AAAA,EACrC;AAEA,EAAA,OAAO,KAAA;AACT;;;;"}
@@ -278,8 +278,8 @@ class AwsCodeCommitUrlReader {
278
278
  ],
279
279
  etag: data.etag ?? ""
280
280
  };
281
- } catch (error) {
282
- errors.assertError(error);
281
+ } catch (e) {
282
+ const error = errors.toError(e);
283
283
  if (error.name === "NotFoundError") {
284
284
  return {
285
285
  files: [],
@@ -1 +1 @@
1
- {"version":3,"file":"AwsCodeCommitUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n AwsCodeCommitIntegration,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n assertError,\n ForwardedError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport {\n CodeCommitClient,\n GetFileCommand,\n GetFileCommandInput,\n GetFileCommandOutput,\n GetFolderCommand,\n} from '@aws-sdk/client-codecommit';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport { Readable } from 'node:stream';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { relative } from 'node:path/posix';\nimport { AbortController } from '@aws-sdk/abort-controller';\n\nexport function parseUrl(\n url: string,\n requireGitPath: boolean = false,\n): {\n path: string;\n repositoryName: string;\n region: string;\n commitSpecifier?: string;\n} {\n const parsedUrl = new URL(url);\n\n if (parsedUrl.pathname.includes('/files/edit/')) {\n throw new Error(\n 'Please provide the view url to yaml file from CodeCommit, not the edit url',\n );\n }\n if (requireGitPath && !parsedUrl.pathname.includes('/browse/')) {\n throw new Error('Please provide full path to yaml file from CodeCommit');\n }\n\n const hostMatch = parsedUrl.host.match(\n /^([^\\.]+)\\.console\\.aws\\.amazon\\.com$/,\n );\n if (!hostMatch) {\n throw new Error(\n `Invalid AWS CodeCommit URL (unexpected host format): ${url}`,\n );\n }\n const [, region] = hostMatch;\n\n const pathMatch = parsedUrl.pathname.match(\n /^\\/codesuite\\/codecommit\\/repositories\\/([^\\/]+)\\/browse\\/((.*)\\/)?--\\/(.*)$/,\n );\n\n if (!pathMatch) {\n if (!requireGitPath) {\n const pathname = parsedUrl.pathname\n .split('/--/')[0]\n .replace('/codesuite/codecommit/repositories/', '');\n const [repositoryName, commitSpecifier] = pathname.split('/browse');\n\n return {\n region,\n repositoryName: repositoryName.replace(/^\\/|\\/$/g, ''),\n path: '/',\n commitSpecifier:\n commitSpecifier === ''\n ? undefined\n : commitSpecifier?.replace(/^\\/|\\/$/g, ''),\n };\n }\n throw new Error(\n `Invalid AWS CodeCommit URL (unexpected path format): ${url}`,\n );\n }\n const [, repositoryName, , commitSpecifier, path] = pathMatch;\n\n return {\n region,\n repositoryName,\n path,\n // the commitSpecifier is passed to AWS SDK which does not allow empty strings so replace empty string with undefined\n commitSpecifier: commitSpecifier === '' ? undefined : commitSpecifier,\n };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for AWS CodeCommit.\n *\n * @public\n */\nexport class AwsCodeCommitUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n\n return integrations.awsCodeCommit.list().map(integration => {\n const reader = new AwsCodeCommitUrlReader(credsManager, integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => {\n return (\n url.host.endsWith(integration.config.host) &&\n url.pathname.startsWith('/codesuite/codecommit')\n );\n };\n\n return { reader, predicate };\n });\n };\n\n private readonly credsManager: AwsCredentialsManager;\n private readonly integration: AwsCodeCommitIntegration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n };\n\n constructor(\n credsManager: AwsCredentialsManager,\n integration: AwsCodeCommitIntegration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {\n this.credsManager = credsManager;\n this.integration = integration;\n this.deps = deps;\n }\n\n /**\n * If accessKeyId and secretAccessKey are missing, the standard credentials provider chain will be used:\n * https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html\n */\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return {\n accessKeyId,\n secretAccessKey,\n };\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n region: string,\n integration?: AwsCodeCommitIntegration,\n ): Promise<AwsCredentialIdentityProvider> {\n // Fall back to the default credential chain if neither account ID\n // nor explicit credentials are provided\n if (!integration) {\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\n }\n\n const accessKeyId = integration.config.accessKeyId;\n const secretAccessKey = integration.config.secretAccessKey;\n let explicitCredentials: AwsCredentialIdentityProvider;\n if (accessKeyId && secretAccessKey) {\n explicitCredentials = AwsCodeCommitUrlReader.buildStaticCredentials(\n accessKeyId,\n secretAccessKey,\n );\n } else {\n explicitCredentials = (await credsManager.getCredentialProvider())\n .sdkCredentialProvider;\n }\n\n const roleArn = integration.config.roleArn;\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-code-commit-url-reader',\n RoleArn: roleArn,\n ExternalId: integration.config.externalId,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n\n private async buildCodeCommitClient(\n credsManager: AwsCredentialsManager,\n region: string,\n integration: AwsCodeCommitIntegration,\n ): Promise<CodeCommitClient> {\n const credentials = await AwsCodeCommitUrlReader.buildCredentials(\n credsManager,\n region,\n integration,\n );\n\n const codeCommit = new CodeCommitClient({\n customUserAgent: 'backstage-aws-codecommit-url-reader',\n region: region,\n credentials: credentials,\n });\n return codeCommit;\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n // etag and lastModifiedAfter are not supported by the CodeCommit API\n try {\n const { path, repositoryName, region, commitSpecifier } = parseUrl(\n url,\n true,\n );\n const codeCommitClient = await this.buildCodeCommitClient(\n this.credsManager,\n region,\n this.integration,\n );\n const abortController = new AbortController();\n\n const input: GetFileCommandInput = {\n repositoryName: repositoryName,\n commitSpecifier: commitSpecifier,\n filePath: path,\n };\n\n options?.signal?.addEventListener('abort', () => abortController.abort());\n const getObjectCommand = new GetFileCommand(input);\n const response: GetFileCommandOutput = await codeCommitClient.send(\n getObjectCommand,\n {\n abortSignal: abortController.signal,\n },\n );\n\n if (options?.etag && options.etag === response.commitId) {\n throw new NotModifiedError();\n }\n\n return ReadUrlResponseFactory.fromReadable(\n Readable.from([response?.fileContent]),\n {\n etag: response.commitId,\n },\n );\n } catch (e) {\n if (e.$metadata && e.$metadata.httpStatusCode === 304) {\n throw new NotModifiedError();\n }\n if (e.name && e.name === 'NotModifiedError') {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError('Could not retrieve file from CodeCommit', e);\n }\n }\n\n async readTreePath(\n codeCommitClient: CodeCommitClient,\n abortSignal: any,\n path: string,\n repositoryName: string,\n commitSpecifier?: string,\n etag?: string,\n ): Promise<string[]> {\n const getFolderCommand = new GetFolderCommand({\n folderPath: path,\n repositoryName: repositoryName,\n commitSpecifier: commitSpecifier,\n });\n const response = await codeCommitClient.send(getFolderCommand, {\n abortSignal: abortSignal,\n });\n\n if (etag && etag === response.commitId) {\n throw new NotModifiedError();\n }\n\n const output: string[] = [];\n if (response.files) {\n response.files.forEach(file => {\n if (file.absolutePath) {\n output.push(file.absolutePath);\n }\n });\n }\n if (!response.subFolders) {\n return output;\n }\n\n for (const subFolder of response.subFolders) {\n if (subFolder.absolutePath) {\n output.push(\n ...(await this.readTreePath(\n codeCommitClient,\n abortSignal,\n subFolder.absolutePath,\n repositoryName,\n commitSpecifier,\n etag,\n )),\n );\n }\n }\n return output;\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n // url: https://eu-west-1.console.aws.amazon.com/codesuite/codecommit/repositories/test-stijn-delete-techdocs/browse?region=eu-west-1\n try {\n const { path, repositoryName, region, commitSpecifier } = parseUrl(url);\n const codeCommitClient = await this.buildCodeCommitClient(\n this.credsManager,\n region,\n this.integration,\n );\n\n const abortController = new AbortController();\n options?.signal?.addEventListener('abort', () => abortController.abort());\n\n const allFiles: string[] = await this.readTreePath(\n codeCommitClient,\n abortController.signal,\n path,\n repositoryName,\n commitSpecifier,\n options?.etag,\n );\n const responses = [];\n\n for (let i = 0; i < allFiles.length; i++) {\n const getFileCommand = new GetFileCommand({\n repositoryName: repositoryName,\n filePath: String(allFiles[i]),\n commitSpecifier: commitSpecifier,\n });\n const response = await codeCommitClient.send(getFileCommand);\n const objectData = await Readable.from([response?.fileContent]);\n\n responses.push({\n data: objectData,\n path: relative(\n path.startsWith('/') ? path : `/${path}`,\n allFiles[i].startsWith('/') ? allFiles[i] : `/${allFiles[i]}`,\n ),\n });\n }\n\n return await this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n if (e.name && e.name === 'NotModifiedError') {\n throw new NotModifiedError();\n }\n throw new ForwardedError(\n 'Could not retrieve file tree from CodeCommit',\n e,\n );\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url, true);\n\n if (path.match(/[*?]/)) {\n throw new Error('Unsupported search pattern URL');\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (error) {\n assertError(error);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n const secretAccessKey = this.integration.config.secretAccessKey;\n return `awsCodeCommit{host=${this.integration.config.host},authed=${Boolean(\n secretAccessKey,\n )}}`;\n }\n}\n"],"names":["repositoryName","commitSpecifier","ScmIntegrations","DefaultAwsCredentialsManager","fromTemporaryCredentials","CodeCommitClient","abortController","AbortController","GetFileCommand","NotModifiedError","ReadUrlResponseFactory","Readable","ForwardedError","GetFolderCommand","relative","assertError"],"mappings":"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"}
1
+ {"version":3,"file":"AwsCodeCommitUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n AwsCodeCommitIntegration,\n ScmIntegrations,\n} from '@backstage/integration';\nimport { toError, ForwardedError, NotModifiedError } from '@backstage/errors';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport {\n CodeCommitClient,\n GetFileCommand,\n GetFileCommandInput,\n GetFileCommandOutput,\n GetFolderCommand,\n} from '@aws-sdk/client-codecommit';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport { Readable } from 'node:stream';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { relative } from 'node:path/posix';\nimport { AbortController } from '@aws-sdk/abort-controller';\n\nexport function parseUrl(\n url: string,\n requireGitPath: boolean = false,\n): {\n path: string;\n repositoryName: string;\n region: string;\n commitSpecifier?: string;\n} {\n const parsedUrl = new URL(url);\n\n if (parsedUrl.pathname.includes('/files/edit/')) {\n throw new Error(\n 'Please provide the view url to yaml file from CodeCommit, not the edit url',\n );\n }\n if (requireGitPath && !parsedUrl.pathname.includes('/browse/')) {\n throw new Error('Please provide full path to yaml file from CodeCommit');\n }\n\n const hostMatch = parsedUrl.host.match(\n /^([^\\.]+)\\.console\\.aws\\.amazon\\.com$/,\n );\n if (!hostMatch) {\n throw new Error(\n `Invalid AWS CodeCommit URL (unexpected host format): ${url}`,\n );\n }\n const [, region] = hostMatch;\n\n const pathMatch = parsedUrl.pathname.match(\n /^\\/codesuite\\/codecommit\\/repositories\\/([^\\/]+)\\/browse\\/((.*)\\/)?--\\/(.*)$/,\n );\n\n if (!pathMatch) {\n if (!requireGitPath) {\n const pathname = parsedUrl.pathname\n .split('/--/')[0]\n .replace('/codesuite/codecommit/repositories/', '');\n const [repositoryName, commitSpecifier] = pathname.split('/browse');\n\n return {\n region,\n repositoryName: repositoryName.replace(/^\\/|\\/$/g, ''),\n path: '/',\n commitSpecifier:\n commitSpecifier === ''\n ? undefined\n : commitSpecifier?.replace(/^\\/|\\/$/g, ''),\n };\n }\n throw new Error(\n `Invalid AWS CodeCommit URL (unexpected path format): ${url}`,\n );\n }\n const [, repositoryName, , commitSpecifier, path] = pathMatch;\n\n return {\n region,\n repositoryName,\n path,\n // the commitSpecifier is passed to AWS SDK which does not allow empty strings so replace empty string with undefined\n commitSpecifier: commitSpecifier === '' ? undefined : commitSpecifier,\n };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for AWS CodeCommit.\n *\n * @public\n */\nexport class AwsCodeCommitUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n\n return integrations.awsCodeCommit.list().map(integration => {\n const reader = new AwsCodeCommitUrlReader(credsManager, integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => {\n return (\n url.host.endsWith(integration.config.host) &&\n url.pathname.startsWith('/codesuite/codecommit')\n );\n };\n\n return { reader, predicate };\n });\n };\n\n private readonly credsManager: AwsCredentialsManager;\n private readonly integration: AwsCodeCommitIntegration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n };\n\n constructor(\n credsManager: AwsCredentialsManager,\n integration: AwsCodeCommitIntegration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {\n this.credsManager = credsManager;\n this.integration = integration;\n this.deps = deps;\n }\n\n /**\n * If accessKeyId and secretAccessKey are missing, the standard credentials provider chain will be used:\n * https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html\n */\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return {\n accessKeyId,\n secretAccessKey,\n };\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n region: string,\n integration?: AwsCodeCommitIntegration,\n ): Promise<AwsCredentialIdentityProvider> {\n // Fall back to the default credential chain if neither account ID\n // nor explicit credentials are provided\n if (!integration) {\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\n }\n\n const accessKeyId = integration.config.accessKeyId;\n const secretAccessKey = integration.config.secretAccessKey;\n let explicitCredentials: AwsCredentialIdentityProvider;\n if (accessKeyId && secretAccessKey) {\n explicitCredentials = AwsCodeCommitUrlReader.buildStaticCredentials(\n accessKeyId,\n secretAccessKey,\n );\n } else {\n explicitCredentials = (await credsManager.getCredentialProvider())\n .sdkCredentialProvider;\n }\n\n const roleArn = integration.config.roleArn;\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-code-commit-url-reader',\n RoleArn: roleArn,\n ExternalId: integration.config.externalId,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n\n private async buildCodeCommitClient(\n credsManager: AwsCredentialsManager,\n region: string,\n integration: AwsCodeCommitIntegration,\n ): Promise<CodeCommitClient> {\n const credentials = await AwsCodeCommitUrlReader.buildCredentials(\n credsManager,\n region,\n integration,\n );\n\n const codeCommit = new CodeCommitClient({\n customUserAgent: 'backstage-aws-codecommit-url-reader',\n region: region,\n credentials: credentials,\n });\n return codeCommit;\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n // etag and lastModifiedAfter are not supported by the CodeCommit API\n try {\n const { path, repositoryName, region, commitSpecifier } = parseUrl(\n url,\n true,\n );\n const codeCommitClient = await this.buildCodeCommitClient(\n this.credsManager,\n region,\n this.integration,\n );\n const abortController = new AbortController();\n\n const input: GetFileCommandInput = {\n repositoryName: repositoryName,\n commitSpecifier: commitSpecifier,\n filePath: path,\n };\n\n options?.signal?.addEventListener('abort', () => abortController.abort());\n const getObjectCommand = new GetFileCommand(input);\n const response: GetFileCommandOutput = await codeCommitClient.send(\n getObjectCommand,\n {\n abortSignal: abortController.signal,\n },\n );\n\n if (options?.etag && options.etag === response.commitId) {\n throw new NotModifiedError();\n }\n\n return ReadUrlResponseFactory.fromReadable(\n Readable.from([response?.fileContent]),\n {\n etag: response.commitId,\n },\n );\n } catch (e) {\n if (e.$metadata && e.$metadata.httpStatusCode === 304) {\n throw new NotModifiedError();\n }\n if (e.name && e.name === 'NotModifiedError') {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError('Could not retrieve file from CodeCommit', e);\n }\n }\n\n async readTreePath(\n codeCommitClient: CodeCommitClient,\n abortSignal: any,\n path: string,\n repositoryName: string,\n commitSpecifier?: string,\n etag?: string,\n ): Promise<string[]> {\n const getFolderCommand = new GetFolderCommand({\n folderPath: path,\n repositoryName: repositoryName,\n commitSpecifier: commitSpecifier,\n });\n const response = await codeCommitClient.send(getFolderCommand, {\n abortSignal: abortSignal,\n });\n\n if (etag && etag === response.commitId) {\n throw new NotModifiedError();\n }\n\n const output: string[] = [];\n if (response.files) {\n response.files.forEach(file => {\n if (file.absolutePath) {\n output.push(file.absolutePath);\n }\n });\n }\n if (!response.subFolders) {\n return output;\n }\n\n for (const subFolder of response.subFolders) {\n if (subFolder.absolutePath) {\n output.push(\n ...(await this.readTreePath(\n codeCommitClient,\n abortSignal,\n subFolder.absolutePath,\n repositoryName,\n commitSpecifier,\n etag,\n )),\n );\n }\n }\n return output;\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n // url: https://eu-west-1.console.aws.amazon.com/codesuite/codecommit/repositories/test-stijn-delete-techdocs/browse?region=eu-west-1\n try {\n const { path, repositoryName, region, commitSpecifier } = parseUrl(url);\n const codeCommitClient = await this.buildCodeCommitClient(\n this.credsManager,\n region,\n this.integration,\n );\n\n const abortController = new AbortController();\n options?.signal?.addEventListener('abort', () => abortController.abort());\n\n const allFiles: string[] = await this.readTreePath(\n codeCommitClient,\n abortController.signal,\n path,\n repositoryName,\n commitSpecifier,\n options?.etag,\n );\n const responses = [];\n\n for (let i = 0; i < allFiles.length; i++) {\n const getFileCommand = new GetFileCommand({\n repositoryName: repositoryName,\n filePath: String(allFiles[i]),\n commitSpecifier: commitSpecifier,\n });\n const response = await codeCommitClient.send(getFileCommand);\n const objectData = await Readable.from([response?.fileContent]);\n\n responses.push({\n data: objectData,\n path: relative(\n path.startsWith('/') ? path : `/${path}`,\n allFiles[i].startsWith('/') ? allFiles[i] : `/${allFiles[i]}`,\n ),\n });\n }\n\n return await this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n if (e.name && e.name === 'NotModifiedError') {\n throw new NotModifiedError();\n }\n throw new ForwardedError(\n 'Could not retrieve file tree from CodeCommit',\n e,\n );\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url, true);\n\n if (path.match(/[*?]/)) {\n throw new Error('Unsupported search pattern URL');\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n const error = toError(e);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n const secretAccessKey = this.integration.config.secretAccessKey;\n return `awsCodeCommit{host=${this.integration.config.host},authed=${Boolean(\n secretAccessKey,\n )}}`;\n }\n}\n"],"names":["repositoryName","commitSpecifier","ScmIntegrations","DefaultAwsCredentialsManager","fromTemporaryCredentials","CodeCommitClient","abortController","AbortController","GetFileCommand","NotModifiedError","ReadUrlResponseFactory","Readable","ForwardedError","GetFolderCommand","relative","toError"],"mappings":"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"}
@@ -264,8 +264,8 @@ class AwsS3UrlReader {
264
264
  ],
265
265
  etag: data.etag ?? ""
266
266
  };
267
- } catch (error) {
268
- errors.assertError(error);
267
+ } catch (e) {
268
+ const error = errors.toError(e);
269
269
  if (error.name === "NotFoundError") {
270
270
  return {
271
271
  files: [],
@@ -1 +1 @@
1
- {"version":3,"file":"AwsS3UrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AwsS3UrlReader.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n AwsS3Integration,\n ScmIntegrations,\n AwsS3IntegrationConfig,\n} from '@backstage/integration';\nimport {\n assertError,\n ForwardedError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport {\n S3Client,\n ListObjectsV2Command,\n ListObjectsV2CommandOutput,\n GetObjectCommand,\n GetObjectCommandInput,\n} from '@aws-sdk/client-s3';\nimport { AbortController } from '@aws-sdk/abort-controller';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { Readable } from 'node:stream';\nimport { relative } from 'node:path/posix';\n\nexport const DEFAULT_REGION = 'us-east-1';\n\n/**\n * Path style URLs: https://s3.(region).amazonaws.com/(bucket)/(key)\n * The region can also be on the old form: https://s3-(region).amazonaws.com/(bucket)/(key)\n * Virtual hosted style URLs: https://(bucket).s3.(region).amazonaws.com/(key)\n * See https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#path-style-access\n */\nexport function parseUrl(\n url: string,\n config: AwsS3IntegrationConfig,\n): { path: string; bucket: string; region: string } {\n const parsedUrl = new URL(url);\n\n /**\n * Removes the leading '/' from the pathname to be processed\n * as a parameter by AWS S3 SDK getObject method.\n */\n const pathname = parsedUrl.pathname.substring(1);\n const host = parsedUrl.host;\n\n // Treat Amazon hosted separately because it has special region logic\n if (\n config.host === 'amazonaws.com' ||\n config.host === 'amazonaws.com.cn' ||\n config.host.endsWith('.amazonaws.com') ||\n config.host.endsWith('.amazonaws.com.cn')\n ) {\n const match = host.match(\n /^(?:([a-z0-9.-]+)\\.)?s3(?:[.-]([a-z0-9-]+))?\\.amazonaws\\.com(\\.cn)?$/,\n );\n if (!match) {\n throw new Error(`Invalid AWS S3 URL ${url}`);\n }\n\n const [, hostBucket, hostRegion] = match;\n\n if (config.s3ForcePathStyle || !hostBucket) {\n const slashIndex = pathname.indexOf('/');\n if (slashIndex < 0) {\n throw new Error(\n `Invalid path-style AWS S3 URL ${url}, does not contain bucket in the path`,\n );\n }\n\n return {\n path: pathname.substring(slashIndex + 1),\n bucket: pathname.substring(0, slashIndex),\n region: hostRegion ?? DEFAULT_REGION,\n };\n }\n\n return {\n path: pathname,\n bucket: hostBucket,\n region: hostRegion ?? DEFAULT_REGION,\n };\n }\n\n const usePathStyle =\n config.s3ForcePathStyle || host.length === config.host.length;\n\n if (usePathStyle) {\n const slashIndex = pathname.indexOf('/');\n if (slashIndex < 0) {\n throw new Error(\n `Invalid path-style AWS S3 URL ${url}, does not contain bucket in the path`,\n );\n }\n\n return {\n path: pathname.substring(slashIndex + 1),\n bucket: pathname.substring(0, slashIndex),\n region: DEFAULT_REGION,\n };\n }\n\n return {\n path: pathname,\n bucket: host.substring(0, host.length - config.host.length - 1),\n region: DEFAULT_REGION,\n };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for AWS S3 buckets.\n *\n * @public\n */\nexport class AwsS3UrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n\n return integrations.awsS3.list().map(integration => {\n const reader = new AwsS3UrlReader(credsManager, integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) =>\n url.host.endsWith(integration.config.host);\n return { reader, predicate };\n });\n };\n\n private readonly credsManager: AwsCredentialsManager;\n private readonly integration: AwsS3Integration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n };\n\n constructor(\n credsManager: AwsCredentialsManager,\n integration: AwsS3Integration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {\n this.credsManager = credsManager;\n this.integration = integration;\n this.deps = deps;\n }\n\n /**\n * If accessKeyId and secretAccessKey are missing, the standard credentials provider chain will be used:\n * https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html\n */\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return {\n accessKeyId,\n secretAccessKey,\n };\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n region: string,\n integration?: AwsS3Integration,\n ): Promise<AwsCredentialIdentityProvider> {\n // Fall back to the default credential chain if neither account ID\n // nor explicit credentials are provided\n if (!integration) {\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\n }\n\n const accessKeyId = integration.config.accessKeyId;\n const secretAccessKey = integration.config.secretAccessKey;\n let explicitCredentials: AwsCredentialIdentityProvider;\n if (accessKeyId && secretAccessKey) {\n explicitCredentials = AwsS3UrlReader.buildStaticCredentials(\n accessKeyId,\n secretAccessKey,\n );\n } else {\n explicitCredentials = (await credsManager.getCredentialProvider())\n .sdkCredentialProvider;\n }\n\n const roleArn = integration.config.roleArn;\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-s3-url-reader',\n RoleArn: roleArn,\n ExternalId: integration.config.externalId,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n\n private async buildS3Client(\n credsManager: AwsCredentialsManager,\n region: string,\n integration: AwsS3Integration,\n ): Promise<S3Client> {\n const credentials = await AwsS3UrlReader.buildCredentials(\n credsManager,\n region,\n integration,\n );\n\n const s3 = new S3Client({\n customUserAgent: 'backstage-aws-s3-url-reader',\n region: region,\n credentials: credentials,\n endpoint: integration.config.endpoint,\n forcePathStyle: integration.config.s3ForcePathStyle,\n });\n return s3;\n }\n\n private async retrieveS3ObjectData(stream: Readable): Promise<Readable> {\n return new Promise((resolve, reject) => {\n try {\n const chunks: any[] = [];\n stream.on('data', chunk => chunks.push(chunk));\n stream.on('error', (e: Error) =>\n reject(new ForwardedError('Unable to read stream', e)),\n );\n stream.on('end', () => resolve(Readable.from(Buffer.concat(chunks))));\n } catch (e) {\n throw new ForwardedError('Unable to parse the response data', e);\n }\n });\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter } = options ?? {};\n\n try {\n const { path, bucket, region } = parseUrl(url, this.integration.config);\n const s3Client = await this.buildS3Client(\n this.credsManager,\n region,\n this.integration,\n );\n const abortController = new AbortController();\n\n const params: GetObjectCommandInput = {\n Bucket: bucket,\n Key: path,\n ...(etag && { IfNoneMatch: etag }),\n ...(lastModifiedAfter && {\n IfModifiedSince: lastModifiedAfter,\n }),\n };\n\n options?.signal?.addEventListener('abort', () => abortController.abort());\n const getObjectCommand = new GetObjectCommand(params);\n const response = await s3Client.send(getObjectCommand, {\n abortSignal: abortController.signal,\n });\n\n const s3ObjectData = await this.retrieveS3ObjectData(\n response.Body as Readable,\n );\n\n return ReadUrlResponseFactory.fromReadable(s3ObjectData, {\n etag: response.ETag,\n lastModifiedAt: response.LastModified,\n });\n } catch (e) {\n if (e.$metadata && e.$metadata.httpStatusCode === 304) {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError('Could not retrieve file from S3', e);\n }\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n try {\n const { path, bucket, region } = parseUrl(url, this.integration.config);\n const s3Client = await this.buildS3Client(\n this.credsManager,\n region,\n this.integration,\n );\n const abortController = new AbortController();\n const allObjects: String[] = [];\n const responses = [];\n let continuationToken: string | undefined;\n let output: ListObjectsV2CommandOutput;\n do {\n const listObjectsV2Command = new ListObjectsV2Command({\n Bucket: bucket,\n ContinuationToken: continuationToken,\n Prefix: path,\n });\n options?.signal?.addEventListener('abort', () =>\n abortController.abort(),\n );\n output = await s3Client.send(listObjectsV2Command, {\n abortSignal: abortController.signal,\n });\n if (output.Contents) {\n output.Contents.forEach(contents => {\n allObjects.push(contents.Key!);\n });\n }\n continuationToken = output.NextContinuationToken;\n } while (continuationToken);\n\n for (let i = 0; i < allObjects.length; i++) {\n const getObjectCommand = new GetObjectCommand({\n Bucket: bucket,\n Key: String(allObjects[i]),\n });\n const response = await s3Client.send(getObjectCommand);\n const s3ObjectData = await this.retrieveS3ObjectData(\n response.Body as Readable,\n );\n\n responses.push({\n data: s3ObjectData,\n path: relative(path, String(allObjects[i])),\n lastModifiedAt: response?.LastModified ?? undefined,\n });\n }\n\n return await this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n throw new ForwardedError('Could not retrieve file tree from S3', e);\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url, this.integration.config);\n\n if (path.match(/[*?]/)) {\n throw new Error('Unsupported search pattern URL');\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (error) {\n assertError(error);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n const secretAccessKey = this.integration.config.secretAccessKey;\n return `awsS3{host=${this.integration.config.host},authed=${Boolean(\n secretAccessKey,\n )}}`;\n }\n}\n"],"names":["ScmIntegrations","DefaultAwsCredentialsManager","fromTemporaryCredentials","S3Client","ForwardedError","Readable","abortController","AbortController","GetObjectCommand","ReadUrlResponseFactory","NotModifiedError","ListObjectsV2Command","relative","assertError"],"mappings":"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"}
1
+ {"version":3,"file":"AwsS3UrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AwsS3UrlReader.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n AwsS3Integration,\n ScmIntegrations,\n AwsS3IntegrationConfig,\n} from '@backstage/integration';\nimport { toError, ForwardedError, NotModifiedError } from '@backstage/errors';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport {\n S3Client,\n ListObjectsV2Command,\n ListObjectsV2CommandOutput,\n GetObjectCommand,\n GetObjectCommandInput,\n} from '@aws-sdk/client-s3';\nimport { AbortController } from '@aws-sdk/abort-controller';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { Readable } from 'node:stream';\nimport { relative } from 'node:path/posix';\n\nexport const DEFAULT_REGION = 'us-east-1';\n\n/**\n * Path style URLs: https://s3.(region).amazonaws.com/(bucket)/(key)\n * The region can also be on the old form: https://s3-(region).amazonaws.com/(bucket)/(key)\n * Virtual hosted style URLs: https://(bucket).s3.(region).amazonaws.com/(key)\n * See https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#path-style-access\n */\nexport function parseUrl(\n url: string,\n config: AwsS3IntegrationConfig,\n): { path: string; bucket: string; region: string } {\n const parsedUrl = new URL(url);\n\n /**\n * Removes the leading '/' from the pathname to be processed\n * as a parameter by AWS S3 SDK getObject method.\n */\n const pathname = parsedUrl.pathname.substring(1);\n const host = parsedUrl.host;\n\n // Treat Amazon hosted separately because it has special region logic\n if (\n config.host === 'amazonaws.com' ||\n config.host === 'amazonaws.com.cn' ||\n config.host.endsWith('.amazonaws.com') ||\n config.host.endsWith('.amazonaws.com.cn')\n ) {\n const match = host.match(\n /^(?:([a-z0-9.-]+)\\.)?s3(?:[.-]([a-z0-9-]+))?\\.amazonaws\\.com(\\.cn)?$/,\n );\n if (!match) {\n throw new Error(`Invalid AWS S3 URL ${url}`);\n }\n\n const [, hostBucket, hostRegion] = match;\n\n if (config.s3ForcePathStyle || !hostBucket) {\n const slashIndex = pathname.indexOf('/');\n if (slashIndex < 0) {\n throw new Error(\n `Invalid path-style AWS S3 URL ${url}, does not contain bucket in the path`,\n );\n }\n\n return {\n path: pathname.substring(slashIndex + 1),\n bucket: pathname.substring(0, slashIndex),\n region: hostRegion ?? DEFAULT_REGION,\n };\n }\n\n return {\n path: pathname,\n bucket: hostBucket,\n region: hostRegion ?? DEFAULT_REGION,\n };\n }\n\n const usePathStyle =\n config.s3ForcePathStyle || host.length === config.host.length;\n\n if (usePathStyle) {\n const slashIndex = pathname.indexOf('/');\n if (slashIndex < 0) {\n throw new Error(\n `Invalid path-style AWS S3 URL ${url}, does not contain bucket in the path`,\n );\n }\n\n return {\n path: pathname.substring(slashIndex + 1),\n bucket: pathname.substring(0, slashIndex),\n region: DEFAULT_REGION,\n };\n }\n\n return {\n path: pathname,\n bucket: host.substring(0, host.length - config.host.length - 1),\n region: DEFAULT_REGION,\n };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for AWS S3 buckets.\n *\n * @public\n */\nexport class AwsS3UrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n\n return integrations.awsS3.list().map(integration => {\n const reader = new AwsS3UrlReader(credsManager, integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) =>\n url.host.endsWith(integration.config.host);\n return { reader, predicate };\n });\n };\n\n private readonly credsManager: AwsCredentialsManager;\n private readonly integration: AwsS3Integration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n };\n\n constructor(\n credsManager: AwsCredentialsManager,\n integration: AwsS3Integration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {\n this.credsManager = credsManager;\n this.integration = integration;\n this.deps = deps;\n }\n\n /**\n * If accessKeyId and secretAccessKey are missing, the standard credentials provider chain will be used:\n * https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html\n */\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return {\n accessKeyId,\n secretAccessKey,\n };\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n region: string,\n integration?: AwsS3Integration,\n ): Promise<AwsCredentialIdentityProvider> {\n // Fall back to the default credential chain if neither account ID\n // nor explicit credentials are provided\n if (!integration) {\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\n }\n\n const accessKeyId = integration.config.accessKeyId;\n const secretAccessKey = integration.config.secretAccessKey;\n let explicitCredentials: AwsCredentialIdentityProvider;\n if (accessKeyId && secretAccessKey) {\n explicitCredentials = AwsS3UrlReader.buildStaticCredentials(\n accessKeyId,\n secretAccessKey,\n );\n } else {\n explicitCredentials = (await credsManager.getCredentialProvider())\n .sdkCredentialProvider;\n }\n\n const roleArn = integration.config.roleArn;\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-s3-url-reader',\n RoleArn: roleArn,\n ExternalId: integration.config.externalId,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n\n private async buildS3Client(\n credsManager: AwsCredentialsManager,\n region: string,\n integration: AwsS3Integration,\n ): Promise<S3Client> {\n const credentials = await AwsS3UrlReader.buildCredentials(\n credsManager,\n region,\n integration,\n );\n\n const s3 = new S3Client({\n customUserAgent: 'backstage-aws-s3-url-reader',\n region: region,\n credentials: credentials,\n endpoint: integration.config.endpoint,\n forcePathStyle: integration.config.s3ForcePathStyle,\n });\n return s3;\n }\n\n private async retrieveS3ObjectData(stream: Readable): Promise<Readable> {\n return new Promise((resolve, reject) => {\n try {\n const chunks: any[] = [];\n stream.on('data', chunk => chunks.push(chunk));\n stream.on('error', (e: Error) =>\n reject(new ForwardedError('Unable to read stream', e)),\n );\n stream.on('end', () => resolve(Readable.from(Buffer.concat(chunks))));\n } catch (e) {\n throw new ForwardedError('Unable to parse the response data', e);\n }\n });\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter } = options ?? {};\n\n try {\n const { path, bucket, region } = parseUrl(url, this.integration.config);\n const s3Client = await this.buildS3Client(\n this.credsManager,\n region,\n this.integration,\n );\n const abortController = new AbortController();\n\n const params: GetObjectCommandInput = {\n Bucket: bucket,\n Key: path,\n ...(etag && { IfNoneMatch: etag }),\n ...(lastModifiedAfter && {\n IfModifiedSince: lastModifiedAfter,\n }),\n };\n\n options?.signal?.addEventListener('abort', () => abortController.abort());\n const getObjectCommand = new GetObjectCommand(params);\n const response = await s3Client.send(getObjectCommand, {\n abortSignal: abortController.signal,\n });\n\n const s3ObjectData = await this.retrieveS3ObjectData(\n response.Body as Readable,\n );\n\n return ReadUrlResponseFactory.fromReadable(s3ObjectData, {\n etag: response.ETag,\n lastModifiedAt: response.LastModified,\n });\n } catch (e) {\n if (e.$metadata && e.$metadata.httpStatusCode === 304) {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError('Could not retrieve file from S3', e);\n }\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n try {\n const { path, bucket, region } = parseUrl(url, this.integration.config);\n const s3Client = await this.buildS3Client(\n this.credsManager,\n region,\n this.integration,\n );\n const abortController = new AbortController();\n const allObjects: String[] = [];\n const responses = [];\n let continuationToken: string | undefined;\n let output: ListObjectsV2CommandOutput;\n do {\n const listObjectsV2Command = new ListObjectsV2Command({\n Bucket: bucket,\n ContinuationToken: continuationToken,\n Prefix: path,\n });\n options?.signal?.addEventListener('abort', () =>\n abortController.abort(),\n );\n output = await s3Client.send(listObjectsV2Command, {\n abortSignal: abortController.signal,\n });\n if (output.Contents) {\n output.Contents.forEach(contents => {\n allObjects.push(contents.Key!);\n });\n }\n continuationToken = output.NextContinuationToken;\n } while (continuationToken);\n\n for (let i = 0; i < allObjects.length; i++) {\n const getObjectCommand = new GetObjectCommand({\n Bucket: bucket,\n Key: String(allObjects[i]),\n });\n const response = await s3Client.send(getObjectCommand);\n const s3ObjectData = await this.retrieveS3ObjectData(\n response.Body as Readable,\n );\n\n responses.push({\n data: s3ObjectData,\n path: relative(path, String(allObjects[i])),\n lastModifiedAt: response?.LastModified ?? undefined,\n });\n }\n\n return await this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n throw new ForwardedError('Could not retrieve file tree from S3', e);\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url, this.integration.config);\n\n if (path.match(/[*?]/)) {\n throw new Error('Unsupported search pattern URL');\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n const error = toError(e);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n const secretAccessKey = this.integration.config.secretAccessKey;\n return `awsS3{host=${this.integration.config.host},authed=${Boolean(\n secretAccessKey,\n )}}`;\n }\n}\n"],"names":["ScmIntegrations","DefaultAwsCredentialsManager","fromTemporaryCredentials","S3Client","ForwardedError","Readable","abortController","AbortController","GetObjectCommand","ReadUrlResponseFactory","NotModifiedError","ListObjectsV2Command","relative","toError"],"mappings":"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"}
@@ -161,9 +161,8 @@ class AzureBlobStorageUrlReader {
161
161
  ],
162
162
  etag: data.etag ?? ""
163
163
  };
164
- } catch (error) {
165
- errors.assertError(error);
166
- throw error;
164
+ } catch (e) {
165
+ throw errors.toError(e);
167
166
  }
168
167
  }
169
168
  toString() {