@backstage/backend-defaults 0.9.0-next.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @backstage/backend-defaults
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1daedce: Remove Throttle of Bitbucket Server API calls
8
+ - 01edf6e: Allow pass through of redis client and cluster options to Cache core service
9
+ - cf4eb13: Added `actor` property to `BackstageUserPrincipal` containing the subject of the last service (if any) who performed authentication on behalf of the user.
10
+
11
+ ### Patch Changes
12
+
13
+ - 7c6740e: Implemented SRV lookup support in the default `HostDiscovery`. You can now specify internal URLs on the form `http+srv://some-srv-name/api/{{pluginId}}` and they will be resolved in real time.
14
+ - 939116c: Added an optional `backend.trustProxy` app config value, which sets the
15
+ corresponding [Express.js `trust proxy`](https://expressjs.com/en/guide/behind-proxies.html) setting. This lets
16
+ you easily configure proxy trust without making a custom `configure` callback
17
+ for the `rootHttpRouter` service.
18
+
19
+ If you already are using a custom `configure` callback, and if that also _does not_ call `applyDefaults()`, you may want to add the following to it:
20
+
21
+ ```ts
22
+ const trustProxy = config.getOptional('backend.trustProxy');
23
+ if (trustProxy !== undefined) {
24
+ app.set('trust proxy', trustProxy);
25
+ }
26
+ ```
27
+
28
+ - 175528c: Adds `backend.auditor.severityLogLevelMappings` to map severity levels to log levels.
29
+ - Updated dependencies
30
+ - @backstage/backend-plugin-api@1.3.0
31
+ - @backstage/integration@1.16.3
32
+ - @backstage/backend-app-api@1.2.2
33
+ - @backstage/plugin-auth-node@0.6.2
34
+ - @backstage/plugin-permission-node@0.9.1
35
+ - @backstage/backend-dev-utils@0.1.5
36
+ - @backstage/cli-node@0.2.13
37
+ - @backstage/config@1.3.2
38
+ - @backstage/config-loader@1.10.0
39
+ - @backstage/errors@1.2.7
40
+ - @backstage/integration-aws-node@0.1.15
41
+ - @backstage/types@1.2.1
42
+ - @backstage/plugin-events-node@0.4.10
43
+
3
44
  ## 0.9.0-next.2
4
45
 
5
46
  ### Patch Changes
package/config.d.ts CHANGED
@@ -51,6 +51,19 @@ export interface Config {
51
51
  serverShutdownDelay?: string | HumanDuration;
52
52
  };
53
53
 
54
+ /**
55
+ * Corresponds to the Express `trust proxy` setting.
56
+ *
57
+ * @see https://expressjs.com/en/guide/behind-proxies.html
58
+ * @remarks
59
+ *
60
+ * This setting is used to determine whether the backend should trust the
61
+ * `X-Forwarded-*` headers that are set by proxies. This is important for
62
+ * determining the original client IP address and protocol (HTTP/HTTPS) when
63
+ * the backend is behind a reverse proxy or load balancer.
64
+ */
65
+ trustProxy?: boolean | number | string | string[];
66
+
54
67
  /** Address that the backend should listen to. */
55
68
  listen?:
56
69
  | string
@@ -730,18 +743,62 @@ export interface Config {
730
743
  */
731
744
  discovery?: {
732
745
  /**
733
- * A list of target baseUrls and the associated plugins.
746
+ * A list of target base URLs and their associated plugins.
747
+ *
748
+ * @example
749
+ *
750
+ * ```yaml
751
+ * discovery:
752
+ * endpoints:
753
+ * - target: https://internal.example.com/internal-catalog
754
+ * plugins: [catalog]
755
+ * - target: https://internal.example.com/secure/api/{{pluginId}}
756
+ * plugins: [auth, permission]
757
+ * - target:
758
+ * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/search
759
+ * external: https://example.com/search
760
+ * plugins: [search]
761
+ * ```
734
762
  */
735
763
  endpoints: Array<{
736
764
  /**
737
- * The target base URL to use for the plugin.
765
+ * The target base URL to use for the given set of plugins. Note that this
766
+ * needs to be a full URL including the protocol and path parts that fully
767
+ * address the root of a plugin's API endpoints.
738
768
  *
739
- * Can be either a string or an object with internal and external keys.
740
- * Targets with `{{pluginId}}` or `{{ pluginId }} in the URL will be replaced with the plugin ID.
769
+ * @remarks
770
+ *
771
+ * Can be either a single URL or an object where you can explicitly give a
772
+ * dedicated URL for internal (as seen from the backend) and/or external (as
773
+ * seen from the frontend) lookups.
774
+ *
775
+ * The default behavior is to use the backend base URL for external lookups,
776
+ * and a URL formed from the `.listen` and `.https` configs for internal
777
+ * lookups. Adding discovery endpoints as described here overrides one or both
778
+ * of those behaviors for a given set of plugins.
779
+ *
780
+ * URLs can be in the form of a regular HTTP or HTTPS URL if you are using
781
+ * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if
782
+ * you add `+src` to the protocol part then the hostname is treated as an SRV
783
+ * record name and resolved. For example, if you pass in
784
+ * `http+srv://<srv-record>/path` then the record part is resolved into an
785
+ * actual host and port (with random weighted choice as usual when there is
786
+ * more than one match).
787
+ *
788
+ * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them
789
+ * will have them replaced with the plugin ID.
790
+ *
791
+ * Example URLs:
792
+ *
793
+ * - `https://internal.example.com/secure/api/{{pluginId}}`
794
+ * - `http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}`
795
+ * (can only be used in the `internal` key)
741
796
  */
742
797
  target: string | { internal?: string; external?: string };
743
798
  /**
744
- * Array of plugins which use the target base URL.
799
+ * Array of plugins which use that target base URL.
800
+ *
801
+ * The special value `*` can be used to match all plugins.
745
802
  */
746
803
  plugins: string[];
747
804
  }>;
@@ -1,5 +1,5 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
- import { DiscoveryService, RootConfigService } from '@backstage/backend-plugin-api';
2
+ import { LoggerService, DiscoveryService, RootConfigService } from '@backstage/backend-plugin-api';
3
3
 
4
4
  /**
5
5
  * Service discovery for inter-plugin communication.
@@ -13,49 +13,118 @@ import { DiscoveryService, RootConfigService } from '@backstage/backend-plugin-a
13
13
  declare const discoveryServiceFactory: _backstage_backend_plugin_api.ServiceFactory<_backstage_backend_plugin_api.DiscoveryService, "plugin", "singleton">;
14
14
 
15
15
  /**
16
- * HostDiscovery is a basic DiscoveryService implementation
17
- * that can handle plugins that are hosted in a single or multiple deployments.
16
+ * A list of target base URLs and their associated plugins.
18
17
  *
19
- * The deployment may be scaled horizontally, as long as the external URL
20
- * is the same for all instances. However, internal URLs will always be
21
- * resolved to the same host, so there won't be any balancing of internal traffic.
18
+ * @public
19
+ */
20
+ interface HostDiscoveryEndpoint {
21
+ /**
22
+ * The target base URL to use for the given set of plugins. Note that this
23
+ * needs to be a full URL _including_ the protocol and path parts that fully
24
+ * address the root of a plugin's API endpoints.
25
+ *
26
+ * @remarks
27
+ *
28
+ * Can be either a single URL or an object where you can explicitly give a
29
+ * dedicated URL for internal (as seen from the backend) and/or external (as
30
+ * seen from the frontend) lookups.
31
+ *
32
+ * The default behavior is to use the backend base URL for external lookups,
33
+ * and a URL formed from the `.listen` and `.https` configs for internal
34
+ * lookups. Adding discovery endpoints as described here overrides one or both
35
+ * of those behaviors for a given set of plugins.
36
+ *
37
+ * URLs can be in the form of a regular HTTP or HTTPS URL if you are using
38
+ * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if
39
+ * you add `+src` to the protocol part then the hostname is treated as an SRV
40
+ * record name and resolved. For example, if you pass in
41
+ * `http+srv://<record>/path` then the record part is resolved into an
42
+ * actual host and port (with random weighted choice as usual when there is
43
+ * more than one match).
44
+ *
45
+ * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them
46
+ * will have them replaced with the plugin ID.
47
+ *
48
+ * Example URLs:
49
+ *
50
+ * - `https://internal.example.com/secure/api/{{ pluginId }}`
51
+ * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`
52
+ * (can only be used in the `internal` key)
53
+ */
54
+ target: string | {
55
+ internal?: string;
56
+ external?: string;
57
+ };
58
+ /**
59
+ * Array of plugins which use that target base URL.
60
+ *
61
+ * The special value `*` can be used to match all plugins.
62
+ */
63
+ plugins: string[];
64
+ }
65
+ /**
66
+ * Options for the {@link HostDiscovery} class.
22
67
  *
23
68
  * @public
24
69
  */
25
- declare class HostDiscovery implements DiscoveryService {
26
- private readonly internalBaseUrl;
27
- private readonly externalBaseUrl;
28
- private readonly discoveryConfig;
70
+ interface HostDiscoveryOptions {
71
+ /**
72
+ * The logger to use.
73
+ */
74
+ logger: LoggerService;
29
75
  /**
30
- * Creates a new HostDiscovery discovery instance by reading
31
- * from the `backend` config section, specifically the `.baseUrl` for
32
- * discovering the external URL, and the `.listen` and `.https` config
33
- * for the internal one.
34
- *
35
- * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.
36
- * eg.
37
- *
38
- * ```yaml
39
- * discovery:
40
- * endpoints:
41
- * - target: https://internal.example.com/internal-catalog
42
- * plugins: [catalog]
43
- * - target: https://internal.example.com/secure/api/{{pluginId}}
44
- * plugins: [auth, permission]
45
- * - target:
46
- * internal: https://internal.example.com/search
47
- * external: https://example.com/search
48
- * plugins: [search]
49
- * ```
50
- *
51
- * The fixed base path is `/api`, meaning the default full internal
52
- * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
76
+ * A default set of endpoints to use.
77
+ *
78
+ * @remarks
79
+ *
80
+ * These endpoints have lower priority than any that are defined in
81
+ * app-config, but higher priority than the fallback ones.
82
+ *
83
+ * This parameter is usedful for example if you want to provide a shared
84
+ * library of core services to your plugin developers, which is set up for the
85
+ * default behaviors in your org. This alleviates the need for replicating any
86
+ * given set of endpoint config in every backend that you deploy.
53
87
  */
54
- static fromConfig(config: RootConfigService): HostDiscovery;
88
+ defaultEndpoints?: HostDiscoveryEndpoint[];
89
+ }
90
+ /**
91
+ * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation
92
+ * that can handle plugins that are hosted in a single or multiple deployments.
93
+ *
94
+ * @public
95
+ * @remarks
96
+ *
97
+ * Configuration is read from the `backend` config section, specifically the
98
+ * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`
99
+ * config for the internal one. The fixed base path for these is `/api`, meaning
100
+ * for example the default full internal path for the `catalog` plugin typically
101
+ * will be `http://localhost:7007/api/catalog`.
102
+ *
103
+ * Those defaults can be overridden by providing a target and corresponding
104
+ * plugins in `discovery.endpoints`, e.g.:
105
+ *
106
+ * ```yaml
107
+ * discovery:
108
+ * endpoints:
109
+ * # Set a static internal and external base URL for a plugin
110
+ * - target: https://internal.example.com/internal-catalog
111
+ * plugins: [catalog]
112
+ * # Sets a dynamic internal and external base URL pattern for two plugins
113
+ * - target: https://internal.example.com/secure/api/{{pluginId}}
114
+ * plugins: [auth, permission]
115
+ * # Sets a dynamic base URL pattern for only the internal resolution for all
116
+ * # other plugins, while leaving the external resolution unaffected
117
+ * - target:
118
+ * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}
119
+ * plugins: [*]
120
+ * ```
121
+ */
122
+ declare class HostDiscovery implements DiscoveryService {
123
+ #private;
124
+ static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions): HostDiscovery;
55
125
  private constructor();
56
- private getTargetFromConfig;
57
126
  getBaseUrl(pluginId: string): Promise<string>;
58
127
  getExternalBaseUrl(pluginId: string): Promise<string>;
59
128
  }
60
129
 
61
- export { HostDiscovery, discoveryServiceFactory };
130
+ export { HostDiscovery, type HostDiscoveryEndpoint, type HostDiscoveryOptions, discoveryServiceFactory };
@@ -28,7 +28,8 @@ class DefaultAuthService {
28
28
  return helpers.createCredentialsWithUserPrincipal(
29
29
  userResult2.userEntityRef,
30
30
  pluginResult.limitedUserToken,
31
- this.#getJwtExpiration(pluginResult.limitedUserToken)
31
+ this.#getJwtExpiration(pluginResult.limitedUserToken),
32
+ pluginResult.subject
32
33
  );
33
34
  }
34
35
  return helpers.createCredentialsWithServicePrincipal(pluginResult.subject);
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n constructor(\n private readonly userTokenHandler: UserTokenHandler,\n private readonly pluginTokenHandler: PluginTokenHandler,\n private readonly externalTokenHandler: ExternalTokenHandler,\n private readonly pluginId: string,\n private readonly disableDefaultAuthPolicy: boolean,\n private readonly pluginKeySource: PluginKeySource,\n ) {}\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existance.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n constructor(\n private readonly userTokenHandler: UserTokenHandler,\n private readonly pluginTokenHandler: PluginTokenHandler,\n private readonly externalTokenHandler: ExternalTokenHandler,\n private readonly pluginId: string,\n private readonly disableDefaultAuthPolicy: boolean,\n private readonly pluginKeySource: PluginKeySource,\n ) {}\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existance.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":"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"}
@@ -19,7 +19,7 @@ function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
19
19
  }
20
20
  );
21
21
  }
22
- function createCredentialsWithUserPrincipal(sub, token, expiresAt) {
22
+ function createCredentialsWithUserPrincipal(sub, token, expiresAt, actor) {
23
23
  return Object.defineProperty(
24
24
  {
25
25
  $$type: "@backstage/BackstageCredentials",
@@ -27,7 +27,10 @@ function createCredentialsWithUserPrincipal(sub, token, expiresAt) {
27
27
  expiresAt,
28
28
  principal: {
29
29
  type: "user",
30
- userEntityRef: sub
30
+ userEntityRef: sub,
31
+ ...actor && {
32
+ actor: { type: "service", subject: actor }
33
+ }
31
34
  }
32
35
  },
33
36
  "token",
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'service',\n subject: sub,\n accessRestrictions,\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal: {\n type: 'user',\n userEntityRef: sub,\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n return {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'none',\n },\n };\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n"],"names":[],"mappings":";;AAyBgB,SAAA,qCAAA,CACd,GACA,EAAA,KAAA,EACA,kBACyD,EAAA;AACzD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,SAAA;AAAA,QACN,OAAS,EAAA,GAAA;AAAA,QACT;AAAA;AACF,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEgB,SAAA,kCAAA,CACd,GACA,EAAA,KAAA,EACA,SACsD,EAAA;AACtD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAA;AAAA,MACA,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,MAAA;AAAA,QACN,aAAe,EAAA;AAAA;AACjB,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEO,SAAS,kCAA2F,GAAA;AACzG,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,iCAAA;AAAA,IACR,OAAS,EAAA,IAAA;AAAA,IACT,SAAW,EAAA;AAAA,MACT,IAAM,EAAA;AAAA;AACR,GACF;AACF;AAEO,SAAS,+BACd,WAGA,EAAA;AACA,EAAI,IAAA,WAAA,CAAY,WAAW,iCAAmC,EAAA;AAC5D,IAAM,MAAA,IAAI,MAAM,yBAAyB,CAAA;AAAA;AAG3C,EAAA,MAAM,mBAAsB,GAAA,WAAA;AAI5B,EAAI,IAAA,mBAAA,CAAoB,YAAY,IAAM,EAAA;AACxC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,oBAAoB,OAAO,CAAA;AAAA,KAC3D;AAAA;AAGF,EAAO,OAAA,mBAAA;AACT;;;;;;;"}
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'service',\n subject: sub,\n accessRestrictions,\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n actor?: string,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal: {\n type: 'user',\n userEntityRef: sub,\n ...(actor && {\n actor: { type: 'service', subject: actor },\n }),\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n return {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'none',\n },\n };\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n"],"names":[],"mappings":";;AAyBgB,SAAA,qCAAA,CACd,GACA,EAAA,KAAA,EACA,kBACyD,EAAA;AACzD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,SAAA;AAAA,QACN,OAAS,EAAA,GAAA;AAAA,QACT;AAAA;AACF,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEO,SAAS,kCACd,CAAA,GAAA,EACA,KACA,EAAA,SAAA,EACA,KACsD,EAAA;AACtD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAA;AAAA,MACA,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,MAAA;AAAA,QACN,aAAe,EAAA,GAAA;AAAA,QACf,GAAI,KAAS,IAAA;AAAA,UACX,KAAO,EAAA,EAAE,IAAM,EAAA,SAAA,EAAW,SAAS,KAAM;AAAA;AAC3C;AACF,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEO,SAAS,kCAA2F,GAAA;AACzG,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,iCAAA;AAAA,IACR,OAAS,EAAA,IAAA;AAAA,IACT,SAAW,EAAA;AAAA,MACT,IAAM,EAAA;AAAA;AACR,GACF;AACF;AAEO,SAAS,+BACd,WAGA,EAAA;AACA,EAAI,IAAA,WAAA,CAAY,WAAW,iCAAmC,EAAA;AAC5D,IAAM,MAAA,IAAI,MAAM,yBAAyB,CAAA;AAAA;AAG3C,EAAA,MAAM,mBAAsB,GAAA,WAAA;AAI5B,EAAI,IAAA,mBAAA,CAAoB,YAAY,IAAM,EAAA;AACxC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,oBAAoB,OAAO,CAAA;AAAA,KAC3D;AAAA;AAGF,EAAO,OAAA,mBAAA;AACT;;;;;;;"}
@@ -1,41 +1,56 @@
1
1
  'use strict';
2
2
 
3
3
  var config = require('../rootHttpRouter/http/config.cjs.js');
4
+ var SrvResolvers = require('./SrvResolvers.cjs.js');
5
+ var lodash = require('lodash');
4
6
 
5
7
  class HostDiscovery {
6
- constructor(internalBaseUrl, externalBaseUrl, discoveryConfig) {
7
- this.internalBaseUrl = internalBaseUrl;
8
- this.externalBaseUrl = externalBaseUrl;
9
- this.discoveryConfig = discoveryConfig;
8
+ #srvResolver;
9
+ #internalResolvers = /* @__PURE__ */ new Map();
10
+ #externalResolvers = /* @__PURE__ */ new Map();
11
+ #internalFallbackResolver = async () => {
12
+ throw new Error("Not initialized");
13
+ };
14
+ #externalFallbackResolver = async () => {
15
+ throw new Error("Not initialized");
16
+ };
17
+ static fromConfig(config, options) {
18
+ const discovery = new HostDiscovery(new SrvResolvers.SrvResolvers());
19
+ discovery.#updateResolvers(config, options?.defaultEndpoints);
20
+ config.subscribe?.(() => {
21
+ try {
22
+ discovery.#updateResolvers(config, options?.defaultEndpoints);
23
+ } catch (e) {
24
+ options?.logger.error(`Failed to update discovery service: ${e}`);
25
+ }
26
+ });
27
+ return discovery;
10
28
  }
11
- /**
12
- * Creates a new HostDiscovery discovery instance by reading
13
- * from the `backend` config section, specifically the `.baseUrl` for
14
- * discovering the external URL, and the `.listen` and `.https` config
15
- * for the internal one.
16
- *
17
- * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.
18
- * eg.
19
- *
20
- * ```yaml
21
- * discovery:
22
- * endpoints:
23
- * - target: https://internal.example.com/internal-catalog
24
- * plugins: [catalog]
25
- * - target: https://internal.example.com/secure/api/{{pluginId}}
26
- * plugins: [auth, permission]
27
- * - target:
28
- * internal: https://internal.example.com/search
29
- * external: https://example.com/search
30
- * plugins: [search]
31
- * ```
32
- *
33
- * The fixed base path is `/api`, meaning the default full internal
34
- * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
35
- */
36
- static fromConfig(config$1) {
37
- const basePath = "/api";
38
- const externalBaseUrl = config$1.getString("backend.baseUrl").replace(/\/+$/, "");
29
+ constructor(srvResolver) {
30
+ this.#srvResolver = srvResolver;
31
+ this.#internalResolvers = /* @__PURE__ */ new Map();
32
+ this.#externalResolvers = /* @__PURE__ */ new Map();
33
+ this.#internalFallbackResolver = () => {
34
+ throw new Error("Not initialized");
35
+ };
36
+ this.#externalFallbackResolver = () => {
37
+ throw new Error("Not initialized");
38
+ };
39
+ }
40
+ async getBaseUrl(pluginId) {
41
+ const resolver = this.#internalResolvers.get(pluginId) ?? this.#internalResolvers.get("*") ?? this.#internalFallbackResolver;
42
+ return await resolver(pluginId);
43
+ }
44
+ async getExternalBaseUrl(pluginId) {
45
+ const resolver = this.#externalResolvers.get(pluginId) ?? this.#externalResolvers.get("*") ?? this.#externalFallbackResolver;
46
+ return await resolver(pluginId);
47
+ }
48
+ #updateResolvers(config, defaultEndpoints) {
49
+ this.#updateFallbackResolvers(config);
50
+ this.#updatePluginResolvers(config, defaultEndpoints);
51
+ }
52
+ #updateFallbackResolvers(config$1) {
53
+ const backendBaseUrl = lodash.trimEnd(config$1.getString("backend.baseUrl"), "/");
39
54
  const {
40
55
  listen: { host: listenHost = "::", port: listenPort }
41
56
  } = config.readHttpServerOptions(config$1.getConfig("backend"));
@@ -49,31 +64,91 @@ class HostDiscovery {
49
64
  if (host.includes(":")) {
50
65
  host = `[${host}]`;
51
66
  }
52
- const internalBaseUrl = `${protocol}://${host}:${listenPort}`;
53
- return new HostDiscovery(
54
- internalBaseUrl + basePath,
55
- externalBaseUrl + basePath,
56
- config$1.getOptionalConfig("discovery")
67
+ this.#internalFallbackResolver = this.#makeResolver(
68
+ `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,
69
+ false
57
70
  );
58
- }
59
- getTargetFromConfig(pluginId, type) {
60
- const endpoints = this.discoveryConfig?.getOptionalConfigArray("endpoints");
61
- const targetOrObj = endpoints?.find((endpoint) => endpoint.getStringArray("plugins").includes(pluginId))?.get("target");
62
- const target = typeof targetOrObj === "string" ? targetOrObj : targetOrObj?.[type];
63
- if (!target) {
64
- const baseUrl = type === "external" ? this.externalBaseUrl : this.internalBaseUrl;
65
- return `${baseUrl}/${encodeURIComponent(pluginId)}`;
66
- }
67
- return target.replace(
68
- /\{\{\s*pluginId\s*\}\}/g,
69
- encodeURIComponent(pluginId)
71
+ this.#externalFallbackResolver = this.#makeResolver(
72
+ `${backendBaseUrl}/api/{{pluginId}}`,
73
+ false
70
74
  );
71
75
  }
72
- async getBaseUrl(pluginId) {
73
- return this.getTargetFromConfig(pluginId, "internal");
76
+ #updatePluginResolvers(config, defaultEndpoints) {
77
+ const endpoints = defaultEndpoints?.slice() ?? [];
78
+ const endpointConfigs = config.getOptionalConfigArray(
79
+ "discovery.endpoints"
80
+ );
81
+ for (const endpointConfig of endpointConfigs ?? []) {
82
+ if (typeof endpointConfig.get("target") === "string") {
83
+ endpoints.push({
84
+ target: endpointConfig.getString("target"),
85
+ plugins: endpointConfig.getStringArray("plugins")
86
+ });
87
+ } else {
88
+ endpoints.push({
89
+ target: {
90
+ internal: endpointConfig.getOptionalString("target.internal"),
91
+ external: endpointConfig.getOptionalString("target.external")
92
+ },
93
+ plugins: endpointConfig.getStringArray("plugins")
94
+ });
95
+ }
96
+ }
97
+ const internalResolvers = /* @__PURE__ */ new Map();
98
+ const externalResolvers = /* @__PURE__ */ new Map();
99
+ for (const { target, plugins } of endpoints) {
100
+ let internalResolver;
101
+ let externalResolver;
102
+ if (typeof target === "string") {
103
+ internalResolver = externalResolver = this.#makeResolver(target, false);
104
+ } else {
105
+ if (target.internal) {
106
+ internalResolver = this.#makeResolver(target.internal, true);
107
+ }
108
+ if (target.external) {
109
+ externalResolver = this.#makeResolver(target.external, false);
110
+ }
111
+ }
112
+ if (internalResolver) {
113
+ for (const pluginId of plugins) {
114
+ internalResolvers.set(pluginId, internalResolver);
115
+ }
116
+ }
117
+ if (externalResolver) {
118
+ for (const pluginId of plugins) {
119
+ externalResolvers.set(pluginId, externalResolver);
120
+ }
121
+ }
122
+ }
123
+ this.#internalResolvers = internalResolvers;
124
+ this.#externalResolvers = externalResolvers;
74
125
  }
75
- async getExternalBaseUrl(pluginId) {
76
- return this.getTargetFromConfig(pluginId, "external");
126
+ #makeResolver(urlPattern, allowSrv) {
127
+ const withPluginId = (pluginId, url) => {
128
+ return url.replace(
129
+ /\{\{\s*pluginId\s*\}\}/g,
130
+ encodeURIComponent(pluginId)
131
+ );
132
+ };
133
+ if (!this.#srvResolver.isSrvUrl(urlPattern)) {
134
+ return async (pluginId) => withPluginId(pluginId, urlPattern);
135
+ }
136
+ if (!allowSrv) {
137
+ throw new Error(
138
+ `SRV resolver URLs cannot be used in the target for external endpoints`
139
+ );
140
+ }
141
+ const lazyResolvers = /* @__PURE__ */ new Map();
142
+ return async (pluginId) => {
143
+ let lazyResolver = lazyResolvers.get(pluginId);
144
+ if (!lazyResolver) {
145
+ lazyResolver = this.#srvResolver.getResolver(
146
+ withPluginId(pluginId, urlPattern)
147
+ );
148
+ lazyResolvers.set(pluginId, lazyResolver);
149
+ }
150
+ return await lazyResolver();
151
+ };
77
152
  }
78
153
  }
79
154
 
@@ -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 RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\n\ntype Target = string | { internal: string; external: string };\n\n/**\n * HostDiscovery is a basic DiscoveryService implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * The deployment may be scaled horizontally, as long as the external URL\n * is the same for all instances. However, internal URLs will always be\n * resolved to the same host, so there won't be any balancing of internal traffic.\n *\n * @public\n */\nexport class HostDiscovery implements DiscoveryService {\n /**\n * Creates a new HostDiscovery discovery instance by reading\n * from the `backend` config section, specifically the `.baseUrl` for\n * discovering the external URL, and the `.listen` and `.https` config\n * for the internal one.\n *\n * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.\n * eg.\n *\n * ```yaml\n * discovery:\n * endpoints:\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * - target:\n * internal: https://internal.example.com/search\n * external: https://example.com/search\n * plugins: [search]\n * ```\n *\n * The fixed base path is `/api`, meaning the default full internal\n * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.\n */\n static fromConfig(config: RootConfigService) {\n const basePath = '/api';\n const externalBaseUrl = config\n .getString('backend.baseUrl')\n .replace(/\\/+$/, '');\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 const internalBaseUrl = `${protocol}://${host}:${listenPort}`;\n\n return new HostDiscovery(\n internalBaseUrl + basePath,\n externalBaseUrl + basePath,\n config.getOptionalConfig('discovery'),\n );\n }\n\n private constructor(\n private readonly internalBaseUrl: string,\n private readonly externalBaseUrl: string,\n private readonly discoveryConfig: Config | undefined,\n ) {}\n\n private getTargetFromConfig(pluginId: string, type: 'internal' | 'external') {\n const endpoints = this.discoveryConfig?.getOptionalConfigArray('endpoints');\n\n const targetOrObj = endpoints\n ?.find(endpoint => endpoint.getStringArray('plugins').includes(pluginId))\n ?.get<Target>('target');\n\n const target =\n typeof targetOrObj === 'string' ? targetOrObj : targetOrObj?.[type];\n\n if (!target) {\n const baseUrl =\n type === 'external' ? this.externalBaseUrl : this.internalBaseUrl;\n\n return `${baseUrl}/${encodeURIComponent(pluginId)}`;\n }\n\n return target.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n return this.getTargetFromConfig(pluginId, 'internal');\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n return this.getTargetFromConfig(pluginId, 'external');\n }\n}\n"],"names":["config","readHttpServerOptions"],"mappings":";;;;AAmCO,MAAM,aAA0C,CAAA;AAAA,EA4D7C,WAAA,CACW,eACA,EAAA,eAAA,EACA,eACjB,EAAA;AAHiB,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AAAA;AAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAtCH,OAAO,WAAWA,QAA2B,EAAA;AAC3C,IAAA,MAAM,QAAW,GAAA,MAAA;AACjB,IAAA,MAAM,kBAAkBA,QACrB,CAAA,SAAA,CAAU,iBAAiB,CAC3B,CAAA,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAErB,IAAM,MAAA;AAAA,MACJ,QAAQ,EAAE,IAAA,EAAM,UAAa,GAAA,IAAA,EAAM,MAAM,UAAW;AAAA,KAClD,GAAAC,4BAAA,CAAsBD,QAAO,CAAA,SAAA,CAAU,SAAS,CAAC,CAAA;AACrD,IAAA,MAAM,QAAW,GAAAA,QAAA,CAAO,GAAI,CAAA,eAAe,IAAI,OAAU,GAAA,MAAA;AAGzD,IAAA,IAAI,IAAO,GAAA,UAAA;AACX,IAAI,IAAA,IAAA,KAAS,IAAQ,IAAA,IAAA,KAAS,EAAI,EAAA;AAIhC,MAAO,IAAA,GAAA,WAAA;AAAA,KACT,MAAA,IAAW,SAAS,SAAW,EAAA;AAC7B,MAAO,IAAA,GAAA,WAAA;AAAA;AAET,IAAI,IAAA,IAAA,CAAK,QAAS,CAAA,GAAG,CAAG,EAAA;AACtB,MAAA,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA,CAAA;AAAA;AAGjB,IAAA,MAAM,kBAAkB,CAAG,EAAA,QAAQ,CAAM,GAAA,EAAA,IAAI,IAAI,UAAU,CAAA,CAAA;AAE3D,IAAA,OAAO,IAAI,aAAA;AAAA,MACT,eAAkB,GAAA,QAAA;AAAA,MAClB,eAAkB,GAAA,QAAA;AAAA,MAClBA,QAAA,CAAO,kBAAkB,WAAW;AAAA,KACtC;AAAA;AACF,EAQQ,mBAAA,CAAoB,UAAkB,IAA+B,EAAA;AAC3E,IAAA,MAAM,SAAY,GAAA,IAAA,CAAK,eAAiB,EAAA,sBAAA,CAAuB,WAAW,CAAA;AAE1E,IAAA,MAAM,WAAc,GAAA,SAAA,EAChB,IAAK,CAAA,CAAA,QAAA,KAAY,QAAS,CAAA,cAAA,CAAe,SAAS,CAAA,CAAE,QAAS,CAAA,QAAQ,CAAC,CAAA,EACtE,IAAY,QAAQ,CAAA;AAExB,IAAA,MAAM,SACJ,OAAO,WAAA,KAAgB,QAAW,GAAA,WAAA,GAAc,cAAc,IAAI,CAAA;AAEpE,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAA,MAAM,OACJ,GAAA,IAAA,KAAS,UAAa,GAAA,IAAA,CAAK,kBAAkB,IAAK,CAAA,eAAA;AAEpD,MAAA,OAAO,CAAG,EAAA,OAAO,CAAI,CAAA,EAAA,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA;AAGnD,IAAA,OAAO,MAAO,CAAA,OAAA;AAAA,MACZ,yBAAA;AAAA,MACA,mBAAmB,QAAQ;AAAA,KAC7B;AAAA;AACF,EAEA,MAAM,WAAW,QAAmC,EAAA;AAClD,IAAO,OAAA,IAAA,CAAK,mBAAoB,CAAA,QAAA,EAAU,UAAU,CAAA;AAAA;AACtD,EAEA,MAAM,mBAAmB,QAAmC,EAAA;AAC1D,IAAO,OAAA,IAAA,CAAK,mBAAoB,CAAA,QAAA,EAAU,UAAU,CAAA;AAAA;AAExD;;;;"}
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';\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 const endpointConfigs = config.getOptionalConfigArray(\n 'discovery.endpoints',\n );\n for (const endpointConfig of endpointConfigs ?? []) {\n if (typeof endpointConfig.get('target') === 'string') {\n endpoints.push({\n target: endpointConfig.getString('target'),\n plugins: endpointConfig.getStringArray('plugins'),\n });\n } else {\n endpoints.push({\n target: {\n internal: endpointConfig.getOptionalString('target.internal'),\n external: endpointConfig.getOptionalString('target.external'),\n },\n plugins: endpointConfig.getStringArray('plugins'),\n });\n }\n }\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"],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var dns = require('dns');
5
+
6
+ const PROTOCOL_SUFFIX = "+srv:";
7
+ class SrvResolvers {
8
+ #cache;
9
+ #cacheTtlMillis;
10
+ #resolveSrv;
11
+ constructor(options) {
12
+ this.#cache = /* @__PURE__ */ new Map();
13
+ this.#cacheTtlMillis = options?.cacheTtlMillis ?? 1e3;
14
+ this.#resolveSrv = options?.resolveSrv ?? ((host) => new Promise((resolve, reject) => {
15
+ dns.resolveSrv(host, (err, result) => {
16
+ if (err) {
17
+ reject(err);
18
+ } else {
19
+ resolve(result);
20
+ }
21
+ });
22
+ }));
23
+ }
24
+ isSrvUrl(url) {
25
+ try {
26
+ this.#parseSrvUrl(url);
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Get a resolver function for a given SRV form URL.
34
+ *
35
+ * @param url An SRV form URL, e.g. `http+srv://myplugin.services.region.example.net/api/myplugin`
36
+ * @returns A function that returns resolved URLs, e.g. `http://1234abcd.region.example.net:8080/api/myplugin`
37
+ */
38
+ getResolver(url) {
39
+ const { protocol, host, path } = this.#parseSrvUrl(url);
40
+ return () => this.#resolveHost(host).then(
41
+ (resolved) => `${protocol}://${resolved}${path}`
42
+ );
43
+ }
44
+ /**
45
+ * Attempts to parse out the relevant parts of an SRV URL.
46
+ */
47
+ #parseSrvUrl(url) {
48
+ let parsedUrl;
49
+ try {
50
+ parsedUrl = new URL(url);
51
+ } catch {
52
+ throw new errors.InputError(
53
+ `SRV resolver expected a valid URL starting with http(s)+srv:// but got '${url}'`
54
+ );
55
+ }
56
+ if (!parsedUrl.protocol?.endsWith(PROTOCOL_SUFFIX) || !parsedUrl.hostname) {
57
+ throw new errors.InputError(
58
+ `SRV resolver expected a URL with protocol http(s)+srv:// but got '${url}'`
59
+ );
60
+ }
61
+ if (parsedUrl.port) {
62
+ throw new errors.InputError(
63
+ `SRV resolver URLs cannot contain a port but got '${url}'`
64
+ );
65
+ }
66
+ if (parsedUrl.username || parsedUrl.password) {
67
+ throw new errors.InputError(
68
+ `SRV resolver URLs cannot contain username or password but got '${url}'`
69
+ );
70
+ }
71
+ if (parsedUrl.search || parsedUrl.hash) {
72
+ throw new errors.InputError(
73
+ `SRV resolver URLs cannot contain search params or a hash but got '${url}'`
74
+ );
75
+ }
76
+ const protocol = parsedUrl.protocol.substring(
77
+ 0,
78
+ parsedUrl.protocol.length - PROTOCOL_SUFFIX.length
79
+ );
80
+ const host = parsedUrl.hostname;
81
+ const path = parsedUrl.pathname.replace(/\/+$/, "");
82
+ if (!["http", "https"].includes(protocol)) {
83
+ throw new errors.InputError(
84
+ `SRV URLs must be based on http or https but got '${url}'`
85
+ );
86
+ }
87
+ return { protocol, host, path };
88
+ }
89
+ /**
90
+ * Resolves a single SRV record name to a host:port string.
91
+ */
92
+ #resolveHost(host) {
93
+ let records = this.#cache.get(host);
94
+ if (!records) {
95
+ records = this.#resolveSrv(host).then(
96
+ (result) => {
97
+ if (!result.length) {
98
+ throw new errors.NotFoundError(`No SRV records found for ${host}`);
99
+ }
100
+ return result;
101
+ },
102
+ (err) => {
103
+ throw new errors.ForwardedError(`Failed SRV resolution for ${host}`, err);
104
+ }
105
+ );
106
+ this.#cache.set(host, records);
107
+ setTimeout(() => {
108
+ this.#cache.delete(host);
109
+ }, this.#cacheTtlMillis);
110
+ }
111
+ return records.then((rs) => {
112
+ const r = this.#pickRandomRecord(rs);
113
+ return `${r.name}:${r.port}`;
114
+ });
115
+ }
116
+ /**
117
+ * Among a set of records, pick one at random.
118
+ *
119
+ * This assumes that the set is not empty.
120
+ *
121
+ * Since this contract only ever returns a single record, the best it can do
122
+ * is to pick weighted-randomly among the highest-priority records. In order
123
+ * to be smarter than that, the caller would have to be able to make decisions
124
+ * on the whole set of records.
125
+ */
126
+ #pickRandomRecord(allRecords) {
127
+ const lowestPriority = allRecords.reduce(
128
+ (acc, r) => Math.min(acc, r.priority),
129
+ Number.MAX_SAFE_INTEGER
130
+ );
131
+ const records = allRecords.filter((r) => r.priority === lowestPriority);
132
+ const totalWeight = records.reduce((acc, r) => acc + r.weight, 0);
133
+ const targetWeight = Math.random() * totalWeight;
134
+ let result = records[0];
135
+ let currentWeight = 0;
136
+ for (const record of records) {
137
+ currentWeight += record.weight;
138
+ if (targetWeight <= currentWeight) {
139
+ result = record;
140
+ break;
141
+ }
142
+ }
143
+ return result;
144
+ }
145
+ }
146
+
147
+ exports.SrvResolvers = SrvResolvers;
148
+ //# sourceMappingURL=SrvResolvers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SrvResolvers.cjs.js","sources":["../../../src/entrypoints/discovery/SrvResolvers.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 { ForwardedError, InputError, NotFoundError } from '@backstage/errors';\nimport { resolveSrv, SrvRecord } from 'dns';\n\nconst PROTOCOL_SUFFIX = '+srv:';\n\n/**\n * Helps with resolution and caching of SRV lookups.\n *\n * Supports URLs on the form `http+srv://myplugin.services.region.example.net/api/myplugin`\n */\nexport class SrvResolvers {\n readonly #cache: Map<string, Promise<SrvRecord[]>>;\n readonly #cacheTtlMillis: number;\n readonly #resolveSrv: (host: string) => Promise<SrvRecord[]>;\n\n constructor(options?: {\n resolveSrv?: (host: string) => Promise<SrvRecord[]>;\n cacheTtlMillis?: number;\n }) {\n this.#cache = new Map();\n this.#cacheTtlMillis = options?.cacheTtlMillis ?? 1000;\n this.#resolveSrv =\n options?.resolveSrv ??\n (host =>\n new Promise((resolve, reject) => {\n resolveSrv(host, (err, result) => {\n if (err) {\n reject(err);\n } else {\n resolve(result);\n }\n });\n }));\n }\n\n isSrvUrl(url: string): boolean {\n try {\n this.#parseSrvUrl(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get a resolver function for a given SRV form URL.\n *\n * @param url An SRV form URL, e.g. `http+srv://myplugin.services.region.example.net/api/myplugin`\n * @returns A function that returns resolved URLs, e.g. `http://1234abcd.region.example.net:8080/api/myplugin`\n */\n getResolver(url: string): () => Promise<string> {\n const { protocol, host, path } = this.#parseSrvUrl(url);\n return () =>\n this.#resolveHost(host).then(\n resolved => `${protocol}://${resolved}${path}`,\n );\n }\n\n /**\n * Attempts to parse out the relevant parts of an SRV URL.\n */\n #parseSrvUrl(url: string): { protocol: string; host: string; path: string } {\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new InputError(\n `SRV resolver expected a valid URL starting with http(s)+srv:// but got '${url}'`,\n );\n }\n if (!parsedUrl.protocol?.endsWith(PROTOCOL_SUFFIX) || !parsedUrl.hostname) {\n throw new InputError(\n `SRV resolver expected a URL with protocol http(s)+srv:// but got '${url}'`,\n );\n }\n if (parsedUrl.port) {\n throw new InputError(\n `SRV resolver URLs cannot contain a port but got '${url}'`,\n );\n }\n if (parsedUrl.username || parsedUrl.password) {\n throw new InputError(\n `SRV resolver URLs cannot contain username or password but got '${url}'`,\n );\n }\n if (parsedUrl.search || parsedUrl.hash) {\n throw new InputError(\n `SRV resolver URLs cannot contain search params or a hash but got '${url}'`,\n );\n }\n\n const protocol = parsedUrl.protocol.substring(\n 0,\n parsedUrl.protocol.length - PROTOCOL_SUFFIX.length,\n );\n const host = parsedUrl.hostname;\n const path = parsedUrl.pathname.replace(/\\/+$/, '');\n\n if (!['http', 'https'].includes(protocol)) {\n throw new InputError(\n `SRV URLs must be based on http or https but got '${url}'`,\n );\n }\n\n return { protocol, host, path };\n }\n\n /**\n * Resolves a single SRV record name to a host:port string.\n */\n #resolveHost(host: string): Promise<string> {\n let records = this.#cache.get(host);\n if (!records) {\n records = this.#resolveSrv(host).then(\n result => {\n if (!result.length) {\n throw new NotFoundError(`No SRV records found for ${host}`);\n }\n return result;\n },\n err => {\n throw new ForwardedError(`Failed SRV resolution for ${host}`, err);\n },\n );\n this.#cache.set(host, records);\n setTimeout(() => {\n this.#cache.delete(host);\n }, this.#cacheTtlMillis);\n }\n\n return records.then(rs => {\n const r = this.#pickRandomRecord(rs);\n return `${r.name}:${r.port}`;\n });\n }\n\n /**\n * Among a set of records, pick one at random.\n *\n * This assumes that the set is not empty.\n *\n * Since this contract only ever returns a single record, the best it can do\n * is to pick weighted-randomly among the highest-priority records. In order\n * to be smarter than that, the caller would have to be able to make decisions\n * on the whole set of records.\n */\n #pickRandomRecord(allRecords: SrvRecord[]): SrvRecord {\n // Lowest priority number means highest priority\n const lowestPriority = allRecords.reduce(\n (acc, r) => Math.min(acc, r.priority),\n Number.MAX_SAFE_INTEGER,\n );\n const records = allRecords.filter(r => r.priority === lowestPriority);\n\n const totalWeight = records.reduce((acc, r) => acc + r.weight, 0);\n const targetWeight = Math.random() * totalWeight;\n\n // Just as a fallback, we expect the loop below to always find a result\n let result = records[0];\n let currentWeight = 0;\n\n for (const record of records) {\n currentWeight += record.weight;\n if (targetWeight <= currentWeight) {\n result = record;\n break;\n }\n }\n\n return result;\n }\n}\n"],"names":["resolveSrv","InputError","NotFoundError","ForwardedError"],"mappings":";;;;;AAmBA,MAAM,eAAkB,GAAA,OAAA;AAOjB,MAAM,YAAa,CAAA;AAAA,EACf,MAAA;AAAA,EACA,eAAA;AAAA,EACA,WAAA;AAAA,EAET,YAAY,OAGT,EAAA;AACD,IAAK,IAAA,CAAA,MAAA,uBAAa,GAAI,EAAA;AACtB,IAAK,IAAA,CAAA,eAAA,GAAkB,SAAS,cAAkB,IAAA,GAAA;AAClD,IAAK,IAAA,CAAA,WAAA,GACH,SAAS,UACR,KAAA,CAAA,IAAA,KACC,IAAI,OAAQ,CAAA,CAAC,SAAS,MAAW,KAAA;AAC/B,MAAWA,cAAA,CAAA,IAAA,EAAM,CAAC,GAAA,EAAK,MAAW,KAAA;AAChC,QAAA,IAAI,GAAK,EAAA;AACP,UAAA,MAAA,CAAO,GAAG,CAAA;AAAA,SACL,MAAA;AACL,UAAA,OAAA,CAAQ,MAAM,CAAA;AAAA;AAChB,OACD,CAAA;AAAA,KACF,CAAA,CAAA;AAAA;AACP,EAEA,SAAS,GAAsB,EAAA;AAC7B,IAAI,IAAA;AACF,MAAA,IAAA,CAAK,aAAa,GAAG,CAAA;AACrB,MAAO,OAAA,IAAA;AAAA,KACD,CAAA,MAAA;AACN,MAAO,OAAA,KAAA;AAAA;AACT;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,GAAoC,EAAA;AAC9C,IAAA,MAAM,EAAE,QAAU,EAAA,IAAA,EAAM,MAAS,GAAA,IAAA,CAAK,aAAa,GAAG,CAAA;AACtD,IAAA,OAAO,MACL,IAAA,CAAK,YAAa,CAAA,IAAI,CAAE,CAAA,IAAA;AAAA,MACtB,cAAY,CAAG,EAAA,QAAQ,CAAM,GAAA,EAAA,QAAQ,GAAG,IAAI,CAAA;AAAA,KAC9C;AAAA;AACJ;AAAA;AAAA;AAAA,EAKA,aAAa,GAA+D,EAAA;AAC1E,IAAI,IAAA,SAAA;AACJ,IAAI,IAAA;AACF,MAAY,SAAA,GAAA,IAAI,IAAI,GAAG,CAAA;AAAA,KACjB,CAAA,MAAA;AACN,MAAA,MAAM,IAAIC,iBAAA;AAAA,QACR,2EAA2E,GAAG,CAAA,CAAA;AAAA,OAChF;AAAA;AAEF,IAAI,IAAA,CAAC,UAAU,QAAU,EAAA,QAAA,CAAS,eAAe,CAAK,IAAA,CAAC,UAAU,QAAU,EAAA;AACzE,MAAA,MAAM,IAAIA,iBAAA;AAAA,QACR,qEAAqE,GAAG,CAAA,CAAA;AAAA,OAC1E;AAAA;AAEF,IAAA,IAAI,UAAU,IAAM,EAAA;AAClB,MAAA,MAAM,IAAIA,iBAAA;AAAA,QACR,oDAAoD,GAAG,CAAA,CAAA;AAAA,OACzD;AAAA;AAEF,IAAI,IAAA,SAAA,CAAU,QAAY,IAAA,SAAA,CAAU,QAAU,EAAA;AAC5C,MAAA,MAAM,IAAIA,iBAAA;AAAA,QACR,kEAAkE,GAAG,CAAA,CAAA;AAAA,OACvE;AAAA;AAEF,IAAI,IAAA,SAAA,CAAU,MAAU,IAAA,SAAA,CAAU,IAAM,EAAA;AACtC,MAAA,MAAM,IAAIA,iBAAA;AAAA,QACR,qEAAqE,GAAG,CAAA,CAAA;AAAA,OAC1E;AAAA;AAGF,IAAM,MAAA,QAAA,GAAW,UAAU,QAAS,CAAA,SAAA;AAAA,MAClC,CAAA;AAAA,MACA,SAAA,CAAU,QAAS,CAAA,MAAA,GAAS,eAAgB,CAAA;AAAA,KAC9C;AACA,IAAA,MAAM,OAAO,SAAU,CAAA,QAAA;AACvB,IAAA,MAAM,IAAO,GAAA,SAAA,CAAU,QAAS,CAAA,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAElD,IAAA,IAAI,CAAC,CAAC,MAAA,EAAQ,OAAO,CAAE,CAAA,QAAA,CAAS,QAAQ,CAAG,EAAA;AACzC,MAAA,MAAM,IAAIA,iBAAA;AAAA,QACR,oDAAoD,GAAG,CAAA,CAAA;AAAA,OACzD;AAAA;AAGF,IAAO,OAAA,EAAE,QAAU,EAAA,IAAA,EAAM,IAAK,EAAA;AAAA;AAChC;AAAA;AAAA;AAAA,EAKA,aAAa,IAA+B,EAAA;AAC1C,IAAA,IAAI,OAAU,GAAA,IAAA,CAAK,MAAO,CAAA,GAAA,CAAI,IAAI,CAAA;AAClC,IAAA,IAAI,CAAC,OAAS,EAAA;AACZ,MAAU,OAAA,GAAA,IAAA,CAAK,WAAY,CAAA,IAAI,CAAE,CAAA,IAAA;AAAA,QAC/B,CAAU,MAAA,KAAA;AACR,UAAI,IAAA,CAAC,OAAO,MAAQ,EAAA;AAClB,YAAA,MAAM,IAAIC,oBAAA,CAAc,CAA4B,yBAAA,EAAA,IAAI,CAAE,CAAA,CAAA;AAAA;AAE5D,UAAO,OAAA,MAAA;AAAA,SACT;AAAA,QACA,CAAO,GAAA,KAAA;AACL,UAAA,MAAM,IAAIC,qBAAA,CAAe,CAA6B,0BAAA,EAAA,IAAI,IAAI,GAAG,CAAA;AAAA;AACnE,OACF;AACA,MAAK,IAAA,CAAA,MAAA,CAAO,GAAI,CAAA,IAAA,EAAM,OAAO,CAAA;AAC7B,MAAA,UAAA,CAAW,MAAM;AACf,QAAK,IAAA,CAAA,MAAA,CAAO,OAAO,IAAI,CAAA;AAAA,OACzB,EAAG,KAAK,eAAe,CAAA;AAAA;AAGzB,IAAO,OAAA,OAAA,CAAQ,KAAK,CAAM,EAAA,KAAA;AACxB,MAAM,MAAA,CAAA,GAAI,IAAK,CAAA,iBAAA,CAAkB,EAAE,CAAA;AACnC,MAAA,OAAO,CAAG,EAAA,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,EAAE,IAAI,CAAA,CAAA;AAAA,KAC3B,CAAA;AAAA;AACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,kBAAkB,UAAoC,EAAA;AAEpD,IAAA,MAAM,iBAAiB,UAAW,CAAA,MAAA;AAAA,MAChC,CAAC,GAAK,EAAA,CAAA,KAAM,KAAK,GAAI,CAAA,GAAA,EAAK,EAAE,QAAQ,CAAA;AAAA,MACpC,MAAO,CAAA;AAAA,KACT;AACA,IAAA,MAAM,UAAU,UAAW,CAAA,MAAA,CAAO,CAAK,CAAA,KAAA,CAAA,CAAE,aAAa,cAAc,CAAA;AAEpE,IAAM,MAAA,WAAA,GAAc,QAAQ,MAAO,CAAA,CAAC,KAAK,CAAM,KAAA,GAAA,GAAM,CAAE,CAAA,MAAA,EAAQ,CAAC,CAAA;AAChE,IAAM,MAAA,YAAA,GAAe,IAAK,CAAA,MAAA,EAAW,GAAA,WAAA;AAGrC,IAAI,IAAA,MAAA,GAAS,QAAQ,CAAC,CAAA;AACtB,IAAA,IAAI,aAAgB,GAAA,CAAA;AAEpB,IAAA,KAAA,MAAW,UAAU,OAAS,EAAA;AAC5B,MAAA,aAAA,IAAiB,MAAO,CAAA,MAAA;AACxB,MAAA,IAAI,gBAAgB,aAAe,EAAA;AACjC,QAAS,MAAA,GAAA,MAAA;AACT,QAAA;AAAA;AACF;AAGF,IAAO,OAAA,MAAA;AAAA;AAEX;;;;"}
@@ -6,10 +6,14 @@ var HostDiscovery = require('./HostDiscovery.cjs.js');
6
6
  const discoveryServiceFactory = backendPluginApi.createServiceFactory({
7
7
  service: backendPluginApi.coreServices.discovery,
8
8
  deps: {
9
- config: backendPluginApi.coreServices.rootConfig
9
+ config: backendPluginApi.coreServices.rootConfig,
10
+ logger: backendPluginApi.coreServices.logger
10
11
  },
11
- async factory({ config }) {
12
- return HostDiscovery.HostDiscovery.fromConfig(config);
12
+ async factory({ config, logger }) {
13
+ return HostDiscovery.HostDiscovery.fromConfig(config, {
14
+ logger,
15
+ defaultEndpoints: []
16
+ });
13
17
  }
14
18
  });
15
19
 
@@ -1 +1 @@
1
- {"version":3,"file":"discoveryServiceFactory.cjs.js","sources":["../../../src/entrypoints/discovery/discoveryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { HostDiscovery } from './HostDiscovery';\n\n/**\n * Service discovery for inter-plugin communication.\n *\n * See {@link @backstage/code-plugin-api#DiscoveryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/discovery | the service docs}\n * for more information.\n *\n * @public\n */\nexport const discoveryServiceFactory = createServiceFactory({\n service: coreServices.discovery,\n deps: {\n config: coreServices.rootConfig,\n },\n async factory({ config }) {\n return HostDiscovery.fromConfig(config);\n },\n});\n"],"names":["createServiceFactory","coreServices","HostDiscovery"],"mappings":";;;;;AA+BO,MAAM,0BAA0BA,qCAAqB,CAAA;AAAA,EAC1D,SAASC,6BAAa,CAAA,SAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,MAAA,EAAU,EAAA;AACxB,IAAO,OAAAC,2BAAA,CAAc,WAAW,MAAM,CAAA;AAAA;AAE1C,CAAC;;;;"}
1
+ {"version":3,"file":"discoveryServiceFactory.cjs.js","sources":["../../../src/entrypoints/discovery/discoveryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { HostDiscovery } from './HostDiscovery';\n\n/**\n * Service discovery for inter-plugin communication.\n *\n * See {@link @backstage/code-plugin-api#DiscoveryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/discovery | the service docs}\n * for more information.\n *\n * @public\n */\nexport const discoveryServiceFactory = createServiceFactory({\n service: coreServices.discovery,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n },\n async factory({ config, logger }) {\n return HostDiscovery.fromConfig(config, {\n logger,\n defaultEndpoints: [],\n });\n },\n});\n"],"names":["createServiceFactory","coreServices","HostDiscovery"],"mappings":";;;;;AA+BO,MAAM,0BAA0BA,qCAAqB,CAAA;AAAA,EAC1D,SAASC,6BAAa,CAAA,SAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,MAAA,EAAQ,QAAU,EAAA;AAChC,IAAO,OAAAC,2BAAA,CAAc,WAAW,MAAQ,EAAA;AAAA,MACtC,MAAA;AAAA,MACA,kBAAkB;AAAC,KACpB,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -2,6 +2,12 @@
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
  var pluginPermissionNode = require('@backstage/plugin-permission-node');
5
+ var errors = require('@backstage/errors');
6
+ var Router = require('express-promise-router');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
+
10
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
5
11
 
6
12
  function assertRefPluginId(ref, pluginId) {
7
13
  if (ref.pluginId !== pluginId) {
@@ -13,13 +19,29 @@ function assertRefPluginId(ref, pluginId) {
13
19
  const permissionsRegistryServiceFactory = backendPluginApi.createServiceFactory({
14
20
  service: backendPluginApi.coreServices.permissionsRegistry,
15
21
  deps: {
22
+ auth: backendPluginApi.coreServices.auth,
23
+ httpAuth: backendPluginApi.coreServices.httpAuth,
16
24
  lifecycle: backendPluginApi.coreServices.lifecycle,
17
25
  httpRouter: backendPluginApi.coreServices.httpRouter,
18
26
  pluginMetadata: backendPluginApi.coreServices.pluginMetadata
19
27
  },
20
- async factory({ httpRouter, lifecycle, pluginMetadata }) {
28
+ async factory({ auth, httpAuth, httpRouter, lifecycle, pluginMetadata }) {
21
29
  const router = pluginPermissionNode.createPermissionIntegrationRouter();
22
30
  const pluginId = pluginMetadata.getId();
31
+ const applyConditionMiddleware = Router__default.default();
32
+ applyConditionMiddleware.use(
33
+ "/.well-known/backstage/permissions/apply-conditions",
34
+ async (req, _res, next) => {
35
+ const credentials = await httpAuth.credentials(req, {
36
+ allow: ["user", "service"]
37
+ });
38
+ if (auth.isPrincipal(credentials, "user") && !credentials.principal.actor) {
39
+ throw new errors.NotAllowedError();
40
+ }
41
+ next();
42
+ }
43
+ );
44
+ httpRouter.use(applyConditionMiddleware);
23
45
  httpRouter.use(router);
24
46
  let started = false;
25
47
  lifecycle.addStartupHook(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"permissionsRegistryServiceFactory.cjs.js","sources":["../../../src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 PermissionsRegistryService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n PermissionResourceRef,\n createPermissionIntegrationRouter,\n} from '@backstage/plugin-permission-node';\n\nfunction assertRefPluginId(ref: PermissionResourceRef, pluginId: string) {\n if (ref.pluginId !== pluginId) {\n throw new Error(\n `Resource type '${ref.resourceType}' belongs to plugin '${ref.pluginId}', but was used with plugin '${pluginId}'`,\n );\n }\n}\n\n/**\n * Permission system integration for registering resources and permissions.\n *\n * See {@link @backstage/core-plugin-api#PermissionsRegistryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/permission-integrations | the service docs}\n * for more information.\n *\n * @public\n */\nexport const permissionsRegistryServiceFactory = createServiceFactory({\n service: coreServices.permissionsRegistry,\n deps: {\n lifecycle: coreServices.lifecycle,\n httpRouter: coreServices.httpRouter,\n pluginMetadata: coreServices.pluginMetadata,\n },\n async factory({ httpRouter, lifecycle, pluginMetadata }) {\n const router = createPermissionIntegrationRouter();\n const pluginId = pluginMetadata.getId();\n\n httpRouter.use(router);\n\n let started = false;\n lifecycle.addStartupHook(() => {\n started = true;\n });\n\n return {\n addResourceType(resource) {\n if (started) {\n throw new Error(\n 'Cannot add permission resource types after the plugin has started',\n );\n }\n assertRefPluginId(resource.resourceRef, pluginId);\n router.addResourceType({\n ...resource,\n resourceType: resource.resourceRef.resourceType,\n });\n },\n addPermissions(permissions) {\n if (started) {\n throw new Error(\n 'Cannot add permissions after the plugin has started',\n );\n }\n router.addPermissions(permissions);\n },\n addPermissionRules(rules) {\n if (started) {\n throw new Error(\n 'Cannot add permission rules after the plugin has started',\n );\n }\n router.addPermissionRules(rules);\n },\n getPermissionRuleset(resourceRef) {\n assertRefPluginId(resourceRef, pluginId);\n return router.getPermissionRuleset(resourceRef);\n },\n } satisfies PermissionsRegistryService;\n },\n});\n"],"names":["createServiceFactory","coreServices","createPermissionIntegrationRouter"],"mappings":";;;;;AA0BA,SAAS,iBAAA,CAAkB,KAA4B,QAAkB,EAAA;AACvE,EAAI,IAAA,GAAA,CAAI,aAAa,QAAU,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,kBAAkB,GAAI,CAAA,YAAY,wBAAwB,GAAI,CAAA,QAAQ,gCAAgC,QAAQ,CAAA,CAAA;AAAA,KAChH;AAAA;AAEJ;AAWO,MAAM,oCAAoCA,qCAAqB,CAAA;AAAA,EACpE,SAASC,6BAAa,CAAA,mBAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,YAAYA,6BAAa,CAAA,UAAA;AAAA,IACzB,gBAAgBA,6BAAa,CAAA;AAAA,GAC/B;AAAA,EACA,MAAM,OAAQ,CAAA,EAAE,UAAY,EAAA,SAAA,EAAW,gBAAkB,EAAA;AACvD,IAAA,MAAM,SAASC,sDAAkC,EAAA;AACjD,IAAM,MAAA,QAAA,GAAW,eAAe,KAAM,EAAA;AAEtC,IAAA,UAAA,CAAW,IAAI,MAAM,CAAA;AAErB,IAAA,IAAI,OAAU,GAAA,KAAA;AACd,IAAA,SAAA,CAAU,eAAe,MAAM;AAC7B,MAAU,OAAA,GAAA,IAAA;AAAA,KACX,CAAA;AAED,IAAO,OAAA;AAAA,MACL,gBAAgB,QAAU,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAkB,iBAAA,CAAA,QAAA,CAAS,aAAa,QAAQ,CAAA;AAChD,QAAA,MAAA,CAAO,eAAgB,CAAA;AAAA,UACrB,GAAG,QAAA;AAAA,UACH,YAAA,EAAc,SAAS,WAAY,CAAA;AAAA,SACpC,CAAA;AAAA,OACH;AAAA,MACA,eAAe,WAAa,EAAA;AAC1B,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,eAAe,WAAW,CAAA;AAAA,OACnC;AAAA,MACA,mBAAmB,KAAO,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,mBAAmB,KAAK,CAAA;AAAA,OACjC;AAAA,MACA,qBAAqB,WAAa,EAAA;AAChC,QAAA,iBAAA,CAAkB,aAAa,QAAQ,CAAA;AACvC,QAAO,OAAA,MAAA,CAAO,qBAAqB,WAAW,CAAA;AAAA;AAChD,KACF;AAAA;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"permissionsRegistryServiceFactory.cjs.js","sources":["../../../src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 PermissionsRegistryService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n PermissionResourceRef,\n createPermissionIntegrationRouter,\n} from '@backstage/plugin-permission-node';\nimport { NotAllowedError } from '@backstage/errors';\nimport Router from 'express-promise-router';\n\nfunction assertRefPluginId(ref: PermissionResourceRef, pluginId: string) {\n if (ref.pluginId !== pluginId) {\n throw new Error(\n `Resource type '${ref.resourceType}' belongs to plugin '${ref.pluginId}', but was used with plugin '${pluginId}'`,\n );\n }\n}\n\n/**\n * Permission system integration for registering resources and permissions.\n *\n * See {@link @backstage/core-plugin-api#PermissionsRegistryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/permission-integrations | the service docs}\n * for more information.\n *\n * @public\n */\nexport const permissionsRegistryServiceFactory = createServiceFactory({\n service: coreServices.permissionsRegistry,\n deps: {\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n lifecycle: coreServices.lifecycle,\n httpRouter: coreServices.httpRouter,\n pluginMetadata: coreServices.pluginMetadata,\n },\n async factory({ auth, httpAuth, httpRouter, lifecycle, pluginMetadata }) {\n const router = createPermissionIntegrationRouter();\n\n const pluginId = pluginMetadata.getId();\n\n const applyConditionMiddleware = Router();\n applyConditionMiddleware.use(\n '/.well-known/backstage/permissions/apply-conditions',\n async (req, _res, next) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['user', 'service'],\n });\n if (\n auth.isPrincipal(credentials, 'user') &&\n !credentials.principal.actor\n ) {\n throw new NotAllowedError();\n }\n next();\n },\n );\n httpRouter.use(applyConditionMiddleware);\n httpRouter.use(router);\n\n let started = false;\n lifecycle.addStartupHook(() => {\n started = true;\n });\n\n return {\n addResourceType(resource) {\n if (started) {\n throw new Error(\n 'Cannot add permission resource types after the plugin has started',\n );\n }\n assertRefPluginId(resource.resourceRef, pluginId);\n router.addResourceType({\n ...resource,\n resourceType: resource.resourceRef.resourceType,\n });\n },\n addPermissions(permissions) {\n if (started) {\n throw new Error(\n 'Cannot add permissions after the plugin has started',\n );\n }\n router.addPermissions(permissions);\n },\n addPermissionRules(rules) {\n if (started) {\n throw new Error(\n 'Cannot add permission rules after the plugin has started',\n );\n }\n router.addPermissionRules(rules);\n },\n getPermissionRuleset(resourceRef) {\n assertRefPluginId(resourceRef, pluginId);\n return router.getPermissionRuleset(resourceRef);\n },\n } satisfies PermissionsRegistryService;\n },\n});\n"],"names":["createServiceFactory","coreServices","createPermissionIntegrationRouter","Router","NotAllowedError"],"mappings":";;;;;;;;;;;AA4BA,SAAS,iBAAA,CAAkB,KAA4B,QAAkB,EAAA;AACvE,EAAI,IAAA,GAAA,CAAI,aAAa,QAAU,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,kBAAkB,GAAI,CAAA,YAAY,wBAAwB,GAAI,CAAA,QAAQ,gCAAgC,QAAQ,CAAA,CAAA;AAAA,KAChH;AAAA;AAEJ;AAWO,MAAM,oCAAoCA,qCAAqB,CAAA;AAAA,EACpE,SAASC,6BAAa,CAAA,mBAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,IACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,YAAYA,6BAAa,CAAA,UAAA;AAAA,IACzB,gBAAgBA,6BAAa,CAAA;AAAA,GAC/B;AAAA,EACA,MAAM,QAAQ,EAAE,IAAA,EAAM,UAAU,UAAY,EAAA,SAAA,EAAW,gBAAkB,EAAA;AACvE,IAAA,MAAM,SAASC,sDAAkC,EAAA;AAEjD,IAAM,MAAA,QAAA,GAAW,eAAe,KAAM,EAAA;AAEtC,IAAA,MAAM,2BAA2BC,uBAAO,EAAA;AACxC,IAAyB,wBAAA,CAAA,GAAA;AAAA,MACvB,qDAAA;AAAA,MACA,OAAO,GAAK,EAAA,IAAA,EAAM,IAAS,KAAA;AACzB,QAAA,MAAM,WAAc,GAAA,MAAM,QAAS,CAAA,WAAA,CAAY,GAAK,EAAA;AAAA,UAClD,KAAA,EAAO,CAAC,MAAA,EAAQ,SAAS;AAAA,SAC1B,CAAA;AACD,QACE,IAAA,IAAA,CAAK,YAAY,WAAa,EAAA,MAAM,KACpC,CAAC,WAAA,CAAY,UAAU,KACvB,EAAA;AACA,UAAA,MAAM,IAAIC,sBAAgB,EAAA;AAAA;AAE5B,QAAK,IAAA,EAAA;AAAA;AACP,KACF;AACA,IAAA,UAAA,CAAW,IAAI,wBAAwB,CAAA;AACvC,IAAA,UAAA,CAAW,IAAI,MAAM,CAAA;AAErB,IAAA,IAAI,OAAU,GAAA,KAAA;AACd,IAAA,SAAA,CAAU,eAAe,MAAM;AAC7B,MAAU,OAAA,GAAA,IAAA;AAAA,KACX,CAAA;AAED,IAAO,OAAA;AAAA,MACL,gBAAgB,QAAU,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAkB,iBAAA,CAAA,QAAA,CAAS,aAAa,QAAQ,CAAA;AAChD,QAAA,MAAA,CAAO,eAAgB,CAAA;AAAA,UACrB,GAAG,QAAA;AAAA,UACH,YAAA,EAAc,SAAS,WAAY,CAAA;AAAA,SACpC,CAAA;AAAA,OACH;AAAA,MACA,eAAe,WAAa,EAAA;AAC1B,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,eAAe,WAAW,CAAA;AAAA,OACnC;AAAA,MACA,mBAAmB,KAAO,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,mBAAmB,KAAK,CAAA;AAAA,OACjC;AAAA,MACA,qBAAqB,WAAa,EAAA;AAChC,QAAA,iBAAA,CAAkB,aAAa,QAAQ,CAAA;AACvC,QAAO,OAAA,MAAA,CAAO,qBAAqB,WAAW,CAAA;AAAA;AAChD,KACF;AAAA;AAEJ,CAAC;;;;"}
@@ -32,6 +32,7 @@ const rootHttpRouterServiceFactoryWithOptions = (options) => backendPluginApi.cr
32
32
  const { indexPath, configure = defaultConfigure } = options ?? {};
33
33
  const logger = rootLogger.child({ service: "rootHttpRouter" });
34
34
  const app = express__default.default();
35
+ const trustProxy = config$2.getOptional("backend.trustProxy");
35
36
  const router = DefaultRootHttpRouter.DefaultRootHttpRouter.create({ indexPath });
36
37
  const middleware = MiddlewareFactory.MiddlewareFactory.create({ config: config$2, logger });
37
38
  const routes = router.handler();
@@ -54,6 +55,9 @@ const rootHttpRouterServiceFactoryWithOptions = (options) => backendPluginApi.cr
54
55
  if (process.env.NODE_ENV === "development") {
55
56
  app.set("json spaces", 2);
56
57
  }
58
+ if (trustProxy !== void 0) {
59
+ app.set("trust proxy", trustProxy);
60
+ }
57
61
  app.use(middleware.helmet());
58
62
  app.use(middleware.cors());
59
63
  app.use(middleware.compression());
@@ -1 +1 @@
1
- {"version":3,"file":"rootHttpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 RootConfigService,\n coreServices,\n createServiceFactory,\n LifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport express, { RequestHandler, Express } from 'express';\nimport type { Server } from 'node:http';\nimport {\n createHttpServer,\n MiddlewareFactory,\n readHttpServerOptions,\n} from './http';\nimport { DefaultRootHttpRouter } from './DefaultRootHttpRouter';\nimport { createHealthRouter } from './createHealthRouter';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { readDurationFromConfig } from '@backstage/config';\n\n/**\n * @public\n */\nexport interface RootHttpRouterConfigureContext {\n app: Express;\n server: Server;\n middleware: MiddlewareFactory;\n routes: RequestHandler;\n config: RootConfigService;\n logger: LoggerService;\n lifecycle: LifecycleService;\n healthRouter: RequestHandler;\n applyDefaults: () => void;\n}\n\n/**\n * HTTP route registration for root services.\n *\n * See {@link @backstage/code-plugin-api#RootHttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport type RootHttpRouterFactoryOptions = {\n /**\n * The path to forward all unmatched requests to. Defaults to '/api/app' if\n * not given. Disables index path behavior if false is given.\n */\n indexPath?: string | false;\n\n configure?(context: RootHttpRouterConfigureContext): void;\n};\n\nfunction defaultConfigure({ applyDefaults }: RootHttpRouterConfigureContext) {\n applyDefaults();\n}\n\nconst rootHttpRouterServiceFactoryWithOptions = (\n options?: RootHttpRouterFactoryOptions,\n) =>\n createServiceFactory({\n service: coreServices.rootHttpRouter,\n deps: {\n config: coreServices.rootConfig,\n rootLogger: coreServices.rootLogger,\n lifecycle: coreServices.rootLifecycle,\n health: coreServices.rootHealth,\n },\n async factory({ config, rootLogger, lifecycle, health }) {\n const { indexPath, configure = defaultConfigure } = options ?? {};\n const logger = rootLogger.child({ service: 'rootHttpRouter' });\n const app = express();\n\n const router = DefaultRootHttpRouter.create({ indexPath });\n const middleware = MiddlewareFactory.create({ config, logger });\n const routes = router.handler();\n\n const healthRouter = createHealthRouter({ config, health });\n\n const server = await createHttpServer(\n app,\n readHttpServerOptions(config.getOptionalConfig('backend')),\n { logger },\n );\n\n configure({\n app,\n server,\n routes,\n middleware,\n config,\n logger,\n lifecycle,\n healthRouter,\n applyDefaults() {\n if (process.env.NODE_ENV === 'development') {\n app.set('json spaces', 2);\n }\n app.use(middleware.helmet());\n app.use(middleware.cors());\n app.use(middleware.compression());\n app.use(middleware.logging());\n app.use(healthRouter);\n app.use(routes);\n app.use(middleware.notFound());\n app.use(middleware.error());\n },\n });\n\n if (config.has('backend.lifecycle.serverShutdownDelay')) {\n const serverShutdownDelay = readDurationFromConfig(config, {\n key: 'backend.lifecycle.serverShutdownDelay',\n });\n lifecycle.addBeforeShutdownHook(async () => {\n const timeoutMs = durationToMilliseconds(serverShutdownDelay);\n return await new Promise(resolve => {\n setTimeout(resolve, timeoutMs);\n });\n });\n }\n\n lifecycle.addShutdownHook(() => server.stop());\n\n await server.start();\n\n return router;\n },\n });\n\n/** @public */\nexport const rootHttpRouterServiceFactory = Object.assign(\n rootHttpRouterServiceFactoryWithOptions,\n rootHttpRouterServiceFactoryWithOptions(),\n);\n"],"names":["createServiceFactory","coreServices","config","express","DefaultRootHttpRouter","MiddlewareFactory","createHealthRouter","createHttpServer","readHttpServerOptions","readDurationFromConfig","durationToMilliseconds"],"mappings":"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"}
1
+ {"version":3,"file":"rootHttpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 RootConfigService,\n coreServices,\n createServiceFactory,\n LifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport express, { RequestHandler, Express } from 'express';\nimport type { Server } from 'node:http';\nimport {\n createHttpServer,\n MiddlewareFactory,\n readHttpServerOptions,\n} from './http';\nimport { DefaultRootHttpRouter } from './DefaultRootHttpRouter';\nimport { createHealthRouter } from './createHealthRouter';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { readDurationFromConfig } from '@backstage/config';\n\n/**\n * @public\n */\nexport interface RootHttpRouterConfigureContext {\n app: Express;\n server: Server;\n middleware: MiddlewareFactory;\n routes: RequestHandler;\n config: RootConfigService;\n logger: LoggerService;\n lifecycle: LifecycleService;\n healthRouter: RequestHandler;\n applyDefaults: () => void;\n}\n\n/**\n * HTTP route registration for root services.\n *\n * See {@link @backstage/code-plugin-api#RootHttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport type RootHttpRouterFactoryOptions = {\n /**\n * The path to forward all unmatched requests to. Defaults to '/api/app' if\n * not given. Disables index path behavior if false is given.\n */\n indexPath?: string | false;\n\n configure?(context: RootHttpRouterConfigureContext): void;\n};\n\nfunction defaultConfigure({ applyDefaults }: RootHttpRouterConfigureContext) {\n applyDefaults();\n}\n\nconst rootHttpRouterServiceFactoryWithOptions = (\n options?: RootHttpRouterFactoryOptions,\n) =>\n createServiceFactory({\n service: coreServices.rootHttpRouter,\n deps: {\n config: coreServices.rootConfig,\n rootLogger: coreServices.rootLogger,\n lifecycle: coreServices.rootLifecycle,\n health: coreServices.rootHealth,\n },\n async factory({ config, rootLogger, lifecycle, health }) {\n const { indexPath, configure = defaultConfigure } = options ?? {};\n const logger = rootLogger.child({ service: 'rootHttpRouter' });\n const app = express();\n\n const trustProxy = config.getOptional('backend.trustProxy');\n\n const router = DefaultRootHttpRouter.create({ indexPath });\n const middleware = MiddlewareFactory.create({ config, logger });\n const routes = router.handler();\n\n const healthRouter = createHealthRouter({ config, health });\n\n const server = await createHttpServer(\n app,\n readHttpServerOptions(config.getOptionalConfig('backend')),\n { logger },\n );\n\n configure({\n app,\n server,\n routes,\n middleware,\n config,\n logger,\n lifecycle,\n healthRouter,\n applyDefaults() {\n if (process.env.NODE_ENV === 'development') {\n app.set('json spaces', 2);\n }\n if (trustProxy !== undefined) {\n app.set('trust proxy', trustProxy);\n }\n app.use(middleware.helmet());\n app.use(middleware.cors());\n app.use(middleware.compression());\n app.use(middleware.logging());\n app.use(healthRouter);\n app.use(routes);\n app.use(middleware.notFound());\n app.use(middleware.error());\n },\n });\n\n if (config.has('backend.lifecycle.serverShutdownDelay')) {\n const serverShutdownDelay = readDurationFromConfig(config, {\n key: 'backend.lifecycle.serverShutdownDelay',\n });\n lifecycle.addBeforeShutdownHook(async () => {\n const timeoutMs = durationToMilliseconds(serverShutdownDelay);\n return await new Promise(resolve => {\n setTimeout(resolve, timeoutMs);\n });\n });\n }\n\n lifecycle.addShutdownHook(() => server.stop());\n\n await server.start();\n\n return router;\n },\n });\n\n/** @public */\nexport const rootHttpRouterServiceFactory = Object.assign(\n rootHttpRouterServiceFactoryWithOptions,\n rootHttpRouterServiceFactoryWithOptions(),\n);\n"],"names":["createServiceFactory","coreServices","config","express","DefaultRootHttpRouter","MiddlewareFactory","createHealthRouter","createHttpServer","readHttpServerOptions","readDurationFromConfig","durationToMilliseconds"],"mappings":"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"}
@@ -6,22 +6,11 @@ var parseGitUrl = require('git-url-parse');
6
6
  var lodash = require('lodash');
7
7
  var minimatch = require('minimatch');
8
8
  var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js');
9
- var pThrottle = require('p-throttle');
10
9
 
11
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
12
11
 
13
12
  var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl);
14
- var pThrottle__default = /*#__PURE__*/_interopDefaultCompat(pThrottle);
15
13
 
16
- const throttle = pThrottle__default.default({
17
- limit: 1,
18
- interval: 1e3
19
- });
20
- const throttledFetch = throttle(
21
- async (url, options) => {
22
- return await fetch(url, options);
23
- }
24
- );
25
14
  class BitbucketServerUrlReader {
26
15
  constructor(integration, deps) {
27
16
  this.integration = integration;
@@ -52,7 +41,7 @@ class BitbucketServerUrlReader {
52
41
  );
53
42
  let response;
54
43
  try {
55
- response = await throttledFetch(bitbucketUrl.toString(), {
44
+ response = await fetch(bitbucketUrl.toString(), {
56
45
  headers: {
57
46
  ...requestOptions.headers,
58
47
  ...etag && { "If-None-Match": etag },
@@ -93,7 +82,7 @@ class BitbucketServerUrlReader {
93
82
  url,
94
83
  this.integration.config
95
84
  );
96
- const archiveResponse = await throttledFetch(
85
+ const archiveResponse = await fetch(
97
86
  downloadUrl,
98
87
  integration.getBitbucketServerRequestOptions(this.integration.config)
99
88
  );
@@ -165,7 +154,7 @@ class BitbucketServerUrlReader {
165
154
  const { name: repoName, owner: project, ref: branch } = parseGitUrl__default.default(url);
166
155
  const branchParameter = branch ? `?filterText=${encodeURIComponent(branch)}` : "/default";
167
156
  const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`;
168
- const branchListResponse = await throttledFetch(
157
+ const branchListResponse = await fetch(
169
158
  branchListUrl,
170
159
  integration.getBitbucketServerRequestOptions(this.integration.config)
171
160
  );
@@ -1 +1 @@
1
- {"version":3,"file":"BitbucketServerUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/BitbucketServerUrlReader.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 {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n assertError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport {\n BitbucketServerIntegration,\n getBitbucketServerDownloadUrl,\n getBitbucketServerFileFetchUrl,\n getBitbucketServerRequestOptions,\n ScmIntegrations,\n} from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd } from 'lodash';\nimport { Minimatch } from 'minimatch';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\n\nimport pThrottle from 'p-throttle';\n\n// 1 per second\nconst throttle = pThrottle({\n limit: 1,\n interval: 1000,\n});\n\nconst throttledFetch = throttle(\n async (url: RequestInfo, options?: RequestInit) => {\n return await fetch(url, options);\n },\n);\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket Server APIs.\n *\n * @public\n */\nexport class BitbucketServerUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n return integrations.bitbucketServer.list().map(integration => {\n const reader = new BitbucketServerUrlReader(integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => url.host === integration.config.host;\n return { reader, predicate };\n });\n };\n\n constructor(\n private readonly integration: BitbucketServerIntegration,\n private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },\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, signal } = options ?? {};\n const bitbucketUrl = getBitbucketServerFileFetchUrl(\n url,\n this.integration.config,\n );\n const requestOptions = getBitbucketServerRequestOptions(\n this.integration.config,\n );\n\n let response: Response;\n try {\n response = await throttledFetch(bitbucketUrl.toString(), {\n headers: {\n ...requestOptions.headers,\n ...(etag && { 'If-None-Match': etag }),\n ...(lastModifiedAfter && {\n 'If-Modified-Since': lastModifiedAfter.toUTCString(),\n }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can be\n // removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const { filepath } = parseGitUrl(url);\n\n const lastCommitShortHash = await this.getLastCommitShortHash(url);\n if (options?.etag && options.etag === lastCommitShortHash) {\n throw new NotModifiedError();\n }\n\n const downloadUrl = await getBitbucketServerDownloadUrl(\n url,\n this.integration.config,\n );\n const archiveResponse = await throttledFetch(\n downloadUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!archiveResponse.ok) {\n const message = `Failed to read tree from ${url}, ${archiveResponse.status} ${archiveResponse.statusText}`;\n if (archiveResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return await this.deps.treeResponseFactory.fromTarArchive({\n response: archiveResponse,\n subpath: filepath,\n etag: lastCommitShortHash,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { filepath } = parseGitUrl(url);\n\n // If it's a direct URL we use readUrl instead\n if (!filepath?.match(/[*?]/)) {\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 const matcher = new Minimatch(filepath);\n\n // TODO(freben): For now, read the entire repo and filter through that. In\n // a future improvement, we could be smart and try to deduce that non-glob\n // prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used\n // to get just that part of the repo.\n const treeUrl = trimEnd(url.replace(filepath, ''), '/');\n\n const tree = await this.readTree(treeUrl, {\n etag: options?.etag,\n filter: path => matcher.match(path),\n });\n const files = await tree.files();\n\n return {\n etag: tree.etag,\n files: files.map(file => ({\n url: this.integration.resolveUrl({\n url: `/${file.path}`,\n base: url,\n }),\n content: file.content,\n lastModifiedAt: file.lastModifiedAt,\n })),\n };\n }\n\n toString() {\n const { host, token } = this.integration.config;\n const authed = Boolean(token);\n return `bitbucketServer{host=${host},authed=${authed}}`;\n }\n\n private async getLastCommitShortHash(url: string): Promise<string> {\n const { name: repoName, owner: project, ref: branch } = parseGitUrl(url);\n\n // If a branch is provided use that otherwise fall back to the default branch\n const branchParameter = branch\n ? `?filterText=${encodeURIComponent(branch)}`\n : '/default';\n\n // https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp211 (branches docs)\n const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`;\n\n const branchListResponse = await throttledFetch(\n branchListUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!branchListResponse.ok) {\n const message = `Failed to retrieve branch list from ${branchListUrl}, ${branchListResponse.status} ${branchListResponse.statusText}`;\n if (branchListResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n const branchMatches = await branchListResponse.json();\n\n if (branchMatches && branchMatches.size > 0) {\n const exactBranchMatch = branchMatches.values.filter(\n (branchDetails: { displayId: string }) =>\n branchDetails.displayId === branch,\n )[0];\n return exactBranchMatch.latestCommit.substring(0, 12);\n }\n\n // Handle when no branch is provided using the default as the fallback\n if (!branch && branchMatches) {\n return branchMatches.latestCommit.substring(0, 12);\n }\n\n throw new Error(\n `Failed to find Last Commit using ${\n branch ? `branch \"${branch}\"` : 'default branch'\n } in response from ${branchListUrl}`,\n );\n }\n}\n"],"names":["pThrottle","ScmIntegrations","getBitbucketServerFileFetchUrl","getBitbucketServerRequestOptions","NotModifiedError","ReadUrlResponseFactory","NotFoundError","parseGitUrl","getBitbucketServerDownloadUrl","assertError","Minimatch","trimEnd"],"mappings":"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"}
1
+ {"version":3,"file":"BitbucketServerUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/BitbucketServerUrlReader.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 {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n assertError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport {\n BitbucketServerIntegration,\n getBitbucketServerDownloadUrl,\n getBitbucketServerFileFetchUrl,\n getBitbucketServerRequestOptions,\n ScmIntegrations,\n} from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd } from 'lodash';\nimport { Minimatch } from 'minimatch';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket Server APIs.\n *\n * @public\n */\nexport class BitbucketServerUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n return integrations.bitbucketServer.list().map(integration => {\n const reader = new BitbucketServerUrlReader(integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => url.host === integration.config.host;\n return { reader, predicate };\n });\n };\n\n constructor(\n private readonly integration: BitbucketServerIntegration,\n private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },\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, signal } = options ?? {};\n const bitbucketUrl = getBitbucketServerFileFetchUrl(\n url,\n this.integration.config,\n );\n const requestOptions = getBitbucketServerRequestOptions(\n this.integration.config,\n );\n\n let response: Response;\n try {\n response = await fetch(bitbucketUrl.toString(), {\n headers: {\n ...requestOptions.headers,\n ...(etag && { 'If-None-Match': etag }),\n ...(lastModifiedAfter && {\n 'If-Modified-Since': lastModifiedAfter.toUTCString(),\n }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can be\n // removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const { filepath } = parseGitUrl(url);\n\n const lastCommitShortHash = await this.getLastCommitShortHash(url);\n if (options?.etag && options.etag === lastCommitShortHash) {\n throw new NotModifiedError();\n }\n\n const downloadUrl = await getBitbucketServerDownloadUrl(\n url,\n this.integration.config,\n );\n const archiveResponse = await fetch(\n downloadUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!archiveResponse.ok) {\n const message = `Failed to read tree from ${url}, ${archiveResponse.status} ${archiveResponse.statusText}`;\n if (archiveResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return await this.deps.treeResponseFactory.fromTarArchive({\n response: archiveResponse,\n subpath: filepath,\n etag: lastCommitShortHash,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { filepath } = parseGitUrl(url);\n\n // If it's a direct URL we use readUrl instead\n if (!filepath?.match(/[*?]/)) {\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 const matcher = new Minimatch(filepath);\n\n // TODO(freben): For now, read the entire repo and filter through that. In\n // a future improvement, we could be smart and try to deduce that non-glob\n // prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used\n // to get just that part of the repo.\n const treeUrl = trimEnd(url.replace(filepath, ''), '/');\n\n const tree = await this.readTree(treeUrl, {\n etag: options?.etag,\n filter: path => matcher.match(path),\n });\n const files = await tree.files();\n\n return {\n etag: tree.etag,\n files: files.map(file => ({\n url: this.integration.resolveUrl({\n url: `/${file.path}`,\n base: url,\n }),\n content: file.content,\n lastModifiedAt: file.lastModifiedAt,\n })),\n };\n }\n\n toString() {\n const { host, token } = this.integration.config;\n const authed = Boolean(token);\n return `bitbucketServer{host=${host},authed=${authed}}`;\n }\n\n private async getLastCommitShortHash(url: string): Promise<string> {\n const { name: repoName, owner: project, ref: branch } = parseGitUrl(url);\n\n // If a branch is provided use that otherwise fall back to the default branch\n const branchParameter = branch\n ? `?filterText=${encodeURIComponent(branch)}`\n : '/default';\n\n // https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp211 (branches docs)\n const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`;\n\n const branchListResponse = await fetch(\n branchListUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!branchListResponse.ok) {\n const message = `Failed to retrieve branch list from ${branchListUrl}, ${branchListResponse.status} ${branchListResponse.statusText}`;\n if (branchListResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n const branchMatches = await branchListResponse.json();\n\n if (branchMatches && branchMatches.size > 0) {\n const exactBranchMatch = branchMatches.values.filter(\n (branchDetails: { displayId: string }) =>\n branchDetails.displayId === branch,\n )[0];\n return exactBranchMatch.latestCommit.substring(0, 12);\n }\n\n // Handle when no branch is provided using the default as the fallback\n if (!branch && branchMatches) {\n return branchMatches.latestCommit.substring(0, 12);\n }\n\n throw new Error(\n `Failed to find Last Commit using ${\n branch ? `branch \"${branch}\"` : 'default branch'\n } in response from ${branchListUrl}`,\n );\n }\n}\n"],"names":["ScmIntegrations","getBitbucketServerFileFetchUrl","getBitbucketServerRequestOptions","NotModifiedError","ReadUrlResponseFactory","NotFoundError","parseGitUrl","getBitbucketServerDownloadUrl","assertError","Minimatch","trimEnd"],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var name = "@backstage/backend-defaults";
6
- var version = "0.9.0-next.2";
6
+ var version = "0.9.0";
7
7
  var description = "Backend defaults used by Backstage backend apps";
8
8
  var backstage = {
9
9
  role: "node-library"
@@ -176,7 +176,6 @@ var dependencies = {
176
176
  "node-fetch": "^2.7.0",
177
177
  "node-forge": "^1.3.1",
178
178
  "p-limit": "^3.1.0",
179
- "p-throttle": "^4.1.1",
180
179
  "path-to-regexp": "^8.0.0",
181
180
  pg: "^8.11.3",
182
181
  "pg-connection-string": "^2.3.0",
@@ -1 +1 @@
1
- {"version":3,"file":"package.json.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"package.json.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-defaults",
3
- "version": "0.9.0-next.2",
3
+ "version": "0.9.0",
4
4
  "description": "Backend defaults used by Backstage backend apps",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -208,19 +208,19 @@
208
208
  "@aws-sdk/credential-providers": "^3.350.0",
209
209
  "@aws-sdk/types": "^3.347.0",
210
210
  "@azure/storage-blob": "^12.5.0",
211
- "@backstage/backend-app-api": "1.2.1",
212
- "@backstage/backend-dev-utils": "0.1.5",
213
- "@backstage/backend-plugin-api": "1.2.1",
214
- "@backstage/cli-node": "0.2.13",
215
- "@backstage/config": "1.3.2",
216
- "@backstage/config-loader": "1.10.0",
217
- "@backstage/errors": "1.2.7",
218
- "@backstage/integration": "1.16.3-next.0",
219
- "@backstage/integration-aws-node": "0.1.15",
220
- "@backstage/plugin-auth-node": "0.6.1",
221
- "@backstage/plugin-events-node": "0.4.9",
222
- "@backstage/plugin-permission-node": "0.9.0",
223
- "@backstage/types": "1.2.1",
211
+ "@backstage/backend-app-api": "^1.2.2",
212
+ "@backstage/backend-dev-utils": "^0.1.5",
213
+ "@backstage/backend-plugin-api": "^1.3.0",
214
+ "@backstage/cli-node": "^0.2.13",
215
+ "@backstage/config": "^1.3.2",
216
+ "@backstage/config-loader": "^1.10.0",
217
+ "@backstage/errors": "^1.2.7",
218
+ "@backstage/integration": "^1.16.3",
219
+ "@backstage/integration-aws-node": "^0.1.15",
220
+ "@backstage/plugin-auth-node": "^0.6.2",
221
+ "@backstage/plugin-events-node": "^0.4.10",
222
+ "@backstage/plugin-permission-node": "^0.9.1",
223
+ "@backstage/types": "^1.2.1",
224
224
  "@google-cloud/storage": "^7.0.0",
225
225
  "@keyv/memcache": "^2.0.1",
226
226
  "@keyv/redis": "^4.0.1",
@@ -253,7 +253,6 @@
253
253
  "node-fetch": "^2.7.0",
254
254
  "node-forge": "^1.3.1",
255
255
  "p-limit": "^3.1.0",
256
- "p-throttle": "^4.1.1",
257
256
  "path-to-regexp": "^8.0.0",
258
257
  "pg": "^8.11.3",
259
258
  "pg-connection-string": "^2.3.0",
@@ -271,9 +270,9 @@
271
270
  },
272
271
  "devDependencies": {
273
272
  "@aws-sdk/util-stream-node": "^3.350.0",
274
- "@backstage/backend-plugin-api": "1.2.1",
275
- "@backstage/backend-test-utils": "1.3.2-next.2",
276
- "@backstage/cli": "0.32.0-next.2",
273
+ "@backstage/backend-plugin-api": "^1.3.0",
274
+ "@backstage/backend-test-utils": "^1.4.0",
275
+ "@backstage/cli": "^0.32.0",
277
276
  "@google-cloud/cloud-sql-connector": "^1.4.0",
278
277
  "@types/archiver": "^6.0.0",
279
278
  "@types/base64-stream": "^1.0.2",