@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 +41 -0
- package/config.d.ts +62 -5
- package/dist/discovery.d.ts +105 -36
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js +2 -1
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
- package/dist/entrypoints/auth/helpers.cjs.js +5 -2
- package/dist/entrypoints/auth/helpers.cjs.js.map +1 -1
- package/dist/entrypoints/discovery/HostDiscovery.cjs.js +128 -53
- package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -1
- package/dist/entrypoints/discovery/SrvResolvers.cjs.js +148 -0
- package/dist/entrypoints/discovery/SrvResolvers.cjs.js.map +1 -0
- package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js +7 -3
- package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js +23 -1
- package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +4 -0
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +3 -14
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +1 -2
- package/dist/package.json.cjs.js.map +1 -1
- package/package.json +17 -18
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
|
|
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
|
|
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
|
-
*
|
|
740
|
-
*
|
|
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
|
|
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
|
}>;
|
package/dist/discovery.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
70
|
+
interface HostDiscoveryOptions {
|
|
71
|
+
/**
|
|
72
|
+
* The logger to use.
|
|
73
|
+
*/
|
|
74
|
+
logger: LoggerService;
|
|
29
75
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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":"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"}
|
|
@@ -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,
|
|
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":"
|
|
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":";;;;;;;;;;;;;;;;;;;AAqEA,SAAS,gBAAA,CAAiB,EAAE,aAAA,EAAiD,EAAA;AAC3E,EAAc,aAAA,EAAA;AAChB;AAEA,MAAM,uCAAA,GAA0C,CAC9C,OAAA,KAEAA,qCAAqB,CAAA;AAAA,EACnB,SAASC,6BAAa,CAAA,cAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,YAAYA,6BAAa,CAAA,UAAA;AAAA,IACzB,WAAWA,6BAAa,CAAA,aAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAQ,CAAA,UAAEC,UAAQ,UAAY,EAAA,SAAA,EAAW,QAAU,EAAA;AACvD,IAAA,MAAM,EAAE,SAAW,EAAA,SAAA,GAAY,gBAAiB,EAAA,GAAI,WAAW,EAAC;AAChE,IAAA,MAAM,SAAS,UAAW,CAAA,KAAA,CAAM,EAAE,OAAA,EAAS,kBAAkB,CAAA;AAC7D,IAAA,MAAM,MAAMC,wBAAQ,EAAA;AAEpB,IAAA,MAAM,MAAS,
|
|
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
|
|
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
|
|
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
|
|
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":";;;;;;;;;;;;;AAgDO,MAAM,wBAAqD,CAAA;AAAA,EAYhE,WAAA,CACmB,aACA,IACjB,EAAA;AAFiB,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA;AAChB,EAdH,OAAO,OAAyB,GAAA,CAAC,EAAE,MAAA,EAAQ,qBAA0B,KAAA;AACnE,IAAM,MAAA,YAAA,GAAeA,2BAAgB,CAAA,UAAA,CAAW,MAAM,CAAA;AACtD,IAAA,OAAO,YAAa,CAAA,eAAA,CAAgB,IAAK,EAAA,CAAE,IAAI,CAAe,WAAA,KAAA;AAC5D,MAAM,MAAA,MAAA,GAAS,IAAI,wBAAA,CAAyB,WAAa,EAAA;AAAA,QACvD;AAAA,OACD,CAAA;AACD,MAAA,MAAM,YAAY,CAAC,GAAA,KAAa,GAAI,CAAA,IAAA,KAAS,YAAY,MAAO,CAAA,IAAA;AAChE,MAAO,OAAA,EAAE,QAAQ,SAAU,EAAA;AAAA,KAC5B,CAAA;AAAA,GACH;AAAA,EAOA,MAAM,KAAK,GAA8B,EAAA;AACvC,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,GAAG,CAAA;AACvC,IAAA,OAAO,SAAS,MAAO,EAAA;AAAA;AACzB,EAEA,MAAM,OACJ,CAAA,GAAA,EACA,OAC0C,EAAA;AAC1C,IAAA,MAAM,EAAE,IAAM,EAAA,iBAAA,EAAmB,MAAO,EAAA,GAAI,WAAW,EAAC;AACxD,IAAA,MAAM,YAAe,GAAAC,0CAAA;AAAA,MACnB,GAAA;AAAA,MACA,KAAK,WAAY,CAAA;AAAA,KACnB;AACA,IAAA,MAAM,cAAiB,GAAAC,4CAAA;AAAA,MACrB,KAAK,WAAY,CAAA;AAAA,KACnB;AAEA,IAAI,IAAA,QAAA;AACJ,IAAI,IAAA;AACF,MAAA,QAAA,GAAW,MAAM,KAAA,CAAM,YAAa,CAAA,QAAA,EAAY,EAAA;AAAA,QAC9C,OAAS,EAAA;AAAA,UACP,GAAG,cAAe,CAAA,OAAA;AAAA,UAClB,GAAI,IAAA,IAAQ,EAAE,eAAA,EAAiB,IAAK,EAAA;AAAA,UACpC,GAAI,iBAAqB,IAAA;AAAA,YACvB,mBAAA,EAAqB,kBAAkB,WAAY;AAAA;AACrD,SACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOA,GAAI,MAAU,IAAA,EAAE,MAAsB;AAAA,OACvC,CAAA;AAAA,aACM,CAAG,EAAA;AACV,MAAA,MAAM,IAAI,KAAM,CAAA,CAAA,eAAA,EAAkB,GAAG,CAAA,EAAA,EAAK,CAAC,CAAE,CAAA,CAAA;AAAA;AAG/C,IAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AAC3B,MAAA,MAAM,IAAIC,uBAAiB,EAAA;AAAA;AAG7B,IAAA,IAAI,SAAS,EAAI,EAAA;AACf,MAAO,OAAAC,6CAAA,CAAuB,aAAa,QAAQ,CAAA;AAAA;AAGrD,IAAM,MAAA,OAAA,GAAU,CAAG,EAAA,GAAG,CAAyB,sBAAA,EAAA,YAAY,KAAK,QAAS,CAAA,MAAM,CAAI,CAAA,EAAA,QAAA,CAAS,UAAU,CAAA,CAAA;AACtG,IAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AAC3B,MAAM,MAAA,IAAIC,qBAAc,OAAO,CAAA;AAAA;AAEjC,IAAM,MAAA,IAAI,MAAM,OAAO,CAAA;AAAA;AACzB,EAEA,MAAM,QACJ,CAAA,GAAA,EACA,OAC2C,EAAA;AAC3C,IAAA,MAAM,EAAE,QAAA,EAAa,GAAAC,4BAAA,CAAY,GAAG,CAAA;AAEpC,IAAA,MAAM,mBAAsB,GAAA,MAAM,IAAK,CAAA,sBAAA,CAAuB,GAAG,CAAA;AACjE,IAAA,IAAI,OAAS,EAAA,IAAA,IAAQ,OAAQ,CAAA,IAAA,KAAS,mBAAqB,EAAA;AACzD,MAAA,MAAM,IAAIH,uBAAiB,EAAA;AAAA;AAG7B,IAAA,MAAM,cAAc,MAAMI,yCAAA;AAAA,MACxB,GAAA;AAAA,MACA,KAAK,WAAY,CAAA;AAAA,KACnB;AACA,IAAA,MAAM,kBAAkB,MAAM,KAAA;AAAA,MAC5B,WAAA;AAAA,MACAL,4CAAA,CAAiC,IAAK,CAAA,WAAA,CAAY,MAAM;AAAA,KAC1D;AACA,IAAI,IAAA,CAAC,gBAAgB,EAAI,EAAA;AACvB,MAAM,MAAA,OAAA,GAAU,4BAA4B,GAAG,CAAA,EAAA,EAAK,gBAAgB,MAAM,CAAA,CAAA,EAAI,gBAAgB,UAAU,CAAA,CAAA;AACxG,MAAI,IAAA,eAAA,CAAgB,WAAW,GAAK,EAAA;AAClC,QAAM,MAAA,IAAIG,qBAAc,OAAO,CAAA;AAAA;AAEjC,MAAM,MAAA,IAAI,MAAM,OAAO,CAAA;AAAA;AAGzB,IAAA,OAAO,MAAM,IAAA,CAAK,IAAK,CAAA,mBAAA,CAAoB,cAAe,CAAA;AAAA,MACxD,QAAU,EAAA,eAAA;AAAA,MACV,OAAS,EAAA,QAAA;AAAA,MACT,IAAM,EAAA,mBAAA;AAAA,MACN,QAAQ,OAAS,EAAA;AAAA,KAClB,CAAA;AAAA;AACH,EAEA,MAAM,MACJ,CAAA,GAAA,EACA,OACyC,EAAA;AACzC,IAAA,MAAM,EAAE,QAAA,EAAa,GAAAC,4BAAA,CAAY,GAAG,CAAA;AAGpC,IAAA,IAAI,CAAC,QAAA,EAAU,KAAM,CAAA,MAAM,CAAG,EAAA;AAC5B,MAAI,IAAA;AACF,QAAA,MAAM,IAAO,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,KAAK,OAAO,CAAA;AAE5C,QAAO,OAAA;AAAA,UACL,KAAO,EAAA;AAAA,YACL;AAAA,cACE,GAAA;AAAA,cACA,SAAS,IAAK,CAAA,MAAA;AAAA,cACd,gBAAgB,IAAK,CAAA;AAAA;AACvB,WACF;AAAA,UACA,IAAA,EAAM,KAAK,IAAQ,IAAA;AAAA,SACrB;AAAA,eACO,KAAO,EAAA;AACd,QAAAE,kBAAA,CAAY,KAAK,CAAA;AACjB,QAAI,IAAA,KAAA,CAAM,SAAS,eAAiB,EAAA;AAClC,UAAO,OAAA;AAAA,YACL,OAAO,EAAC;AAAA,YACR,IAAM,EAAA;AAAA,WACR;AAAA;AAEF,QAAM,MAAA,KAAA;AAAA;AACR;AAGF,IAAM,MAAA,OAAA,GAAU,IAAIC,mBAAA,CAAU,QAAQ,CAAA;AAMtC,IAAA,MAAM,UAAUC,cAAQ,CAAA,GAAA,CAAI,QAAQ,QAAU,EAAA,EAAE,GAAG,GAAG,CAAA;AAEtD,IAAA,MAAM,IAAO,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,OAAS,EAAA;AAAA,MACxC,MAAM,OAAS,EAAA,IAAA;AAAA,MACf,MAAQ,EAAA,CAAA,IAAA,KAAQ,OAAQ,CAAA,KAAA,CAAM,IAAI;AAAA,KACnC,CAAA;AACD,IAAM,MAAA,KAAA,GAAQ,MAAM,IAAA,CAAK,KAAM,EAAA;AAE/B,IAAO,OAAA;AAAA,MACL,MAAM,IAAK,CAAA,IAAA;AAAA,MACX,KAAA,EAAO,KAAM,CAAA,GAAA,CAAI,CAAS,IAAA,MAAA;AAAA,QACxB,GAAA,EAAK,IAAK,CAAA,WAAA,CAAY,UAAW,CAAA;AAAA,UAC/B,GAAA,EAAK,CAAI,CAAA,EAAA,IAAA,CAAK,IAAI,CAAA,CAAA;AAAA,UAClB,IAAM,EAAA;AAAA,SACP,CAAA;AAAA,QACD,SAAS,IAAK,CAAA,OAAA;AAAA,QACd,gBAAgB,IAAK,CAAA;AAAA,OACrB,CAAA;AAAA,KACJ;AAAA;AACF,EAEA,QAAW,GAAA;AACT,IAAA,MAAM,EAAE,IAAA,EAAM,KAAM,EAAA,GAAI,KAAK,WAAY,CAAA,MAAA;AACzC,IAAM,MAAA,MAAA,GAAS,QAAQ,KAAK,CAAA;AAC5B,IAAO,OAAA,CAAA,qBAAA,EAAwB,IAAI,CAAA,QAAA,EAAW,MAAM,CAAA,CAAA,CAAA;AAAA;AACtD,EAEA,MAAc,uBAAuB,GAA8B,EAAA;AACjE,IAAM,MAAA,EAAE,MAAM,QAAU,EAAA,KAAA,EAAO,SAAS,GAAK,EAAA,MAAA,EAAW,GAAAJ,4BAAA,CAAY,GAAG,CAAA;AAGvE,IAAA,MAAM,kBAAkB,MACpB,GAAA,CAAA,YAAA,EAAe,kBAAmB,CAAA,MAAM,CAAC,CACzC,CAAA,GAAA,UAAA;AAGJ,IAAM,MAAA,aAAA,GAAgB,CAAG,EAAA,IAAA,CAAK,WAAY,CAAA,MAAA,CAAO,UAAU,CAAA,UAAA,EAAa,OAAO,CAAA,OAAA,EAAU,QAAQ,CAAA,SAAA,EAAY,eAAe,CAAA,CAAA;AAE5H,IAAA,MAAM,qBAAqB,MAAM,KAAA;AAAA,MAC/B,aAAA;AAAA,MACAJ,4CAAA,CAAiC,IAAK,CAAA,WAAA,CAAY,MAAM;AAAA,KAC1D;AACA,IAAI,IAAA,CAAC,mBAAmB,EAAI,EAAA;AAC1B,MAAM,MAAA,OAAA,GAAU,uCAAuC,aAAa,CAAA,EAAA,EAAK,mBAAmB,MAAM,CAAA,CAAA,EAAI,mBAAmB,UAAU,CAAA,CAAA;AACnI,MAAI,IAAA,kBAAA,CAAmB,WAAW,GAAK,EAAA;AACrC,QAAM,MAAA,IAAIG,qBAAc,OAAO,CAAA;AAAA;AAEjC,MAAM,MAAA,IAAI,MAAM,OAAO,CAAA;AAAA;AAGzB,IAAM,MAAA,aAAA,GAAgB,MAAM,kBAAA,CAAmB,IAAK,EAAA;AAEpD,IAAI,IAAA,aAAA,IAAiB,aAAc,CAAA,IAAA,GAAO,CAAG,EAAA;AAC3C,MAAM,MAAA,gBAAA,GAAmB,cAAc,MAAO,CAAA,MAAA;AAAA,QAC5C,CAAC,aACC,KAAA,aAAA,CAAc,SAAc,KAAA;AAAA,QAC9B,CAAC,CAAA;AACH,MAAA,OAAO,gBAAiB,CAAA,YAAA,CAAa,SAAU,CAAA,CAAA,EAAG,EAAE,CAAA;AAAA;AAItD,IAAI,IAAA,CAAC,UAAU,aAAe,EAAA;AAC5B,MAAA,OAAO,aAAc,CAAA,YAAA,CAAa,SAAU,CAAA,CAAA,EAAG,EAAE,CAAA;AAAA;AAGnD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,oCACE,MAAS,GAAA,CAAA,QAAA,EAAW,MAAM,CAAM,CAAA,CAAA,GAAA,gBAClC,qBAAqB,aAAa,CAAA;AAAA,KACpC;AAAA;AAEJ;;;;"}
|
package/dist/package.json.cjs.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
212
|
-
"@backstage/backend-dev-utils": "0.1.5",
|
|
213
|
-
"@backstage/backend-plugin-api": "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
|
|
219
|
-
"@backstage/integration-aws-node": "0.1.15",
|
|
220
|
-
"@backstage/plugin-auth-node": "0.6.
|
|
221
|
-
"@backstage/plugin-events-node": "0.4.
|
|
222
|
-
"@backstage/plugin-permission-node": "0.9.
|
|
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.
|
|
275
|
-
"@backstage/backend-test-utils": "1.
|
|
276
|
-
"@backstage/cli": "0.32.0
|
|
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",
|