@backstage/backend-defaults 0.17.2 → 0.17.3-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,29 @@
1
1
  # @backstage/backend-defaults
2
2
 
3
- ## 0.17.2
3
+ ## 0.17.3-next.1
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - eb80f4f: Fix gitlabUrlReader issue with retrieving the repository archive tree
7
+ - b75158b: Adapted Azure-related tests for the Azure SDK upgrade to ESM-style exports. The `AzureBlobStorageUrlReader` now accepts an optional `createContainerClient` dependency for testability without needing to mock the `@azure/storage-blob` module.
8
+ - 0211390: Added a new `v2` invoke endpoint (`/.backstage/actions/v2/actions/:id/invoke`) that accepts a wrapped body format `{ input, secrets }` with secrets validation. The existing `v1` invoke endpoint remains unchanged for backward compatibility. Updated `DefaultActionsService` to use the `v2` endpoint. Updated `DefaultActionsRegistryService` to expose secrets schema in the actions list response and validate secrets on invocation.
9
+ - 34f21c3: Fix gitlabUrlReader issue with retrieving the repository archive tree
10
+ - Updated dependencies
11
+ - @backstage/integration@2.0.3-next.1
12
+ - @backstage/backend-plugin-api@1.9.2-next.1
13
+
14
+ ## 0.17.2-next.0
15
+
16
+ ### Patch Changes
17
+
18
+ - a07e6a3: Updated `AzureBlobStorageUrlReader` to reference the correctly-named `AzureBlobStorageIntegration` type from `@backstage/integration`. The previously-used `AzureBlobStorageIntergation` is now an alias for the new type and remains a valid argument to the constructor.
19
+ - def82d4: Fixed the built-in rate limiter throwing a validation error and refusing to start when `backend.rateLimit` is enabled. Requests are now keyed using the address normalization helper from `express-rate-limit`, which is required by newer versions of that library and ensures IPv6 clients are grouped by their address block rather than by individual address.
20
+ - Updated dependencies
21
+ - @backstage/integration@2.0.3-next.0
22
+ - @backstage/plugin-auth-node@0.7.2-next.0
23
+ - @backstage/backend-app-api@1.7.1-next.0
24
+ - @backstage/plugin-permission-node@0.11.1-next.0
25
+ - @backstage/backend-plugin-api@1.9.2-next.0
26
+ - @backstage/plugin-events-node@0.4.23-next.0
8
27
 
9
28
  ## 0.17.1
10
29
 
@@ -47,15 +47,17 @@ class DefaultActionsService {
47
47
  }
48
48
  async invoke(opts) {
49
49
  const pluginId = this.pluginIdFromActionId(opts.id);
50
+ const version = opts.secrets ? "v2" : "v1";
51
+ const body = version === "v2" ? JSON.stringify({ input: opts.input, secrets: opts.secrets }) : JSON.stringify(opts.input);
50
52
  const response = await this.makeRequest({
51
- path: `/.backstage/actions/v1/actions/${encodeURIComponent(
53
+ path: `/.backstage/actions/${version}/actions/${encodeURIComponent(
52
54
  opts.id
53
55
  )}/invoke`,
54
56
  pluginId,
55
57
  credentials: opts.credentials,
56
58
  options: {
57
59
  method: "POST",
58
- body: JSON.stringify(opts.input),
60
+ body,
59
61
  headers: {
60
62
  "Content-Type": "application/json"
61
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultActionsService.cjs.js","sources":["../../../../src/alpha/entrypoints/actions/DefaultActionsService.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 */\nimport {\n AuthService,\n BackstageCredentials,\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport {\n ActionsService,\n ActionsServiceAction,\n} from '@backstage/backend-plugin-api/alpha';\nimport { Minimatch } from 'minimatch';\nimport { Config } from '@backstage/config';\n\nexport class DefaultActionsService implements ActionsService {\n private readonly discovery: DiscoveryService;\n private readonly config: RootConfigService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n\n private constructor(\n discovery: DiscoveryService,\n config: RootConfigService,\n logger: LoggerService,\n auth: AuthService,\n ) {\n this.discovery = discovery;\n this.config = config;\n this.logger = logger;\n this.auth = auth;\n }\n\n static create({\n discovery,\n config,\n logger,\n auth,\n }: {\n discovery: DiscoveryService;\n config: RootConfigService;\n logger: LoggerService;\n auth: AuthService;\n }) {\n return new DefaultActionsService(discovery, config, logger, auth);\n }\n\n async list({ credentials }: { credentials: BackstageCredentials }) {\n const pluginSources =\n this.config.getOptionalStringArray('backend.actions.pluginSources') ?? [];\n\n const remoteActionsList = await Promise.all(\n pluginSources.map(async source => {\n try {\n const response = await this.makeRequest({\n path: `/.backstage/actions/v1/actions`,\n pluginId: source,\n credentials,\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n const { actions } = (await response.json()) as {\n actions: ActionsServiceAction;\n };\n\n return actions;\n } catch (error) {\n this.logger.warn(`Failed to fetch actions from ${source}`, error);\n return [];\n }\n }),\n );\n\n return { actions: this.applyFilters(remoteActionsList.flat()) };\n }\n\n async invoke(opts: {\n id: string;\n input?: JsonObject;\n credentials: BackstageCredentials;\n }) {\n const pluginId = this.pluginIdFromActionId(opts.id);\n const response = await this.makeRequest({\n path: `/.backstage/actions/v1/actions/${encodeURIComponent(\n opts.id,\n )}/invoke`,\n pluginId,\n credentials: opts.credentials,\n options: {\n method: 'POST',\n body: JSON.stringify(opts.input),\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const { output } = await response.json();\n return { output };\n }\n\n private async makeRequest(opts: {\n path: string;\n pluginId: string;\n options?: RequestInit;\n credentials: BackstageCredentials;\n }) {\n const { path, pluginId, credentials, options } = opts;\n const baseUrl = await this.discovery.getBaseUrl(pluginId);\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: opts.pluginId,\n });\n\n return fetch(`${baseUrl}${path}`, {\n ...options,\n headers: {\n ...options?.headers,\n Authorization: `Bearer ${token}`,\n },\n });\n }\n\n private pluginIdFromActionId(id: string): string {\n const colonIndex = id.indexOf(':');\n if (colonIndex === -1) {\n throw new Error(`Invalid action id: ${id}`);\n }\n return id.substring(0, colonIndex);\n }\n\n private applyFilters(\n actions: ActionsServiceAction[],\n ): ActionsServiceAction[] {\n const filterConfig = this.config.getOptionalConfig(\n 'backend.actions.filter',\n );\n\n if (!filterConfig) {\n return actions;\n }\n\n const includeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('include') ?? [],\n );\n const excludeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('exclude') ?? [],\n );\n\n return actions.filter(action => {\n const excluded = excludeRules.some(rule =>\n this.matchesRule(action, rule),\n );\n\n if (excluded) {\n return false;\n }\n\n // If no include rules, include by default\n if (includeRules.length === 0) {\n return true;\n }\n\n // Must match at least one include rule\n return includeRules.some(rule => this.matchesRule(action, rule));\n });\n }\n\n private parseFilterRules(configArray: Array<Config>): Array<{\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n }> {\n return configArray.map(ruleConfig => {\n const idPattern = ruleConfig.getOptionalString('id');\n const attributesConfig = ruleConfig.getOptionalConfig('attributes');\n\n const rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n } = {};\n\n if (idPattern) {\n rule.idMatcher = new Minimatch(idPattern);\n }\n\n if (attributesConfig) {\n rule.attributes = {};\n for (const key of ['destructive', 'readOnly', 'idempotent'] as const) {\n const value = attributesConfig.getOptionalBoolean(key);\n if (value !== undefined) {\n rule.attributes[key] = value;\n }\n }\n }\n\n return rule;\n });\n }\n\n private matchesRule(\n action: ActionsServiceAction,\n rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n },\n ): boolean {\n // If id pattern is specified, it must match\n if (rule.idMatcher && !rule.idMatcher.match(action.id)) {\n return false;\n }\n\n // If attributes are specified, all must match\n if (rule.attributes) {\n for (const [key, value] of Object.entries(rule.attributes)) {\n if (\n action.attributes[\n key as 'destructive' | 'readOnly' | 'idempotent'\n ] !== value\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n}\n"],"names":["ResponseError","Minimatch"],"mappings":";;;;;AA+BO,MAAM,qBAAA,CAAgD;AAAA,EAC1C,SAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EAET,WAAA,CACN,SAAA,EACA,MAAA,EACA,MAAA,EACA,IAAA,EACA;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AACjB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,OAAO,MAAA,CAAO;AAAA,IACZ,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF,EAKG;AACD,IAAA,OAAO,IAAI,qBAAA,CAAsB,SAAA,EAAW,MAAA,EAAQ,QAAQ,IAAI,CAAA;AAAA,EAClE;AAAA,EAEA,MAAM,IAAA,CAAK,EAAE,WAAA,EAAY,EAA0C;AACjE,IAAA,MAAM,gBACJ,IAAA,CAAK,MAAA,CAAO,sBAAA,CAAuB,+BAA+B,KAAK,EAAC;AAE1E,IAAA,MAAM,iBAAA,GAAoB,MAAM,OAAA,CAAQ,GAAA;AAAA,MACtC,aAAA,CAAc,GAAA,CAAI,OAAM,MAAA,KAAU;AAChC,QAAA,IAAI;AACF,UAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,WAAA,CAAY;AAAA,YACtC,IAAA,EAAM,CAAA,8BAAA,CAAA;AAAA,YACN,QAAA,EAAU,MAAA;AAAA,YACV;AAAA,WACD,CAAA;AACD,UAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,YAAA,MAAM,MAAMA,oBAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA,UACjD;AACA,UAAA,MAAM,EAAE,OAAA,EAAQ,GAAK,MAAM,SAAS,IAAA,EAAK;AAIzC,UAAA,OAAO,OAAA;AAAA,QACT,SAAS,KAAA,EAAO;AACd,UAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,CAAA,6BAAA,EAAgC,MAAM,IAAI,KAAK,CAAA;AAChE,UAAA,OAAO,EAAC;AAAA,QACV;AAAA,MACF,CAAC;AAAA,KACH;AAEA,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,CAAK,aAAa,iBAAA,CAAkB,IAAA,EAAM,CAAA,EAAE;AAAA,EAChE;AAAA,EAEA,MAAM,OAAO,IAAA,EAIV;AACD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,oBAAA,CAAqB,IAAA,CAAK,EAAE,CAAA;AAClD,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,WAAA,CAAY;AAAA,MACtC,MAAM,CAAA,+BAAA,EAAkC,kBAAA;AAAA,QACtC,IAAA,CAAK;AAAA,OACN,CAAA,OAAA,CAAA;AAAA,MACD,QAAA;AAAA,MACA,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,OAAA,EAAS;AAAA,QACP,MAAA,EAAQ,MAAA;AAAA,QACR,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,KAAK,CAAA;AAAA,QAC/B,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB;AACF,KACD,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,MAAMA,oBAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA,IACjD;AAEA,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,MAAM,SAAS,IAAA,EAAK;AACvC,IAAA,OAAO,EAAE,MAAA,EAAO;AAAA,EAClB;AAAA,EAEA,MAAc,YAAY,IAAA,EAKvB;AACD,IAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,WAAA,EAAa,SAAQ,GAAI,IAAA;AACjD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,SAAA,CAAU,WAAW,QAAQ,CAAA;AAExD,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,IAAA,CAAK,KAAK,qBAAA,CAAsB;AAAA,MACtD,UAAA,EAAY,WAAA;AAAA,MACZ,gBAAgB,IAAA,CAAK;AAAA,KACtB,CAAA;AAED,IAAA,OAAO,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI;AAAA,MAChC,GAAG,OAAA;AAAA,MACH,OAAA,EAAS;AAAA,QACP,GAAG,OAAA,EAAS,OAAA;AAAA,QACZ,aAAA,EAAe,UAAU,KAAK,CAAA;AAAA;AAChC,KACD,CAAA;AAAA,EACH;AAAA,EAEQ,qBAAqB,EAAA,EAAoB;AAC/C,IAAA,MAAM,UAAA,GAAa,EAAA,CAAG,OAAA,CAAQ,GAAG,CAAA;AACjC,IAAA,IAAI,eAAe,EAAA,EAAI;AACrB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,EAAE,CAAA,CAAE,CAAA;AAAA,IAC5C;AACA,IAAA,OAAO,EAAA,CAAG,SAAA,CAAU,CAAA,EAAG,UAAU,CAAA;AAAA,EACnC;AAAA,EAEQ,aACN,OAAA,EACwB;AACxB,IAAA,MAAM,YAAA,GAAe,KAAK,MAAA,CAAO,iBAAA;AAAA,MAC/B;AAAA,KACF;AAEA,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,OAAO,OAAA;AAAA,IACT;AAEA,IAAA,MAAM,eAAe,IAAA,CAAK,gBAAA;AAAA,MACxB,YAAA,CAAa,sBAAA,CAAuB,SAAS,CAAA,IAAK;AAAC,KACrD;AACA,IAAA,MAAM,eAAe,IAAA,CAAK,gBAAA;AAAA,MACxB,YAAA,CAAa,sBAAA,CAAuB,SAAS,CAAA,IAAK;AAAC,KACrD;AAEA,IAAA,OAAO,OAAA,CAAQ,OAAO,CAAA,MAAA,KAAU;AAC9B,MAAA,MAAM,WAAW,YAAA,CAAa,IAAA;AAAA,QAAK,CAAA,IAAA,KACjC,IAAA,CAAK,WAAA,CAAY,MAAA,EAAQ,IAAI;AAAA,OAC/B;AAEA,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,OAAO,KAAA;AAAA,MACT;AAGA,MAAA,IAAI,YAAA,CAAa,WAAW,CAAA,EAAG;AAC7B,QAAA,OAAO,IAAA;AAAA,MACT;AAGA,MAAA,OAAO,aAAa,IAAA,CAAK,CAAA,IAAA,KAAQ,KAAK,WAAA,CAAY,MAAA,EAAQ,IAAI,CAAC,CAAA;AAAA,IACjE,CAAC,CAAA;AAAA,EACH;AAAA,EAEQ,iBAAiB,WAAA,EAKtB;AACD,IAAA,OAAO,WAAA,CAAY,IAAI,CAAA,UAAA,KAAc;AACnC,MAAA,MAAM,SAAA,GAAY,UAAA,CAAW,iBAAA,CAAkB,IAAI,CAAA;AACnD,MAAA,MAAM,gBAAA,GAAmB,UAAA,CAAW,iBAAA,CAAkB,YAAY,CAAA;AAElE,MAAA,MAAM,OAKF,EAAC;AAEL,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,IAAA,CAAK,SAAA,GAAY,IAAIC,mBAAA,CAAU,SAAS,CAAA;AAAA,MAC1C;AAEA,MAAA,IAAI,gBAAA,EAAkB;AACpB,QAAA,IAAA,CAAK,aAAa,EAAC;AACnB,QAAA,KAAA,MAAW,GAAA,IAAO,CAAC,aAAA,EAAe,UAAA,EAAY,YAAY,CAAA,EAAY;AACpE,UAAA,MAAM,KAAA,GAAQ,gBAAA,CAAiB,kBAAA,CAAmB,GAAG,CAAA;AACrD,UAAA,IAAI,UAAU,MAAA,EAAW;AACvB,YAAA,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,KAAA;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAEA,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAAA,EACH;AAAA,EAEQ,WAAA,CACN,QACA,IAAA,EAMS;AAET,IAAA,IAAI,IAAA,CAAK,aAAa,CAAC,IAAA,CAAK,UAAU,KAAA,CAAM,MAAA,CAAO,EAAE,CAAA,EAAG;AACtD,MAAA,OAAO,KAAA;AAAA,IACT;AAGA,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA,EAAG;AAC1D,QAAA,IACE,MAAA,CAAO,UAAA,CACL,GACF,CAAA,KAAM,KAAA,EACN;AACA,UAAA,OAAO,KAAA;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;;"}
1
+ {"version":3,"file":"DefaultActionsService.cjs.js","sources":["../../../../src/alpha/entrypoints/actions/DefaultActionsService.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 */\nimport {\n AuthService,\n BackstageCredentials,\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport {\n ActionsService,\n ActionsServiceAction,\n} from '@backstage/backend-plugin-api/alpha';\nimport { Minimatch } from 'minimatch';\nimport { Config } from '@backstage/config';\n\nexport class DefaultActionsService implements ActionsService {\n private readonly discovery: DiscoveryService;\n private readonly config: RootConfigService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n\n private constructor(\n discovery: DiscoveryService,\n config: RootConfigService,\n logger: LoggerService,\n auth: AuthService,\n ) {\n this.discovery = discovery;\n this.config = config;\n this.logger = logger;\n this.auth = auth;\n }\n\n static create({\n discovery,\n config,\n logger,\n auth,\n }: {\n discovery: DiscoveryService;\n config: RootConfigService;\n logger: LoggerService;\n auth: AuthService;\n }) {\n return new DefaultActionsService(discovery, config, logger, auth);\n }\n\n async list({ credentials }: { credentials: BackstageCredentials }) {\n const pluginSources =\n this.config.getOptionalStringArray('backend.actions.pluginSources') ?? [];\n\n const remoteActionsList = await Promise.all(\n pluginSources.map(async source => {\n try {\n const response = await this.makeRequest({\n path: `/.backstage/actions/v1/actions`,\n pluginId: source,\n credentials,\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n const { actions } = (await response.json()) as {\n actions: ActionsServiceAction;\n };\n\n return actions;\n } catch (error) {\n this.logger.warn(`Failed to fetch actions from ${source}`, error);\n return [];\n }\n }),\n );\n\n return { actions: this.applyFilters(remoteActionsList.flat()) };\n }\n\n async invoke(opts: {\n id: string;\n input?: JsonObject;\n secrets?: JsonObject;\n credentials: BackstageCredentials;\n }) {\n const pluginId = this.pluginIdFromActionId(opts.id);\n // Deprecated: remove v1 fallback once all registries support v2\n const version = opts.secrets ? 'v2' : 'v1';\n const body =\n version === 'v2'\n ? JSON.stringify({ input: opts.input, secrets: opts.secrets })\n : JSON.stringify(opts.input);\n\n const response = await this.makeRequest({\n path: `/.backstage/actions/${version}/actions/${encodeURIComponent(\n opts.id,\n )}/invoke`,\n pluginId,\n credentials: opts.credentials,\n options: {\n method: 'POST',\n body,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const { output } = await response.json();\n return { output };\n }\n\n private async makeRequest(opts: {\n path: string;\n pluginId: string;\n options?: RequestInit;\n credentials: BackstageCredentials;\n }) {\n const { path, pluginId, credentials, options } = opts;\n const baseUrl = await this.discovery.getBaseUrl(pluginId);\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: opts.pluginId,\n });\n\n return fetch(`${baseUrl}${path}`, {\n ...options,\n headers: {\n ...options?.headers,\n Authorization: `Bearer ${token}`,\n },\n });\n }\n\n private pluginIdFromActionId(id: string): string {\n const colonIndex = id.indexOf(':');\n if (colonIndex === -1) {\n throw new Error(`Invalid action id: ${id}`);\n }\n return id.substring(0, colonIndex);\n }\n\n private applyFilters(\n actions: ActionsServiceAction[],\n ): ActionsServiceAction[] {\n const filterConfig = this.config.getOptionalConfig(\n 'backend.actions.filter',\n );\n\n if (!filterConfig) {\n return actions;\n }\n\n const includeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('include') ?? [],\n );\n const excludeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('exclude') ?? [],\n );\n\n return actions.filter(action => {\n const excluded = excludeRules.some(rule =>\n this.matchesRule(action, rule),\n );\n\n if (excluded) {\n return false;\n }\n\n // If no include rules, include by default\n if (includeRules.length === 0) {\n return true;\n }\n\n // Must match at least one include rule\n return includeRules.some(rule => this.matchesRule(action, rule));\n });\n }\n\n private parseFilterRules(configArray: Array<Config>): Array<{\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n }> {\n return configArray.map(ruleConfig => {\n const idPattern = ruleConfig.getOptionalString('id');\n const attributesConfig = ruleConfig.getOptionalConfig('attributes');\n\n const rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n } = {};\n\n if (idPattern) {\n rule.idMatcher = new Minimatch(idPattern);\n }\n\n if (attributesConfig) {\n rule.attributes = {};\n for (const key of ['destructive', 'readOnly', 'idempotent'] as const) {\n const value = attributesConfig.getOptionalBoolean(key);\n if (value !== undefined) {\n rule.attributes[key] = value;\n }\n }\n }\n\n return rule;\n });\n }\n\n private matchesRule(\n action: ActionsServiceAction,\n rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n },\n ): boolean {\n // If id pattern is specified, it must match\n if (rule.idMatcher && !rule.idMatcher.match(action.id)) {\n return false;\n }\n\n // If attributes are specified, all must match\n if (rule.attributes) {\n for (const [key, value] of Object.entries(rule.attributes)) {\n if (\n action.attributes[\n key as 'destructive' | 'readOnly' | 'idempotent'\n ] !== value\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n}\n"],"names":["ResponseError","Minimatch"],"mappings":"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"}
@@ -72,56 +72,84 @@ class DefaultActionsRegistryService {
72
72
  examples: action.examples,
73
73
  schema: {
74
74
  input: action.schema?.input ? zodToJsonSchema__default.default(action.schema.input(v3.z)) : zodToJsonSchema__default.default(v3.z.object({})),
75
- output: action.schema?.output ? zodToJsonSchema__default.default(action.schema.output(v3.z)) : zodToJsonSchema__default.default(v3.z.object({}))
75
+ output: action.schema?.output ? zodToJsonSchema__default.default(action.schema.output(v3.z)) : zodToJsonSchema__default.default(v3.z.object({})),
76
+ ...action.schema?.secrets && {
77
+ secrets: zodToJsonSchema__default.default(action.schema.secrets(v3.z))
78
+ }
76
79
  }
77
80
  }))
78
81
  });
79
82
  });
80
- router.post(
81
- "/.backstage/actions/v1/actions/:actionId/invoke",
82
- async (req, res) => {
83
- const credentials = await this.httpAuth.credentials(req);
84
- if (this.auth.isPrincipal(credentials, "none")) {
85
- throw new errors.NotAllowedError(
86
- `Actions must be invoked by an authenticated principal, not an anonymous request`
87
- );
88
- }
89
- const action = this.actions.get(req.params.actionId);
90
- if (!action) {
91
- throw new errors.NotFoundError(`Action "${req.params.actionId}" not found`);
92
- }
93
- if (action.visibilityPermission) {
94
- const [decision] = await this.permissions.authorize(
95
- [{ permission: action.visibilityPermission }],
96
- { credentials }
97
- );
98
- if (decision.result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
99
- throw new errors.NotFoundError(
100
- `Action "${req.params.actionId}" not found`
101
- );
102
- }
103
- }
104
- const input = action.schema?.input ? action.schema.input(v3.z).safeParse(req.body) : { success: true, data: void 0 };
105
- if (!input.success) {
106
- throw new errors.InputError(
107
- `Invalid input to action "${req.params.actionId}"`,
108
- input.error
109
- );
110
- }
111
- const result = await action.action({
112
- input: input.data,
113
- credentials,
114
- logger: this.logger
115
- });
116
- const output = action.schema?.output ? action.schema.output(v3.z).safeParse(result?.output) : { success: true, data: result?.output };
117
- if (!output.success) {
118
- throw new errors.InputError(
119
- `Invalid output from action "${req.params.actionId}"`,
120
- output.error
83
+ const invokeHandler = (opts) => async (req, res) => {
84
+ const credentials = await this.httpAuth.credentials(req);
85
+ if (this.auth.isPrincipal(credentials, "none")) {
86
+ throw new errors.NotAllowedError(
87
+ `Actions must be invoked by an authenticated principal, not an anonymous request`
88
+ );
89
+ }
90
+ const action = this.actions.get(req.params.actionId);
91
+ if (!action) {
92
+ throw new errors.NotFoundError(`Action "${req.params.actionId}" not found`);
93
+ }
94
+ if (action.visibilityPermission) {
95
+ const [decision] = await this.permissions.authorize(
96
+ [{ permission: action.visibilityPermission }],
97
+ { credentials }
98
+ );
99
+ if (decision.result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
100
+ throw new errors.NotFoundError(
101
+ `Action "${req.params.actionId}" not found`
121
102
  );
122
103
  }
123
- res.json({ output: output.data });
124
104
  }
105
+ const rawInput = opts.wrapped ? req.body.input : req.body;
106
+ const rawSecrets = opts.wrapped ? req.body.secrets : void 0;
107
+ const input = action.schema?.input ? action.schema.input(v3.z).safeParse(rawInput) : { success: true, data: void 0 };
108
+ if (!input.success) {
109
+ throw new errors.InputError(
110
+ `Invalid input to action "${req.params.actionId}"`,
111
+ input.error
112
+ );
113
+ }
114
+ if (action.schema?.secrets && !rawSecrets) {
115
+ throw new errors.InputError(
116
+ `Action "${req.params.actionId}" requires secrets but none were provided`
117
+ );
118
+ }
119
+ if (!action.schema?.secrets && rawSecrets) {
120
+ throw new errors.InputError(
121
+ `Action "${req.params.actionId}" does not accept secrets`
122
+ );
123
+ }
124
+ const secrets = action.schema?.secrets ? action.schema.secrets(v3.z).safeParse(rawSecrets) : { success: true, data: void 0 };
125
+ if (!secrets.success) {
126
+ throw new errors.InputError(
127
+ `Invalid secrets for action "${req.params.actionId}"`,
128
+ secrets.error
129
+ );
130
+ }
131
+ const result = await action.action({
132
+ input: input.data,
133
+ secrets: secrets.data,
134
+ credentials,
135
+ logger: this.logger
136
+ });
137
+ const output = action.schema?.output ? action.schema.output(v3.z).safeParse(result?.output) : { success: true, data: result?.output };
138
+ if (!output.success) {
139
+ throw new errors.InputError(
140
+ `Invalid output from action "${req.params.actionId}"`,
141
+ output.error
142
+ );
143
+ }
144
+ res.json({ output: output.data });
145
+ };
146
+ router.post(
147
+ "/.backstage/actions/v1/actions/:actionId/invoke",
148
+ invokeHandler({ wrapped: false })
149
+ );
150
+ router.post(
151
+ "/.backstage/actions/v2/actions/:actionId/invoke",
152
+ invokeHandler({ wrapped: true })
125
153
  );
126
154
  return router;
127
155
  }
@@ -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('/.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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"}
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, any>];\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, 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 ...(action.schema?.secrets && {\n secrets: zodToJsonSchema(action.schema.secrets(z)),\n }),\n },\n })),\n });\n });\n\n const invokeHandler =\n (opts: { wrapped: boolean }) =>\n async (\n req: import('express').Request,\n res: import('express').Response,\n ) => {\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 rawInput = opts.wrapped ? req.body.input : req.body;\n const rawSecrets = opts.wrapped ? req.body.secrets : undefined;\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(rawInput)\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 if (action.schema?.secrets && !rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" requires secrets but none were provided`,\n );\n }\n\n if (!action.schema?.secrets && rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" does not accept secrets`,\n );\n }\n\n const secrets = action.schema?.secrets\n ? action.schema.secrets(z).safeParse(rawSecrets)\n : ({ success: true, data: undefined } as const);\n\n if (!secrets.success) {\n throw new InputError(\n `Invalid secrets for action \"${req.params.actionId}\"`,\n secrets.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n secrets: secrets.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 // Deprecated: remove v1 invoke route once all callers have migrated to v2\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n invokeHandler({ wrapped: false }),\n );\n\n router.post(\n '/.backstage/actions/v2/actions/:actionId/invoke',\n invokeHandler({ wrapped: true }),\n );\n\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n TSecretsSchema extends AnyZodObject | undefined = undefined,\n >(\n options: ActionsRegistryActionOptions<\n TInputSchema,\n TOutputSchema,\n TSecretsSchema\n >,\n ): 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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"}
@@ -35,16 +35,16 @@ class AzureBlobStorageUrlReader {
35
35
  return { reader, predicate };
36
36
  });
37
37
  };
38
- // private readonly blobServiceClient: BlobServiceClient;
39
- credsManager;
40
38
  integration;
41
39
  deps;
42
40
  constructor(credsManager, integration, deps) {
43
- this.credsManager = credsManager;
44
41
  this.integration = integration;
45
- this.deps = deps;
42
+ this.deps = {
43
+ ...deps,
44
+ createContainerClient: deps.createContainerClient ?? this.#defaultCreateContainerClient.bind(this, credsManager)
45
+ };
46
46
  }
47
- async createContainerClient(containerName) {
47
+ async #defaultCreateContainerClient(credsManager, containerName) {
48
48
  const accountName = this.integration.config.accountName;
49
49
  const accountKey = this.integration.config.accountKey;
50
50
  if (accountKey && accountName) {
@@ -55,9 +55,7 @@ class AzureBlobStorageUrlReader {
55
55
  );
56
56
  return blobServiceClient2.getContainerClient(containerName);
57
57
  }
58
- const credential = await this.credsManager.getCredentials(
59
- accountName
60
- );
58
+ const credential = await credsManager.getCredentials(accountName);
61
59
  let blobServiceClientUrl;
62
60
  if (this.integration.config.endpoint) {
63
61
  if (this.integration.config.sasToken) {
@@ -82,7 +80,7 @@ class AzureBlobStorageUrlReader {
82
80
  const { etag, lastModifiedAfter } = options ?? {};
83
81
  try {
84
82
  const { path, container } = parseUrl(url);
85
- const containerClient = await this.createContainerClient(container);
83
+ const containerClient = await this.deps.createContainerClient(container);
86
84
  const blobClient = containerClient.getBlobClient(path);
87
85
  const getBlobOptions = {
88
86
  abortSignal: options?.signal,
@@ -116,7 +114,7 @@ class AzureBlobStorageUrlReader {
116
114
  async readTree(url, options) {
117
115
  try {
118
116
  const { path, container } = parseUrl(url);
119
- const containerClient = await this.createContainerClient(container);
117
+ const containerClient = await this.deps.createContainerClient(container);
120
118
  const blobs = containerClient.listBlobsFlat({ prefix: path });
121
119
  const responses = [];
122
120
  for await (const blob of blobs) {
@@ -1 +1 @@
1
- {"version":3,"file":"AzureBlobStorageUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BlobDownloadOptions,\n BlobServiceClient,\n ContainerClient,\n StorageSharedKeyCredential,\n} from '@azure/storage-blob';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { toError, ForwardedError, NotModifiedError } from '@backstage/errors';\nimport { Readable } from 'node:stream';\nimport { relative } from 'node:path/posix';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport {\n AzureBlobStorageIntergation,\n AzureCredentialsManager,\n DefaultAzureCredentialsManager,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\n\nexport function parseUrl(url: string): { path: string; container: string } {\n const parsedUrl = new URL(url);\n const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);\n\n if (pathSegments.length < 1) {\n throw new Error(`Invalid Azure Blob Storage URL format: ${url}`);\n }\n\n // First segment is the container name, rest is the blob path\n const container = pathSegments[0];\n const path = pathSegments.slice(1).join('/');\n\n return { path, container };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for Azure storage accounts urls.\n *\n * @public\n */\nexport class AzureBlobStorageUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n\n const credsManager =\n DefaultAzureCredentialsManager.fromIntegrations(integrations);\n\n return integrations.azureBlobStorage.list().map(integrationConfig => {\n const reader = new AzureBlobStorageUrlReader(\n credsManager,\n integrationConfig,\n {\n treeResponseFactory,\n },\n );\n\n const predicate = (url: URL) =>\n url.host.endsWith(\n `${integrationConfig.config.accountName}.${integrationConfig.config.host}`,\n );\n return { reader, predicate };\n });\n };\n\n // private readonly blobServiceClient: BlobServiceClient;\n\n private readonly credsManager: AzureCredentialsManager;\n private readonly integration: AzureBlobStorageIntergation;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n };\n\n constructor(\n credsManager: AzureCredentialsManager,\n integration: AzureBlobStorageIntergation,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {\n this.credsManager = credsManager;\n this.integration = integration;\n this.deps = deps;\n }\n\n private async createContainerClient(\n containerName: string,\n ): Promise<ContainerClient> {\n const accountName = this.integration.config.accountName; // Use the account name from the integration config\n const accountKey = this.integration.config.accountKey; // Get the account key if it exists\n\n if (accountKey && accountName) {\n const creds = new StorageSharedKeyCredential(accountName, accountKey);\n const blobServiceClient = new BlobServiceClient(\n `https://${accountName}.${this.integration.config.host}`,\n creds,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n // Use the credentials manager to get the correct credentials\n const credential = await this.credsManager.getCredentials(\n accountName as string,\n );\n\n let blobServiceClientUrl: string;\n\n if (this.integration.config.endpoint) {\n if (this.integration.config.sasToken) {\n blobServiceClientUrl = `${this.integration.config.endpoint}?${this.integration.config.sasToken}`;\n } else {\n blobServiceClientUrl = `${this.integration.config.endpoint}`;\n }\n } else {\n blobServiceClientUrl = `https://${this.integration.config.accountName}.${this.integration.config.host}`;\n }\n\n const blobServiceClient = new BlobServiceClient(\n blobServiceClientUrl,\n credential,\n );\n return blobServiceClient.getContainerClient(containerName);\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, container } = parseUrl(url);\n\n const containerClient = await this.createContainerClient(container);\n const blobClient = containerClient.getBlobClient(path);\n\n const getBlobOptions: BlobDownloadOptions = {\n abortSignal: options?.signal,\n conditions: {\n ...(etag && { ifNoneMatch: etag }),\n ...(lastModifiedAfter && { ifModifiedSince: lastModifiedAfter }),\n },\n };\n\n const downloadBlockBlobResponse = await blobClient.download(\n 0,\n undefined,\n getBlobOptions,\n );\n\n return ReadUrlResponseFactory.fromReadable(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n {\n etag: downloadBlockBlobResponse.etag,\n lastModifiedAt: downloadBlockBlobResponse.lastModified,\n },\n );\n } catch (e) {\n if (e.statusCode === 304) {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError(\n 'Could not retrieve file from Azure Blob Storage',\n e,\n );\n }\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.createContainerClient(container);\n const blobs = containerClient.listBlobsFlat({ prefix: path });\n\n const responses = [];\n\n for await (const blob of blobs) {\n const blobClient = containerClient.getBlobClient(blob.name);\n\n const downloadBlockBlobResponse = await blobClient.download(\n undefined,\n undefined,\n { abortSignal: options?.signal },\n );\n\n responses.push({\n data: Readable.from(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n ),\n path: relative(path, blob.name),\n lastModifiedAt: blob.properties.lastModified,\n });\n }\n\n return this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n throw new ForwardedError(\n 'Could not retrieve file tree from Azure Blob Storage',\n e,\n );\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url);\n\n if (path.match(/[*?]/)) {\n throw new Error(\n 'Glob search pattern not implemented for AzureBlobStorageUrlReader',\n );\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 throw toError(e);\n }\n }\n\n toString() {\n const accountName = this.integration.config.accountName;\n const accountKey = this.integration.config.accountKey;\n return `azureBlobStorage{accountName=${accountName},authed=${Boolean(\n accountKey,\n )}}`;\n }\n}\n"],"names":["ScmIntegrations","DefaultAzureCredentialsManager","StorageSharedKeyCredential","blobServiceClient","BlobServiceClient","ReadUrlResponseFactory","NotModifiedError","ForwardedError","Readable","relative","toError"],"mappings":"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"}
1
+ {"version":3,"file":"AzureBlobStorageUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BlobDownloadOptions,\n BlobServiceClient,\n ContainerClient,\n StorageSharedKeyCredential,\n} from '@azure/storage-blob';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { toError, ForwardedError, NotModifiedError } from '@backstage/errors';\nimport { Readable } from 'node:stream';\nimport { relative } from 'node:path/posix';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport {\n AzureBlobStorageIntegration,\n AzureCredentialsManager,\n DefaultAzureCredentialsManager,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\n\nexport function parseUrl(url: string): { path: string; container: string } {\n const parsedUrl = new URL(url);\n const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);\n\n if (pathSegments.length < 1) {\n throw new Error(`Invalid Azure Blob Storage URL format: ${url}`);\n }\n\n // First segment is the container name, rest is the blob path\n const container = pathSegments[0];\n const path = pathSegments.slice(1).join('/');\n\n return { path, container };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for Azure storage accounts urls.\n *\n * @public\n */\nexport class AzureBlobStorageUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n\n const credsManager =\n DefaultAzureCredentialsManager.fromIntegrations(integrations);\n\n return integrations.azureBlobStorage.list().map(integrationConfig => {\n const reader = new AzureBlobStorageUrlReader(\n credsManager,\n integrationConfig,\n {\n treeResponseFactory,\n },\n );\n\n const predicate = (url: URL) =>\n url.host.endsWith(\n `${integrationConfig.config.accountName}.${integrationConfig.config.host}`,\n );\n return { reader, predicate };\n });\n };\n\n private readonly integration: AzureBlobStorageIntegration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n createContainerClient: (containerName: string) => Promise<ContainerClient>;\n };\n\n constructor(\n credsManager: AzureCredentialsManager,\n integration: AzureBlobStorageIntegration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n createContainerClient?: (\n containerName: string,\n ) => Promise<ContainerClient>;\n },\n ) {\n this.integration = integration;\n this.deps = {\n ...deps,\n createContainerClient:\n deps.createContainerClient ??\n this.#defaultCreateContainerClient.bind(this, credsManager),\n };\n }\n\n async #defaultCreateContainerClient(\n credsManager: AzureCredentialsManager,\n containerName: string,\n ): Promise<ContainerClient> {\n const accountName = this.integration.config.accountName;\n const accountKey = this.integration.config.accountKey;\n\n if (accountKey && accountName) {\n const creds = new StorageSharedKeyCredential(accountName, accountKey);\n const blobServiceClient = new BlobServiceClient(\n `https://${accountName}.${this.integration.config.host}`,\n creds,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n\n const credential = await credsManager.getCredentials(accountName as string);\n\n let blobServiceClientUrl: string;\n\n if (this.integration.config.endpoint) {\n if (this.integration.config.sasToken) {\n blobServiceClientUrl = `${this.integration.config.endpoint}?${this.integration.config.sasToken}`;\n } else {\n blobServiceClientUrl = `${this.integration.config.endpoint}`;\n }\n } else {\n blobServiceClientUrl = `https://${this.integration.config.accountName}.${this.integration.config.host}`;\n }\n\n const blobServiceClient = new BlobServiceClient(\n blobServiceClientUrl,\n credential,\n );\n return blobServiceClient.getContainerClient(containerName);\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, container } = parseUrl(url);\n\n const containerClient = await this.deps.createContainerClient(container);\n const blobClient = containerClient.getBlobClient(path);\n\n const getBlobOptions: BlobDownloadOptions = {\n abortSignal: options?.signal,\n conditions: {\n ...(etag && { ifNoneMatch: etag }),\n ...(lastModifiedAfter && { ifModifiedSince: lastModifiedAfter }),\n },\n };\n\n const downloadBlockBlobResponse = await blobClient.download(\n 0,\n undefined,\n getBlobOptions,\n );\n\n return ReadUrlResponseFactory.fromReadable(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n {\n etag: downloadBlockBlobResponse.etag,\n lastModifiedAt: downloadBlockBlobResponse.lastModified,\n },\n );\n } catch (e) {\n if (e.statusCode === 304) {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError(\n 'Could not retrieve file from Azure Blob Storage',\n e,\n );\n }\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.deps.createContainerClient(container);\n const blobs = containerClient.listBlobsFlat({ prefix: path });\n\n const responses = [];\n\n for await (const blob of blobs) {\n const blobClient = containerClient.getBlobClient(blob.name);\n\n const downloadBlockBlobResponse = await blobClient.download(\n undefined,\n undefined,\n { abortSignal: options?.signal },\n );\n\n responses.push({\n data: Readable.from(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n ),\n path: relative(path, blob.name),\n lastModifiedAt: blob.properties.lastModified,\n });\n }\n\n return this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n throw new ForwardedError(\n 'Could not retrieve file tree from Azure Blob Storage',\n e,\n );\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url);\n\n if (path.match(/[*?]/)) {\n throw new Error(\n 'Glob search pattern not implemented for AzureBlobStorageUrlReader',\n );\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 throw toError(e);\n }\n }\n\n toString() {\n const accountName = this.integration.config.accountName;\n const accountKey = this.integration.config.accountKey;\n return `azureBlobStorage{accountName=${accountName},authed=${Boolean(\n accountKey,\n )}}`;\n }\n}\n"],"names":["ScmIntegrations","DefaultAzureCredentialsManager","StorageSharedKeyCredential","blobServiceClient","BlobServiceClient","ReadUrlResponseFactory","NotModifiedError","ForwardedError","Readable","relative","toError"],"mappings":"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"}
@@ -41,10 +41,8 @@ const rateLimitMiddleware = (options) => {
41
41
  skipFailedRequests,
42
42
  passOnStoreError,
43
43
  keyGenerator(req, _res) {
44
- if (!req.ip) {
45
- return req.socket.remoteAddress;
46
- }
47
- return req.ip;
44
+ const ip = req.ip ?? req.socket.remoteAddress;
45
+ return ip ? expressRateLimit.ipKeyGenerator(ip) : "";
48
46
  },
49
47
  skip: (req, _res) => {
50
48
  return Boolean(req.ip && ipAllowList.includes(req.ip)) || Boolean(
@@ -1 +1 @@
1
- {"version":3,"file":"rateLimitMiddleware.cjs.js","sources":["../../src/lib/rateLimitMiddleware.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 */\nimport { RequestHandler } from 'express';\nimport { rateLimit, Store } from 'express-rate-limit';\nimport { Config, readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\n\nexport const rateLimitMiddleware = (options: {\n store?: Store;\n config?: Config;\n}): RequestHandler => {\n const { store, config } = options;\n let windowMs: number = 60000;\n if (config && config.has('window')) {\n const windowDuration = readDurationFromConfig(config, {\n key: 'window',\n });\n windowMs = durationToMilliseconds(windowDuration);\n }\n const limit = config?.getOptionalNumber('incomingRequestLimit');\n const ipAllowList = config?.getOptionalStringArray('ipAllowList') ?? [\n '127.0.0.1',\n '0:0:0:0:0:0:0:1',\n '::1',\n ];\n const skipSuccessfulRequests = config?.getOptionalBoolean(\n 'skipSuccessfulRequests',\n );\n const skipFailedRequests = config?.getOptionalBoolean('skipFailedRequests');\n const passOnStoreError = config?.getOptionalBoolean('passOnStoreError');\n\n return rateLimit({\n windowMs,\n limit,\n skipSuccessfulRequests,\n message: {\n error: {\n name: 'Error',\n message: `Too many requests, please try again later`,\n },\n response: {\n statusCode: 429,\n },\n },\n statusCode: 429,\n skipFailedRequests,\n passOnStoreError: passOnStoreError,\n keyGenerator(req, _res): string {\n if (!req.ip) {\n return req.socket.remoteAddress!;\n }\n return req.ip;\n },\n skip: (req, _res) => {\n return (\n Boolean(req.ip && ipAllowList.includes(req.ip)) ||\n Boolean(\n req.socket.remoteAddress &&\n ipAllowList.includes(req.socket.remoteAddress),\n )\n );\n },\n validate: {\n trustProxy: false,\n },\n store,\n });\n};\n"],"names":["config","readDurationFromConfig","durationToMilliseconds","rateLimit"],"mappings":";;;;;;AAoBO,MAAM,mBAAA,GAAsB,CAAC,OAAA,KAGd;AACpB,EAAA,MAAM,EAAE,KAAA,UAAOA,QAAA,EAAO,GAAI,OAAA;AAC1B,EAAA,IAAI,QAAA,GAAmB,GAAA;AACvB,EAAA,IAAIA,QAAA,IAAUA,QAAA,CAAO,GAAA,CAAI,QAAQ,CAAA,EAAG;AAClC,IAAA,MAAM,cAAA,GAAiBC,8BAAuBD,QAAA,EAAQ;AAAA,MACpD,GAAA,EAAK;AAAA,KACN,CAAA;AACD,IAAA,QAAA,GAAWE,6BAAuB,cAAc,CAAA;AAAA,EAClD;AACA,EAAA,MAAM,KAAA,GAAQF,QAAA,EAAQ,iBAAA,CAAkB,sBAAsB,CAAA;AAC9D,EAAA,MAAM,WAAA,GAAcA,QAAA,EAAQ,sBAAA,CAAuB,aAAa,CAAA,IAAK;AAAA,IACnE,WAAA;AAAA,IACA,iBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,MAAM,yBAAyBA,QAAA,EAAQ,kBAAA;AAAA,IACrC;AAAA,GACF;AACA,EAAA,MAAM,kBAAA,GAAqBA,QAAA,EAAQ,kBAAA,CAAmB,oBAAoB,CAAA;AAC1E,EAAA,MAAM,gBAAA,GAAmBA,QAAA,EAAQ,kBAAA,CAAmB,kBAAkB,CAAA;AAEtE,EAAA,OAAOG,0BAAA,CAAU;AAAA,IACf,QAAA;AAAA,IACA,KAAA;AAAA,IACA,sBAAA;AAAA,IACA,OAAA,EAAS;AAAA,MACP,KAAA,EAAO;AAAA,QACL,IAAA,EAAM,OAAA;AAAA,QACN,OAAA,EAAS,CAAA,yCAAA;AAAA,OACX;AAAA,MACA,QAAA,EAAU;AAAA,QACR,UAAA,EAAY;AAAA;AACd,KACF;AAAA,IACA,UAAA,EAAY,GAAA;AAAA,IACZ,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,YAAA,CAAa,KAAK,IAAA,EAAc;AAC9B,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,QAAA,OAAO,IAAI,MAAA,CAAO,aAAA;AAAA,MACpB;AACA,MAAA,OAAO,GAAA,CAAI,EAAA;AAAA,IACb,CAAA;AAAA,IACA,IAAA,EAAM,CAAC,GAAA,EAAK,IAAA,KAAS;AACnB,MAAA,OACE,OAAA,CAAQ,IAAI,EAAA,IAAM,WAAA,CAAY,SAAS,GAAA,CAAI,EAAE,CAAC,CAAA,IAC9C,OAAA;AAAA,QACE,IAAI,MAAA,CAAO,aAAA,IACT,YAAY,QAAA,CAAS,GAAA,CAAI,OAAO,aAAa;AAAA,OACjD;AAAA,IAEJ,CAAA;AAAA,IACA,QAAA,EAAU;AAAA,MACR,UAAA,EAAY;AAAA,KACd;AAAA,IACA;AAAA,GACD,CAAA;AACH;;;;"}
1
+ {"version":3,"file":"rateLimitMiddleware.cjs.js","sources":["../../src/lib/rateLimitMiddleware.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 */\nimport { RequestHandler } from 'express';\nimport { ipKeyGenerator, rateLimit, Store } from 'express-rate-limit';\nimport { Config, readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\n\nexport const rateLimitMiddleware = (options: {\n store?: Store;\n config?: Config;\n}): RequestHandler => {\n const { store, config } = options;\n let windowMs: number = 60000;\n if (config && config.has('window')) {\n const windowDuration = readDurationFromConfig(config, {\n key: 'window',\n });\n windowMs = durationToMilliseconds(windowDuration);\n }\n const limit = config?.getOptionalNumber('incomingRequestLimit');\n const ipAllowList = config?.getOptionalStringArray('ipAllowList') ?? [\n '127.0.0.1',\n '0:0:0:0:0:0:0:1',\n '::1',\n ];\n const skipSuccessfulRequests = config?.getOptionalBoolean(\n 'skipSuccessfulRequests',\n );\n const skipFailedRequests = config?.getOptionalBoolean('skipFailedRequests');\n const passOnStoreError = config?.getOptionalBoolean('passOnStoreError');\n\n return rateLimit({\n windowMs,\n limit,\n skipSuccessfulRequests,\n message: {\n error: {\n name: 'Error',\n message: `Too many requests, please try again later`,\n },\n response: {\n statusCode: 429,\n },\n },\n statusCode: 429,\n skipFailedRequests,\n passOnStoreError: passOnStoreError,\n keyGenerator(req, _res): string {\n // `ipKeyGenerator` normalizes the address before it is used as a rate\n // limiting key. This is required by express-rate-limit 8.x, which rejects\n // key generators that reference `req.ip` directly, as a naive IPv6 key\n // would let clients trivially bypass the limit by rotating addresses\n // within their allotted block.\n const ip = req.ip ?? req.socket.remoteAddress;\n return ip ? ipKeyGenerator(ip) : '';\n },\n skip: (req, _res) => {\n return (\n Boolean(req.ip && ipAllowList.includes(req.ip)) ||\n Boolean(\n req.socket.remoteAddress &&\n ipAllowList.includes(req.socket.remoteAddress),\n )\n );\n },\n validate: {\n trustProxy: false,\n },\n store,\n });\n};\n"],"names":["config","readDurationFromConfig","durationToMilliseconds","rateLimit","ipKeyGenerator"],"mappings":";;;;;;AAoBO,MAAM,mBAAA,GAAsB,CAAC,OAAA,KAGd;AACpB,EAAA,MAAM,EAAE,KAAA,UAAOA,QAAA,EAAO,GAAI,OAAA;AAC1B,EAAA,IAAI,QAAA,GAAmB,GAAA;AACvB,EAAA,IAAIA,QAAA,IAAUA,QAAA,CAAO,GAAA,CAAI,QAAQ,CAAA,EAAG;AAClC,IAAA,MAAM,cAAA,GAAiBC,8BAAuBD,QAAA,EAAQ;AAAA,MACpD,GAAA,EAAK;AAAA,KACN,CAAA;AACD,IAAA,QAAA,GAAWE,6BAAuB,cAAc,CAAA;AAAA,EAClD;AACA,EAAA,MAAM,KAAA,GAAQF,QAAA,EAAQ,iBAAA,CAAkB,sBAAsB,CAAA;AAC9D,EAAA,MAAM,WAAA,GAAcA,QAAA,EAAQ,sBAAA,CAAuB,aAAa,CAAA,IAAK;AAAA,IACnE,WAAA;AAAA,IACA,iBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,MAAM,yBAAyBA,QAAA,EAAQ,kBAAA;AAAA,IACrC;AAAA,GACF;AACA,EAAA,MAAM,kBAAA,GAAqBA,QAAA,EAAQ,kBAAA,CAAmB,oBAAoB,CAAA;AAC1E,EAAA,MAAM,gBAAA,GAAmBA,QAAA,EAAQ,kBAAA,CAAmB,kBAAkB,CAAA;AAEtE,EAAA,OAAOG,0BAAA,CAAU;AAAA,IACf,QAAA;AAAA,IACA,KAAA;AAAA,IACA,sBAAA;AAAA,IACA,OAAA,EAAS;AAAA,MACP,KAAA,EAAO;AAAA,QACL,IAAA,EAAM,OAAA;AAAA,QACN,OAAA,EAAS,CAAA,yCAAA;AAAA,OACX;AAAA,MACA,QAAA,EAAU;AAAA,QACR,UAAA,EAAY;AAAA;AACd,KACF;AAAA,IACA,UAAA,EAAY,GAAA;AAAA,IACZ,kBAAA;AAAA,IACA,gBAAA;AAAA,IACA,YAAA,CAAa,KAAK,IAAA,EAAc;AAM9B,MAAA,MAAM,EAAA,GAAK,GAAA,CAAI,EAAA,IAAM,GAAA,CAAI,MAAA,CAAO,aAAA;AAChC,MAAA,OAAO,EAAA,GAAKC,+BAAA,CAAe,EAAE,CAAA,GAAI,EAAA;AAAA,IACnC,CAAA;AAAA,IACA,IAAA,EAAM,CAAC,GAAA,EAAK,IAAA,KAAS;AACnB,MAAA,OACE,OAAA,CAAQ,IAAI,EAAA,IAAM,WAAA,CAAY,SAAS,GAAA,CAAI,EAAE,CAAC,CAAA,IAC9C,OAAA;AAAA,QACE,IAAI,MAAA,CAAO,aAAA,IACT,YAAY,QAAA,CAAS,GAAA,CAAI,OAAO,aAAa;AAAA,OACjD;AAAA,IAEJ,CAAA;AAAA,IACA,QAAA,EAAU;AAAA,MACR,UAAA,EAAY;AAAA,KACd;AAAA,IACA;AAAA,GACD,CAAA;AACH;;;;"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version = "0.17.2";
5
+ var version = "0.17.3-next.1";
6
6
  var packageinfo = {
7
7
  version: version};
8
8
 
@@ -1,8 +1,9 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
2
  import { RootConfigService, LoggerService, UrlReaderServiceReadTreeResponse, UrlReaderService, UrlReaderServiceReadUrlOptions, UrlReaderServiceReadUrlResponse, UrlReaderServiceReadTreeOptions, UrlReaderServiceSearchOptions, UrlReaderServiceSearchResponse } from '@backstage/backend-plugin-api';
3
- import { AzureIntegration, AzureDevOpsCredentialsProvider, BitbucketCloudIntegration, BitbucketServerIntegration, GerritIntegration, GithubIntegration, GithubCredentialsProvider, GitLabIntegration, GiteaIntegration, HarnessIntegration, AwsS3Integration, AzureCredentialsManager, AzureBlobStorageIntergation } from '@backstage/integration';
3
+ import { AzureIntegration, AzureDevOpsCredentialsProvider, BitbucketCloudIntegration, BitbucketServerIntegration, GerritIntegration, GithubIntegration, GithubCredentialsProvider, GitLabIntegration, GiteaIntegration, HarnessIntegration, AwsS3Integration, AzureCredentialsManager, AzureBlobStorageIntegration } from '@backstage/integration';
4
4
  import { Readable } from 'node:stream';
5
5
  import { AwsCredentialsManager } from '@backstage/integration-aws-node';
6
+ import { ContainerClient } from '@azure/storage-blob';
6
7
  import { Config } from '@backstage/config';
7
8
 
8
9
  /**
@@ -330,14 +331,14 @@ declare class AwsS3UrlReader implements UrlReaderService {
330
331
  * @public
331
332
  */
332
333
  declare class AzureBlobStorageUrlReader implements UrlReaderService {
334
+ #private;
333
335
  static factory: ReaderFactory;
334
- private readonly credsManager;
335
336
  private readonly integration;
336
337
  private readonly deps;
337
- constructor(credsManager: AzureCredentialsManager, integration: AzureBlobStorageIntergation, deps: {
338
+ constructor(credsManager: AzureCredentialsManager, integration: AzureBlobStorageIntegration, deps: {
338
339
  treeResponseFactory: ReadTreeResponseFactory;
340
+ createContainerClient?: (containerName: string) => Promise<ContainerClient>;
339
341
  });
340
- private createContainerClient;
341
342
  read(url: string): Promise<Buffer>;
342
343
  readUrl(url: string, options?: UrlReaderServiceReadUrlOptions): Promise<UrlReaderServiceReadUrlResponse>;
343
344
  readTree(url: string, options?: UrlReaderServiceReadTreeOptions): Promise<UrlReaderServiceReadTreeResponse>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-defaults",
3
- "version": "0.17.2",
3
+ "version": "0.17.3-next.1",
4
4
  "description": "Backend defaults used by Backstage backend apps",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -218,20 +218,20 @@
218
218
  "@aws-sdk/types": "^3.347.0",
219
219
  "@azure/identity": "^4.0.0",
220
220
  "@azure/storage-blob": "^12.5.0",
221
- "@backstage/backend-app-api": "^1.7.0",
222
- "@backstage/backend-dev-utils": "^0.1.7",
223
- "@backstage/backend-plugin-api": "^1.9.1",
224
- "@backstage/cli-node": "^0.3.2",
225
- "@backstage/config": "^1.3.8",
226
- "@backstage/config-loader": "^1.10.11",
227
- "@backstage/errors": "^1.3.1",
228
- "@backstage/integration": "^2.0.2",
229
- "@backstage/integration-aws-node": "^0.2.0",
230
- "@backstage/plugin-auth-node": "^0.7.1",
231
- "@backstage/plugin-events-node": "^0.4.22",
232
- "@backstage/plugin-permission-common": "^0.9.9",
233
- "@backstage/plugin-permission-node": "^0.11.0",
234
- "@backstage/types": "^1.2.2",
221
+ "@backstage/backend-app-api": "1.7.1-next.0",
222
+ "@backstage/backend-dev-utils": "0.1.7",
223
+ "@backstage/backend-plugin-api": "1.9.2-next.1",
224
+ "@backstage/cli-node": "0.3.2",
225
+ "@backstage/config": "1.3.8",
226
+ "@backstage/config-loader": "1.10.11",
227
+ "@backstage/errors": "1.3.1",
228
+ "@backstage/integration": "2.0.3-next.1",
229
+ "@backstage/integration-aws-node": "0.2.0",
230
+ "@backstage/plugin-auth-node": "0.7.2-next.0",
231
+ "@backstage/plugin-events-node": "0.4.23-next.0",
232
+ "@backstage/plugin-permission-common": "0.9.9",
233
+ "@backstage/plugin-permission-node": "0.11.1-next.0",
234
+ "@backstage/types": "1.2.2",
235
235
  "@google-cloud/storage": "^7.0.0",
236
236
  "@keyv/memcache": "^2.0.1",
237
237
  "@keyv/redis": "^4.0.1",
@@ -286,8 +286,8 @@
286
286
  },
287
287
  "devDependencies": {
288
288
  "@aws-sdk/util-stream-node": "^3.350.0",
289
- "@backstage/backend-test-utils": "^1.11.3",
290
- "@backstage/cli": "^0.36.2",
289
+ "@backstage/backend-test-utils": "1.11.4-next.1",
290
+ "@backstage/cli": "0.36.3-next.1",
291
291
  "@google-cloud/cloud-sql-connector": "^1.4.0",
292
292
  "@types/archiver": "^7.0.0",
293
293
  "@types/base64-stream": "^1.0.2",