@backstage/backend-defaults 0.6.0-next.0 → 0.6.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/config.d.ts +74 -1
- package/dist/entrypoints/auth/authServiceFactory.cjs.js +3 -2
- package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +2 -1
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +5 -3
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/database/connectors/postgres.cjs.js +40 -6
- package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js +3 -4
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js +11 -5
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js +23 -0
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/createLifecycleMiddleware.cjs.js +64 -0
- package/dist/entrypoints/rootHttpRouter/createLifecycleMiddleware.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js +1 -7
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +27 -6
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js +29 -0
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js +9 -3
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js +15 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +5 -11
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +5 -14
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +16 -14
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js +5 -14
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +2 -10
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +4 -9
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +7 -16
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js +16 -2
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js +23 -4
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/util.cjs.js +29 -1
- package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -1
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js +1 -6
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +16 -3
- package/dist/package.json.cjs.js.map +1 -1
- package/dist/rootHttpRouter.d.ts +3 -1
- package/dist/urlReader.d.ts +17 -2
- package/package.json +26 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @backstage/backend-defaults
|
|
2
2
|
|
|
3
|
+
## 0.6.0-next.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fd5d337: Added a new `backend.health.headers` configuration that can be used to set additional headers to include in health check responses.
|
|
8
|
+
|
|
9
|
+
**BREAKING CONSUMERS**: As part of this change the `createHealthRouter` function exported from `@backstage/backend-defaults/rootHttpRouter` now requires the root config service to be passed through the `config` option.
|
|
10
|
+
|
|
11
|
+
- 3f34ea9: Throttles Bitbucket Server API calls
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- dfc8b41: Updated dependency `@opentelemetry/api` to `^1.9.0`.
|
|
16
|
+
- 57e0b11: The user and plugin token verification in the default `AuthService` implementation will no longer forward verification errors to the caller, and instead log them as warnings.
|
|
17
|
+
- 57e0b11: The default `authServiceFactory` now correctly depends on the plugin scoped `Logger` services rather than the root scoped one.
|
|
18
|
+
- 0e9c9fa: Implements the `DefaultRootLifecycleService.addBeforeShutdownHook` method, and updates `DefaultRootHttpRouterService` and `DefaultRootHealthService` to listen to that event to stop accepting traffic and close service connections.
|
|
19
|
+
- d0cbd82: Remove use of the `stoppable` library on the `DefaultRootHttpRouterService` as Node's native http server [close](https://nodejs.org/api/http.html#serverclosecallback) method already drains requests.
|
|
20
|
+
Also, we pass a new `lifecycleMiddleware` to the `rootHttpRouterServiceFactory` configure function that must be called manually if you don't call `applyDefaults`.
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @backstage/backend-plugin-api@1.1.0-next.2
|
|
23
|
+
- @backstage/backend-app-api@1.1.0-next.2
|
|
24
|
+
- @backstage/plugin-permission-node@0.8.6-next.2
|
|
25
|
+
- @backstage/errors@1.2.6-next.0
|
|
26
|
+
- @backstage/plugin-auth-node@0.5.5-next.2
|
|
27
|
+
- @backstage/plugin-events-node@0.4.6-next.2
|
|
28
|
+
- @backstage/cli-node@0.2.11-next.1
|
|
29
|
+
- @backstage/config-loader@1.9.3-next.1
|
|
30
|
+
- @backstage/backend-dev-utils@0.1.5
|
|
31
|
+
- @backstage/cli-common@0.1.15
|
|
32
|
+
- @backstage/config@1.3.1-next.0
|
|
33
|
+
- @backstage/integration@1.16.0-next.1
|
|
34
|
+
- @backstage/integration-aws-node@0.1.14-next.0
|
|
35
|
+
- @backstage/types@1.2.0
|
|
36
|
+
|
|
37
|
+
## 0.6.0-next.1
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- fe87fbf: Add task metrics as two gauges that track the last start and end timestamps as epoch seconds.
|
|
42
|
+
- 1ac6b72: Support `connection.type: cloudsql` in database client for usage with `@google-cloud/cloud-sql-connector` and `iam` auth
|
|
43
|
+
- 5c9cc05: Use native fetch instead of node-fetch
|
|
44
|
+
- cf627c6: Fixed an issue in the WinstonLogger where Errors thrown and given to logger.error with field values that could not be cast to a string would throw a TypeError
|
|
45
|
+
- Updated dependencies
|
|
46
|
+
- @backstage/plugin-auth-node@0.5.5-next.1
|
|
47
|
+
- @backstage/backend-app-api@1.1.0-next.1
|
|
48
|
+
- @backstage/config-loader@1.9.3-next.0
|
|
49
|
+
- @backstage/backend-plugin-api@1.1.0-next.1
|
|
50
|
+
- @backstage/plugin-permission-node@0.8.6-next.1
|
|
51
|
+
- @backstage/backend-dev-utils@0.1.5
|
|
52
|
+
- @backstage/cli-common@0.1.15
|
|
53
|
+
- @backstage/cli-node@0.2.11-next.0
|
|
54
|
+
- @backstage/config@1.3.0
|
|
55
|
+
- @backstage/errors@1.2.5
|
|
56
|
+
- @backstage/integration@1.16.0-next.0
|
|
57
|
+
- @backstage/integration-aws-node@0.1.13
|
|
58
|
+
- @backstage/types@1.2.0
|
|
59
|
+
- @backstage/plugin-events-node@0.4.6-next.1
|
|
60
|
+
|
|
3
61
|
## 0.6.0-next.0
|
|
4
62
|
|
|
5
63
|
### Minor Changes
|
package/config.d.ts
CHANGED
|
@@ -28,6 +28,29 @@ export interface Config {
|
|
|
28
28
|
*/
|
|
29
29
|
baseUrl: string;
|
|
30
30
|
|
|
31
|
+
lifecycle?: {
|
|
32
|
+
/**
|
|
33
|
+
* The maximum time that paused requests will wait for the service to start, before returning an error.
|
|
34
|
+
* Defaults to 5 seconds
|
|
35
|
+
* Supported formats:
|
|
36
|
+
* - A string in the format of '1d', '2 seconds' etc. as supported by the `ms`
|
|
37
|
+
* library.
|
|
38
|
+
* - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'.
|
|
39
|
+
* - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`.
|
|
40
|
+
*/
|
|
41
|
+
startupRequestPauseTimeout?: string | HumanDuration;
|
|
42
|
+
/**
|
|
43
|
+
* The minimum time that the HTTP server will delay the shutdown of the backend. During this delay health checks will be set to failing, allowing traffic to drain.
|
|
44
|
+
* Defaults to 0 seconds.
|
|
45
|
+
* Supported formats:
|
|
46
|
+
* - A string in the format of '1d', '2 seconds' etc. as supported by the `ms`
|
|
47
|
+
* library.
|
|
48
|
+
* - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'.
|
|
49
|
+
* - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`.
|
|
50
|
+
*/
|
|
51
|
+
serverShutdownDelay?: string | HumanDuration;
|
|
52
|
+
};
|
|
53
|
+
|
|
31
54
|
/** Address that the backend should listen to. */
|
|
32
55
|
listen?:
|
|
33
56
|
| string
|
|
@@ -384,6 +407,16 @@ export interface Config {
|
|
|
384
407
|
*/
|
|
385
408
|
connection:
|
|
386
409
|
| string
|
|
410
|
+
| {
|
|
411
|
+
/**
|
|
412
|
+
* The specific config for cloudsql connections
|
|
413
|
+
*/
|
|
414
|
+
type: 'cloudsql';
|
|
415
|
+
/**
|
|
416
|
+
* The instance connection name for the cloudsql instance, e.g. `project:region:instance`
|
|
417
|
+
*/
|
|
418
|
+
instance: string;
|
|
419
|
+
}
|
|
387
420
|
| {
|
|
388
421
|
/**
|
|
389
422
|
* Password that belongs to the client User
|
|
@@ -441,7 +474,30 @@ export interface Config {
|
|
|
441
474
|
* Database connection string or Knex object override
|
|
442
475
|
* @visibility secret
|
|
443
476
|
*/
|
|
444
|
-
connection?:
|
|
477
|
+
connection?:
|
|
478
|
+
| string
|
|
479
|
+
| {
|
|
480
|
+
/**
|
|
481
|
+
* The specific config for cloudsql connections
|
|
482
|
+
*/
|
|
483
|
+
type: 'cloudsql';
|
|
484
|
+
/**
|
|
485
|
+
* The instance connection name for the cloudsql instance, e.g. `project:region:instance`
|
|
486
|
+
*/
|
|
487
|
+
instance: string;
|
|
488
|
+
}
|
|
489
|
+
| {
|
|
490
|
+
/**
|
|
491
|
+
* Password that belongs to the client User
|
|
492
|
+
* @visibility secret
|
|
493
|
+
*/
|
|
494
|
+
password?: string;
|
|
495
|
+
/**
|
|
496
|
+
* Other connection settings
|
|
497
|
+
*/
|
|
498
|
+
[key: string]: unknown;
|
|
499
|
+
};
|
|
500
|
+
|
|
445
501
|
/**
|
|
446
502
|
* Whether to ensure the given database exists by creating it if it does not.
|
|
447
503
|
* Defaults to base config if unspecified.
|
|
@@ -519,6 +575,23 @@ export interface Config {
|
|
|
519
575
|
*/
|
|
520
576
|
csp?: { [policyId: string]: string[] | false };
|
|
521
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Options for the health check service and endpoint.
|
|
580
|
+
*/
|
|
581
|
+
health?: {
|
|
582
|
+
/**
|
|
583
|
+
* Additional headers to always include in the health check response.
|
|
584
|
+
*
|
|
585
|
+
* It can be a good idea to set a header that uniquely identifies your service
|
|
586
|
+
* in a multi-service environment. This ensures that the health check that is
|
|
587
|
+
* configured for your service is actually hitting your service and not another.
|
|
588
|
+
*
|
|
589
|
+
* For example, if using Envoy you can use the `service_name_matcher` configuration
|
|
590
|
+
* and set the `x-envoy-upstream-healthchecked-cluster` header to a matching value.
|
|
591
|
+
*/
|
|
592
|
+
headers?: { [name: string]: string };
|
|
593
|
+
};
|
|
594
|
+
|
|
522
595
|
/**
|
|
523
596
|
* Configuration related to URL reading, used for example for reading catalog info
|
|
524
597
|
* files, scaffolder templates, and techdocs content.
|
|
@@ -21,7 +21,7 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
21
21
|
service: backendPluginApi.coreServices.auth,
|
|
22
22
|
deps: {
|
|
23
23
|
config: backendPluginApi.coreServices.rootConfig,
|
|
24
|
-
logger: backendPluginApi.coreServices.
|
|
24
|
+
logger: backendPluginApi.coreServices.logger,
|
|
25
25
|
discovery: backendPluginApi.coreServices.discovery,
|
|
26
26
|
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
27
27
|
database: backendPluginApi.coreServices.database,
|
|
@@ -46,7 +46,8 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
46
46
|
keyDuration
|
|
47
47
|
});
|
|
48
48
|
const userTokens = UserTokenHandler.UserTokenHandler.create({
|
|
49
|
-
discovery
|
|
49
|
+
discovery,
|
|
50
|
+
logger
|
|
50
51
|
});
|
|
51
52
|
const pluginTokens = pluginTokenHandlerDecorator(
|
|
52
53
|
PluginTokenHandler.DefaultPluginTokenHandler.create({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.
|
|
1
|
+
{"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef,\n },\n async factory({\n config,\n discovery,\n plugin,\n logger,\n database,\n pluginTokenHandlerDecorator,\n }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n logger,\n });\n\n const pluginTokens = pluginTokenHandlerDecorator(\n DefaultPluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n }),\n );\n\n const externalTokens = ExternalTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceRef","createServiceFactory","coreServices","createPluginKeySource","UserTokenHandler","DefaultPluginTokenHandler","ExternalTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAkCO,MAAM,wCAAwCA,iCAEnD,CAAA;AAAA,EACA,EAAI,EAAA,uCAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAqB,CAAA;AAAA,IACnB,OAAA;AAAA,IACA,MAAM,EAAC;AAAA,IACP,SAAS,YAAY;AACnB,MAAA,OAAO,CAAQ,IAAA,KAAA,IAAA;AAAA;AACjB,GACD;AACL,CAAC;AAWM,MAAM,qBAAqBA,qCAAqB,CAAA;AAAA,EACrD,SAASC,6BAAa,CAAA,IAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,IACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,IACvB,2BAA6B,EAAA;AAAA,GAC/B;AAAA,EACA,MAAM,OAAQ,CAAA;AAAA,IACZ,MAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACC,EAAA;AACD,IAAA,MAAM,2BACJ,MAAO,CAAA,kBAAA;AAAA,MACL;AAAA,KACG,IAAA,KAAA;AAEP,IAAM,MAAA,WAAA,GAAc,EAAE,KAAA,EAAO,CAAE,EAAA;AAE/B,IAAM,MAAA,SAAA,GAAY,MAAMC,2CAAsB,CAAA;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAM,MAAA,UAAA,GAAaC,kCAAiB,MAAO,CAAA;AAAA,MACzC,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,YAAe,GAAA,2BAAA;AAAA,MACnBC,6CAA0B,MAAO,CAAA;AAAA,QAC/B,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,QAC1B,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAEA,IAAM,MAAA,cAAA,GAAiBC,0CAAqB,MAAO,CAAA;AAAA,MACjD,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,MAC1B,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAM,EAAA;AAAA,MACb,wBAAA;AAAA,MACA;AAAA,KACF;AAAA;AAEJ,CAAC;;;;;"}
|
|
@@ -60,7 +60,8 @@ class DefaultPluginTokenHandler {
|
|
|
60
60
|
requiredClaims: ["iat", "exp", "sub", "aud"]
|
|
61
61
|
}
|
|
62
62
|
).catch((e) => {
|
|
63
|
-
|
|
63
|
+
this.logger.warn("Failed to verify incoming plugin token", e);
|
|
64
|
+
throw new errors.AuthenticationError("Failed plugin token verification");
|
|
64
65
|
});
|
|
65
66
|
return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };
|
|
66
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n throw new AuthenticationError('Invalid plugin token', e);\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
|
|
@@ -6,15 +6,16 @@ var jose = require('jose');
|
|
|
6
6
|
var JwksClient = require('../JwksClient.cjs.js');
|
|
7
7
|
|
|
8
8
|
class UserTokenHandler {
|
|
9
|
-
constructor(jwksClient) {
|
|
9
|
+
constructor(jwksClient, logger) {
|
|
10
10
|
this.jwksClient = jwksClient;
|
|
11
|
+
this.logger = logger;
|
|
11
12
|
}
|
|
12
13
|
static create(options) {
|
|
13
14
|
const jwksClient = new JwksClient.JwksClient(async () => {
|
|
14
15
|
const url = await options.discovery.getBaseUrl("auth");
|
|
15
16
|
return new URL(`${url}/.well-known/jwks.json`);
|
|
16
17
|
});
|
|
17
|
-
return new UserTokenHandler(jwksClient);
|
|
18
|
+
return new UserTokenHandler(jwksClient, options.logger);
|
|
18
19
|
}
|
|
19
20
|
async verifyToken(token) {
|
|
20
21
|
const verifyOpts = this.#getTokenVerificationOptions(token);
|
|
@@ -27,7 +28,8 @@ class UserTokenHandler {
|
|
|
27
28
|
this.jwksClient.getKey,
|
|
28
29
|
verifyOpts
|
|
29
30
|
).catch((e) => {
|
|
30
|
-
|
|
31
|
+
this.logger.warn("Failed to verify incoming user token", e);
|
|
32
|
+
throw new errors.AuthenticationError("Failed user token verification");
|
|
31
33
|
});
|
|
32
34
|
const userEntityRef = payload.sub;
|
|
33
35
|
if (!userEntityRef) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/user/UserTokenHandler.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 { DiscoveryService } from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport {\n base64url,\n decodeJwt,\n decodeProtectedHeader,\n jwtVerify,\n JWTVerifyOptions,\n} from 'jose';\nimport { JwksClient } from '../JwksClient';\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @internal\n */\nexport class UserTokenHandler {\n static create(options: {
|
|
1
|
+
{"version":3,"file":"UserTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/user/UserTokenHandler.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 { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport {\n base64url,\n decodeJwt,\n decodeProtectedHeader,\n jwtVerify,\n JWTVerifyOptions,\n} from 'jose';\nimport { JwksClient } from '../JwksClient';\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @internal\n */\nexport class UserTokenHandler {\n static create(options: {\n discovery: DiscoveryService;\n logger: LoggerService;\n }): UserTokenHandler {\n const jwksClient = new JwksClient(async () => {\n const url = await options.discovery.getBaseUrl('auth');\n return new URL(`${url}/.well-known/jwks.json`);\n });\n return new UserTokenHandler(jwksClient, options.logger);\n }\n\n constructor(\n private readonly jwksClient: JwksClient,\n private readonly logger: LoggerService,\n ) {}\n\n async verifyToken(token: string) {\n const verifyOpts = this.#getTokenVerificationOptions(token);\n if (!verifyOpts) {\n return undefined;\n }\n\n await this.jwksClient.refreshKeyStore(token);\n\n // Verify a limited token, ensuring the necessarily claims are present and token type is correct\n const { payload } = await jwtVerify(\n token,\n this.jwksClient.getKey,\n verifyOpts,\n ).catch(e => {\n this.logger.warn('Failed to verify incoming user token', e);\n throw new AuthenticationError('Failed user token verification');\n });\n\n const userEntityRef = payload.sub;\n\n if (!userEntityRef) {\n throw new AuthenticationError('No user sub found in token');\n }\n\n return { userEntityRef };\n }\n\n #getTokenVerificationOptions(token: string): JWTVerifyOptions | undefined {\n try {\n const { typ } = decodeProtectedHeader(token);\n\n if (typ === tokenTypes.user.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.user.typParam,\n };\n }\n\n if (typ === tokenTypes.limitedUser.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.limitedUser.typParam,\n };\n }\n\n const { aud } = decodeJwt(token);\n if (aud === tokenTypes.user.audClaim) {\n return {\n audience: tokenTypes.user.audClaim,\n };\n }\n } catch {\n /* ignore */\n }\n\n return undefined;\n }\n\n createLimitedUserToken(backstageToken: string) {\n const [headerRaw, payloadRaw] = backstageToken.split('.');\n const header = JSON.parse(\n new TextDecoder().decode(base64url.decode(headerRaw)),\n );\n const payload = JSON.parse(\n new TextDecoder().decode(base64url.decode(payloadRaw)),\n );\n\n const tokenType = header.typ;\n\n // Only new user tokens can be used to create a limited user token. If we\n // can't create a limited token, or the token is already a limited one, we\n // return the original token\n if (!tokenType || tokenType === tokenTypes.limitedUser.typParam) {\n return { token: backstageToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n if (tokenType !== tokenTypes.user.typParam) {\n throw new AuthenticationError(\n 'Failed to create limited user token, invalid token type',\n );\n }\n\n // NOTE: The order and properties in both the header and payload must match\n // the usage in plugins/auth-backend/src/identity/TokenFactory.ts\n const limitedUserToken = [\n base64url.encode(\n JSON.stringify({\n typ: tokenTypes.limitedUser.typParam,\n alg: header.alg,\n kid: header.kid,\n }),\n ),\n base64url.encode(\n JSON.stringify({\n sub: payload.sub,\n iat: payload.iat,\n exp: payload.exp,\n }),\n ),\n payload.uip,\n ].join('.');\n\n return { token: limitedUserToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n isLimitedUserToken(token: string): boolean {\n try {\n const { typ } = decodeProtectedHeader(token);\n return typ === tokenTypes.limitedUser.typParam;\n } catch {\n return false;\n }\n }\n}\n"],"names":["JwksClient","jwtVerify","AuthenticationError","decodeProtectedHeader","tokenTypes","decodeJwt","base64url"],"mappings":"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"}
|
|
@@ -17,8 +17,8 @@ var limiterFactory__default = /*#__PURE__*/_interopDefaultCompat(limiterFactory)
|
|
|
17
17
|
var format__default = /*#__PURE__*/_interopDefaultCompat(format);
|
|
18
18
|
|
|
19
19
|
const ddlLimiter = limiterFactory__default.default(1);
|
|
20
|
-
function createPgDatabaseClient(dbConfig, overrides) {
|
|
21
|
-
const knexConfig = buildPgDatabaseConfig(dbConfig, overrides);
|
|
20
|
+
async function createPgDatabaseClient(dbConfig, overrides) {
|
|
21
|
+
const knexConfig = await buildPgDatabaseConfig(dbConfig, overrides);
|
|
22
22
|
const database = knexFactory__default.default(knexConfig);
|
|
23
23
|
const role = dbConfig.getOptionalString("role");
|
|
24
24
|
if (role) {
|
|
@@ -32,8 +32,8 @@ function createPgDatabaseClient(dbConfig, overrides) {
|
|
|
32
32
|
}
|
|
33
33
|
return database;
|
|
34
34
|
}
|
|
35
|
-
function buildPgDatabaseConfig(dbConfig, overrides) {
|
|
36
|
-
|
|
35
|
+
async function buildPgDatabaseConfig(dbConfig, overrides) {
|
|
36
|
+
const config = mergeDatabaseConfig.mergeDatabaseConfig(
|
|
37
37
|
dbConfig.get(),
|
|
38
38
|
{
|
|
39
39
|
connection: getPgConnectionConfig(dbConfig, !!overrides),
|
|
@@ -41,6 +41,40 @@ function buildPgDatabaseConfig(dbConfig, overrides) {
|
|
|
41
41
|
},
|
|
42
42
|
overrides
|
|
43
43
|
);
|
|
44
|
+
const sanitizedConfig = JSON.parse(JSON.stringify(config));
|
|
45
|
+
delete sanitizedConfig.connection.type;
|
|
46
|
+
delete sanitizedConfig.connection.instance;
|
|
47
|
+
if (config.connection.type === "default" || !config.connection.type) {
|
|
48
|
+
return sanitizedConfig;
|
|
49
|
+
}
|
|
50
|
+
if (config.connection.type !== "cloudsql") {
|
|
51
|
+
throw new Error(`Unknown connection type: ${config.connection.type}`);
|
|
52
|
+
}
|
|
53
|
+
if (config.client !== "pg") {
|
|
54
|
+
throw new Error("Cloud SQL only supports the pg client");
|
|
55
|
+
}
|
|
56
|
+
if (!config.connection.instance) {
|
|
57
|
+
throw new Error("Missing instance connection name for Cloud SQL");
|
|
58
|
+
}
|
|
59
|
+
const {
|
|
60
|
+
Connector: CloudSqlConnector,
|
|
61
|
+
IpAddressTypes,
|
|
62
|
+
AuthTypes
|
|
63
|
+
} = await import('@google-cloud/cloud-sql-connector');
|
|
64
|
+
const connector = new CloudSqlConnector();
|
|
65
|
+
const clientOpts = await connector.getOptions({
|
|
66
|
+
instanceConnectionName: config.connection.instance,
|
|
67
|
+
ipType: IpAddressTypes.PUBLIC,
|
|
68
|
+
authType: AuthTypes.IAM
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
...sanitizedConfig,
|
|
72
|
+
client: "pg",
|
|
73
|
+
connection: {
|
|
74
|
+
...sanitizedConfig.connection,
|
|
75
|
+
...clientOpts
|
|
76
|
+
}
|
|
77
|
+
};
|
|
44
78
|
}
|
|
45
79
|
function getPgConnectionConfig(dbConfig, parseConnectionString) {
|
|
46
80
|
const connection = dbConfig.get("connection");
|
|
@@ -61,7 +95,7 @@ function requirePgConnectionString() {
|
|
|
61
95
|
}
|
|
62
96
|
}
|
|
63
97
|
async function ensurePgDatabaseExists(dbConfig, ...databases) {
|
|
64
|
-
const admin = createPgDatabaseClient(dbConfig, {
|
|
98
|
+
const admin = await createPgDatabaseClient(dbConfig, {
|
|
65
99
|
connection: {
|
|
66
100
|
database: "postgres"
|
|
67
101
|
},
|
|
@@ -97,7 +131,7 @@ async function ensurePgDatabaseExists(dbConfig, ...databases) {
|
|
|
97
131
|
}
|
|
98
132
|
}
|
|
99
133
|
async function ensurePgSchemaExists(dbConfig, ...schemas) {
|
|
100
|
-
const admin = createPgDatabaseClient(dbConfig);
|
|
134
|
+
const admin = await createPgDatabaseClient(dbConfig);
|
|
101
135
|
const role = dbConfig.getOptionalString("role");
|
|
102
136
|
try {
|
|
103
137
|
const ensureSchema = async (database) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.cjs.js","sources":["../../../../src/entrypoints/database/connectors/postgres.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 { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport limiterFactory from 'p-limit';\nimport { Client } from 'pg';\nimport { Connector } from '../types';\nimport defaultNameOverride from './defaultNameOverride';\nimport defaultSchemaOverride from './defaultSchemaOverride';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\nimport format from 'pg-format';\n\n// Limits the number of concurrent DDL operations to 1\nconst ddlLimiter = limiterFactory(1);\n\n/**\n * Creates a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function createPgDatabaseClient(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const knexConfig = buildPgDatabaseConfig(dbConfig, overrides);\n const database = knexFactory(knexConfig);\n\n const role = dbConfig.getOptionalString('role');\n\n if (role) {\n database.client.pool.on(\n 'createSuccess',\n async (_event: number, pgClient: Client) => {\n const query = format('SET ROLE %I', role);\n await pgClient.query(query);\n },\n );\n }\n return database;\n}\n\n/**\n * Builds a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function buildPgDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n return mergeDatabaseConfig(\n dbConfig.get(),\n {\n connection: getPgConnectionConfig(dbConfig, !!overrides),\n useNullAsDefault: true,\n },\n overrides,\n );\n}\n\n/**\n * Gets the postgres connection config\n *\n * @param dbConfig - The database config\n * @param parseConnectionString - Flag to explicitly control connection string parsing\n */\nexport function getPgConnectionConfig(\n dbConfig: Config,\n parseConnectionString?: boolean,\n): Knex.PgConnectionConfig | string {\n const connection = dbConfig.get('connection') as any;\n const isConnectionString =\n typeof connection === 'string' || connection instanceof String;\n const autoParse = typeof parseConnectionString !== 'boolean';\n\n const shouldParseConnectionString = autoParse\n ? isConnectionString\n : parseConnectionString && isConnectionString;\n\n return shouldParseConnectionString\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\n/**\n * Parses a connection string using pg-connection-string\n *\n * @param connectionString - The postgres connection string\n */\nexport function parsePgConnectionString(connectionString: string) {\n const parse = requirePgConnectionString();\n return parse(connectionString);\n}\n\nfunction requirePgConnectionString() {\n try {\n return require('pg-connection-string').parse;\n } catch (e) {\n throw new ForwardedError(\"Postgres: Install 'pg-connection-string'\", e);\n }\n}\n\n/**\n * Creates the missing Postgres database if it does not exist\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to create\n */\nexport async function ensurePgDatabaseExists(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = createPgDatabaseClient(dbConfig, {\n connection: {\n database: 'postgres',\n },\n pool: {\n min: 0,\n acquireTimeoutMillis: 10000,\n },\n });\n\n try {\n const ensureDatabase = async (database: string) => {\n const result = await admin\n .from('pg_database')\n .where('datname', database)\n .count<Record<string, { count: string }>>();\n\n if (parseInt(result[0].count, 10) > 0) {\n return;\n }\n\n await admin.raw(`CREATE DATABASE ??`, [database]);\n };\n\n await Promise.all(\n databases.map(async database => {\n // For initial setup we use a smaller timeout but several retries. Given that this\n // is a separate connection pool we should never really run into issues with connection\n // acquisition timeouts, but we do anyway. This might be a bug in knex or some other dependency.\n let lastErr: Error | undefined = undefined;\n for (let i = 0; i < 3; i++) {\n try {\n return await ddlLimiter(() => ensureDatabase(database));\n } catch (err) {\n lastErr = err;\n }\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n throw lastErr;\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Creates the missing Postgres schema if it does not exist\n *\n * @param dbConfig - The database config\n * @param schemas - The name of the schemas to create\n */\nexport async function ensurePgSchemaExists(\n dbConfig: Config,\n ...schemas: Array<string>\n): Promise<void> {\n const admin = createPgDatabaseClient(dbConfig);\n const role = dbConfig.getOptionalString('role');\n\n try {\n const ensureSchema = async (database: string) => {\n if (role) {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ??`, [\n database,\n role,\n ]);\n } else {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [database]);\n }\n };\n\n await Promise.all(\n schemas.map(database => ddlLimiter(() => ensureSchema(database))),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Drops the Postgres databases.\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to drop\n */\nexport async function dropPgDatabase(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = createPgDatabaseClient(dbConfig);\n try {\n await Promise.all(\n databases.map(async database => {\n await ddlLimiter(() => admin.raw(`DROP DATABASE ??`, [database]));\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\nexport class PgConnector implements Connector {\n constructor(\n private readonly config: Config,\n private readonly prefix: string,\n ) {}\n\n async getClient(\n pluginId: string,\n _deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const databaseName = this.getDatabaseName(pluginId);\n if (databaseName && this.getEnsureExistsConfig(pluginId)) {\n try {\n await ensurePgDatabaseExists(pluginConfig, databaseName);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,\n );\n }\n }\n\n let schemaOverrides;\n if (this.getPluginDivisionModeConfig() === 'schema') {\n schemaOverrides = defaultSchemaOverride(pluginId);\n if (\n this.getEnsureSchemaExistsConfig(pluginId) ||\n this.getEnsureExistsConfig(pluginId)\n ) {\n try {\n await ensurePgSchemaExists(pluginConfig, pluginId);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,\n );\n }\n }\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n schemaOverrides,\n );\n\n const client = createPgDatabaseClient(\n pluginConfig,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using global\n * and plugin specific database config. If no explicit database name is configured\n * and `pluginDivisionMode` is not `schema`, this method will provide a generated name\n * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is\n * `schema`, it will fallback to using the default database for the knex instance.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n\n const databaseName = (connection as Knex.ConnectionConfig)?.database;\n\n // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database\n if (this.getPluginDivisionModeConfig() === 'schema') {\n return databaseName;\n }\n\n // all other supported databases should fallback to an auto-prefixed name\n return databaseName ?? `${this.prefix}${pluginId}`;\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getEnsureExistsConfig(pluginId: string): boolean {\n const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true;\n return (\n this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??\n baseConfig\n );\n }\n\n private getEnsureSchemaExistsConfig(pluginId: string): boolean {\n const baseConfig =\n this.config.getOptionalBoolean('ensureSchemaExists') ?? false;\n return (\n this.config.getOptionalBoolean(\n `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`,\n ) ?? baseConfig\n );\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted\n * unless `pluginDivisionMode` is set to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n (\n baseConnection as Knex.PgConnectionConfig\n ).application_name ||= `backstage_plugin_${pluginId}`;\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? defaultNameOverride(databaseName) : {};\n }\n}\n"],"names":["limiterFactory","knexFactory","format","mergeDatabaseConfig","ForwardedError","ConfigReader","defaultSchemaOverride","merge","omit","defaultNameOverride"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"postgres.cjs.js","sources":["../../../../src/entrypoints/database/connectors/postgres.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 { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport limiterFactory from 'p-limit';\nimport { Client } from 'pg';\nimport { Connector } from '../types';\nimport defaultNameOverride from './defaultNameOverride';\nimport defaultSchemaOverride from './defaultSchemaOverride';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\nimport format from 'pg-format';\n\n// Limits the number of concurrent DDL operations to 1\nconst ddlLimiter = limiterFactory(1);\n\n/**\n * Creates a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport async function createPgDatabaseClient(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const knexConfig = await buildPgDatabaseConfig(dbConfig, overrides);\n const database = knexFactory(knexConfig);\n\n const role = dbConfig.getOptionalString('role');\n\n if (role) {\n database.client.pool.on(\n 'createSuccess',\n async (_event: number, pgClient: Client) => {\n const query = format('SET ROLE %I', role);\n await pgClient.query(query);\n },\n );\n }\n return database;\n}\n\n/**\n * Builds a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport async function buildPgDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const config = mergeDatabaseConfig(\n dbConfig.get(),\n {\n connection: getPgConnectionConfig(dbConfig, !!overrides),\n useNullAsDefault: true,\n },\n overrides,\n );\n\n const sanitizedConfig = JSON.parse(JSON.stringify(config));\n\n // Trim additional properties from the connection object passed to knex\n delete sanitizedConfig.connection.type;\n delete sanitizedConfig.connection.instance;\n\n if (config.connection.type === 'default' || !config.connection.type) {\n return sanitizedConfig;\n }\n\n if (config.connection.type !== 'cloudsql') {\n throw new Error(`Unknown connection type: ${config.connection.type}`);\n }\n\n if (config.client !== 'pg') {\n throw new Error('Cloud SQL only supports the pg client');\n }\n\n if (!config.connection.instance) {\n throw new Error('Missing instance connection name for Cloud SQL');\n }\n\n const {\n Connector: CloudSqlConnector,\n IpAddressTypes,\n AuthTypes,\n } = await import('@google-cloud/cloud-sql-connector');\n const connector = new CloudSqlConnector();\n const clientOpts = await connector.getOptions({\n instanceConnectionName: config.connection.instance,\n ipType: IpAddressTypes.PUBLIC,\n authType: AuthTypes.IAM,\n });\n\n return {\n ...sanitizedConfig,\n client: 'pg',\n connection: {\n ...sanitizedConfig.connection,\n ...clientOpts,\n },\n };\n}\n\n/**\n * Gets the postgres connection config\n *\n * @param dbConfig - The database config\n * @param parseConnectionString - Flag to explicitly control connection string parsing\n */\nexport function getPgConnectionConfig(\n dbConfig: Config,\n parseConnectionString?: boolean,\n): Knex.PgConnectionConfig | string {\n const connection = dbConfig.get('connection') as any;\n const isConnectionString =\n typeof connection === 'string' || connection instanceof String;\n const autoParse = typeof parseConnectionString !== 'boolean';\n\n const shouldParseConnectionString = autoParse\n ? isConnectionString\n : parseConnectionString && isConnectionString;\n\n return shouldParseConnectionString\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\n/**\n * Parses a connection string using pg-connection-string\n *\n * @param connectionString - The postgres connection string\n */\nexport function parsePgConnectionString(connectionString: string) {\n const parse = requirePgConnectionString();\n return parse(connectionString);\n}\n\nfunction requirePgConnectionString() {\n try {\n return require('pg-connection-string').parse;\n } catch (e) {\n throw new ForwardedError(\"Postgres: Install 'pg-connection-string'\", e);\n }\n}\n\n/**\n * Creates the missing Postgres database if it does not exist\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to create\n */\nexport async function ensurePgDatabaseExists(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = await createPgDatabaseClient(dbConfig, {\n connection: {\n database: 'postgres',\n },\n pool: {\n min: 0,\n acquireTimeoutMillis: 10000,\n },\n });\n\n try {\n const ensureDatabase = async (database: string) => {\n const result = await admin\n .from('pg_database')\n .where('datname', database)\n .count<Record<string, { count: string }>>();\n\n if (parseInt(result[0].count, 10) > 0) {\n return;\n }\n\n await admin.raw(`CREATE DATABASE ??`, [database]);\n };\n\n await Promise.all(\n databases.map(async database => {\n // For initial setup we use a smaller timeout but several retries. Given that this\n // is a separate connection pool we should never really run into issues with connection\n // acquisition timeouts, but we do anyway. This might be a bug in knex or some other dependency.\n let lastErr: Error | undefined = undefined;\n for (let i = 0; i < 3; i++) {\n try {\n return await ddlLimiter(() => ensureDatabase(database));\n } catch (err) {\n lastErr = err;\n }\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n throw lastErr;\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Creates the missing Postgres schema if it does not exist\n *\n * @param dbConfig - The database config\n * @param schemas - The name of the schemas to create\n */\nexport async function ensurePgSchemaExists(\n dbConfig: Config,\n ...schemas: Array<string>\n): Promise<void> {\n const admin = await createPgDatabaseClient(dbConfig);\n const role = dbConfig.getOptionalString('role');\n\n try {\n const ensureSchema = async (database: string) => {\n if (role) {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ??`, [\n database,\n role,\n ]);\n } else {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [database]);\n }\n };\n\n await Promise.all(\n schemas.map(database => ddlLimiter(() => ensureSchema(database))),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Drops the Postgres databases.\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to drop\n */\nexport async function dropPgDatabase(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = await createPgDatabaseClient(dbConfig);\n try {\n await Promise.all(\n databases.map(async database => {\n await ddlLimiter(() => admin.raw(`DROP DATABASE ??`, [database]));\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\nexport class PgConnector implements Connector {\n constructor(\n private readonly config: Config,\n private readonly prefix: string,\n ) {}\n\n async getClient(\n pluginId: string,\n _deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const databaseName = this.getDatabaseName(pluginId);\n if (databaseName && this.getEnsureExistsConfig(pluginId)) {\n try {\n await ensurePgDatabaseExists(pluginConfig, databaseName);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,\n );\n }\n }\n\n let schemaOverrides;\n if (this.getPluginDivisionModeConfig() === 'schema') {\n schemaOverrides = defaultSchemaOverride(pluginId);\n if (\n this.getEnsureSchemaExistsConfig(pluginId) ||\n this.getEnsureExistsConfig(pluginId)\n ) {\n try {\n await ensurePgSchemaExists(pluginConfig, pluginId);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,\n );\n }\n }\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n schemaOverrides,\n );\n\n const client = createPgDatabaseClient(\n pluginConfig,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using global\n * and plugin specific database config. If no explicit database name is configured\n * and `pluginDivisionMode` is not `schema`, this method will provide a generated name\n * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is\n * `schema`, it will fallback to using the default database for the knex instance.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n\n const databaseName = (connection as Knex.ConnectionConfig)?.database;\n\n // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database\n if (this.getPluginDivisionModeConfig() === 'schema') {\n return databaseName;\n }\n\n // all other supported databases should fallback to an auto-prefixed name\n return databaseName ?? `${this.prefix}${pluginId}`;\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getEnsureExistsConfig(pluginId: string): boolean {\n const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true;\n return (\n this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??\n baseConfig\n );\n }\n\n private getEnsureSchemaExistsConfig(pluginId: string): boolean {\n const baseConfig =\n this.config.getOptionalBoolean('ensureSchemaExists') ?? false;\n return (\n this.config.getOptionalBoolean(\n `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`,\n ) ?? baseConfig\n );\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted\n * unless `pluginDivisionMode` is set to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n (\n baseConnection as Knex.PgConnectionConfig\n ).application_name ||= `backstage_plugin_${pluginId}`;\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? defaultNameOverride(databaseName) : {};\n }\n}\n"],"names":["limiterFactory","knexFactory","format","mergeDatabaseConfig","ForwardedError","ConfigReader","defaultSchemaOverride","merge","omit","defaultNameOverride"],"mappings":"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"}
|
|
@@ -4,7 +4,8 @@ var Router = require('express-promise-router');
|
|
|
4
4
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
5
5
|
var createAuthIntegrationRouter = require('./http/createAuthIntegrationRouter.cjs.js');
|
|
6
6
|
var createCredentialsBarrier = require('./http/createCredentialsBarrier.cjs.js');
|
|
7
|
-
|
|
7
|
+
require('@backstage/errors');
|
|
8
|
+
require('@backstage/types');
|
|
8
9
|
var createCookieAuthRefreshMiddleware = require('./http/createCookieAuthRefreshMiddleware.cjs.js');
|
|
9
10
|
|
|
10
11
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
@@ -17,12 +18,11 @@ const httpRouterServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
17
18
|
deps: {
|
|
18
19
|
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
19
20
|
config: backendPluginApi.coreServices.rootConfig,
|
|
20
|
-
lifecycle: backendPluginApi.coreServices.lifecycle,
|
|
21
21
|
rootHttpRouter: backendPluginApi.coreServices.rootHttpRouter,
|
|
22
22
|
auth: backendPluginApi.coreServices.auth,
|
|
23
23
|
httpAuth: backendPluginApi.coreServices.httpAuth
|
|
24
24
|
},
|
|
25
|
-
async factory({ auth, httpAuth, config, plugin, rootHttpRouter
|
|
25
|
+
async factory({ auth, httpAuth, config, plugin, rootHttpRouter }) {
|
|
26
26
|
const router = Router__default.default();
|
|
27
27
|
rootHttpRouter.use(`/api/${plugin.getId()}`, router);
|
|
28
28
|
const credentialsBarrier = createCredentialsBarrier.createCredentialsBarrier({
|
|
@@ -30,7 +30,6 @@ const httpRouterServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
30
30
|
config
|
|
31
31
|
});
|
|
32
32
|
router.use(createAuthIntegrationRouter.createAuthIntegrationRouter({ auth }));
|
|
33
|
-
router.use(createLifecycleMiddleware.createLifecycleMiddleware({ lifecycle }));
|
|
34
33
|
router.use(credentialsBarrier.middleware);
|
|
35
34
|
router.use(createCookieAuthRefreshMiddleware.createCookieAuthRefreshMiddleware({ auth, httpAuth }));
|
|
36
35
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpRouter/httpRouterServiceFactory.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 { Handler } from 'express';\nimport PromiseRouter from 'express-promise-router';\nimport {\n coreServices,\n createServiceFactory,\n HttpRouterServiceAuthPolicy,\n} from '@backstage/backend-plugin-api';\nimport {\n
|
|
1
|
+
{"version":3,"file":"httpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpRouter/httpRouterServiceFactory.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 { Handler } from 'express';\nimport PromiseRouter from 'express-promise-router';\nimport {\n coreServices,\n createServiceFactory,\n HttpRouterServiceAuthPolicy,\n} from '@backstage/backend-plugin-api';\nimport {\n createCookieAuthRefreshMiddleware,\n createCredentialsBarrier,\n createAuthIntegrationRouter,\n} from './http';\n\n/**\n * HTTP route registration for plugins.\n *\n * See {@link @backstage/code-plugin-api#HttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport const httpRouterServiceFactory = createServiceFactory({\n service: coreServices.httpRouter,\n initialization: 'always',\n deps: {\n plugin: coreServices.pluginMetadata,\n config: coreServices.rootConfig,\n rootHttpRouter: coreServices.rootHttpRouter,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n },\n async factory({ auth, httpAuth, config, plugin, rootHttpRouter }) {\n const router = PromiseRouter();\n\n rootHttpRouter.use(`/api/${plugin.getId()}`, router);\n\n const credentialsBarrier = createCredentialsBarrier({\n httpAuth,\n config,\n });\n\n router.use(createAuthIntegrationRouter({ auth }));\n router.use(credentialsBarrier.middleware);\n router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth }));\n\n return {\n use(handler: Handler): void {\n router.use(handler);\n },\n addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void {\n credentialsBarrier.addAuthPolicy(policy);\n },\n };\n },\n});\n"],"names":["createServiceFactory","coreServices","PromiseRouter","createCredentialsBarrier","createAuthIntegrationRouter","createCookieAuthRefreshMiddleware"],"mappings":";;;;;;;;;;;;;;AAsCO,MAAM,2BAA2BA,qCAAqB,CAAA;AAAA,EAC3D,SAASC,6BAAa,CAAA,UAAA;AAAA,EACtB,cAAgB,EAAA,QAAA;AAAA,EAChB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,gBAAgBA,6BAAa,CAAA,cAAA;AAAA,IAC7B,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA;AAAA,GACzB;AAAA,EACA,MAAM,QAAQ,EAAE,IAAA,EAAM,UAAU,MAAQ,EAAA,MAAA,EAAQ,gBAAkB,EAAA;AAChE,IAAA,MAAM,SAASC,uBAAc,EAAA;AAE7B,IAAA,cAAA,CAAe,IAAI,CAAQ,KAAA,EAAA,MAAA,CAAO,KAAM,EAAC,IAAI,MAAM,CAAA;AAEnD,IAAA,MAAM,qBAAqBC,iDAAyB,CAAA;AAAA,MAClD,QAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAA,CAAO,GAAI,CAAAC,uDAAA,CAA4B,EAAE,IAAA,EAAM,CAAC,CAAA;AAChD,IAAO,MAAA,CAAA,GAAA,CAAI,mBAAmB,UAAU,CAAA;AACxC,IAAA,MAAA,CAAO,IAAIC,mEAAkC,CAAA,EAAE,IAAM,EAAA,QAAA,EAAU,CAAC,CAAA;AAEhE,IAAO,OAAA;AAAA,MACL,IAAI,OAAwB,EAAA;AAC1B,QAAA,MAAA,CAAO,IAAI,OAAO,CAAA;AAAA,OACpB;AAAA,MACA,cAAc,MAA2C,EAAA;AACvD,QAAA,kBAAA,CAAmB,cAAc,MAAM,CAAA;AAAA;AACzC,KACF;AAAA;AAEJ,CAAC;;;;"}
|