@backstage/backend-defaults 0.9.0-next.1 → 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.
Files changed (24) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/config.d.ts +88 -5
  3. package/dist/discovery.d.ts +105 -36
  4. package/dist/entrypoints/auditor/auditorServiceFactory.cjs.js +33 -7
  5. package/dist/entrypoints/auditor/auditorServiceFactory.cjs.js.map +1 -1
  6. package/dist/entrypoints/auth/DefaultAuthService.cjs.js +2 -1
  7. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
  8. package/dist/entrypoints/auth/helpers.cjs.js +5 -2
  9. package/dist/entrypoints/auth/helpers.cjs.js.map +1 -1
  10. package/dist/entrypoints/discovery/HostDiscovery.cjs.js +128 -53
  11. package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -1
  12. package/dist/entrypoints/discovery/SrvResolvers.cjs.js +148 -0
  13. package/dist/entrypoints/discovery/SrvResolvers.cjs.js.map +1 -0
  14. package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js +7 -3
  15. package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -1
  16. package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js +23 -1
  17. package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js.map +1 -1
  18. package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +4 -0
  19. package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -1
  20. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +3 -14
  21. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
  22. package/dist/package.json.cjs.js +1 -2
  23. package/dist/package.json.cjs.js.map +1 -1
  24. package/package.json +20 -21
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
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
+
44
+ ## 0.9.0-next.2
45
+
46
+ ### Patch Changes
47
+
48
+ - 175528c: Adds `backend.auditor.severityLogLevelMappings` to map severity levels to log levels.
49
+ - Updated dependencies
50
+ - @backstage/backend-app-api@1.2.1
51
+ - @backstage/backend-dev-utils@0.1.5
52
+ - @backstage/backend-plugin-api@1.2.1
53
+ - @backstage/cli-node@0.2.13
54
+ - @backstage/config@1.3.2
55
+ - @backstage/config-loader@1.10.0
56
+ - @backstage/errors@1.2.7
57
+ - @backstage/integration@1.16.3-next.0
58
+ - @backstage/integration-aws-node@0.1.15
59
+ - @backstage/types@1.2.1
60
+ - @backstage/plugin-auth-node@0.6.1
61
+ - @backstage/plugin-events-node@0.4.9
62
+ - @backstage/plugin-permission-node@0.9.0
63
+
3
64
  ## 0.9.0-next.1
4
65
 
5
66
  ### 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
@@ -82,6 +95,32 @@ export interface Config {
82
95
  };
83
96
  };
84
97
 
98
+ /**
99
+ * Options used by the default auditor service.
100
+ */
101
+ auditor?: {
102
+ /**
103
+ * Defines how audit event severity levels are mapped to log levels.
104
+ * This allows you to control the verbosity of audit logs based on the
105
+ * severity of the event. For example, you might want to log 'low' severity
106
+ * events as 'debug' messages, while logging 'critical' events as 'error'
107
+ * messages. Each severity level ('low', 'medium', 'high', 'critical')
108
+ * can be mapped to one of the standard log levels ('debug', 'info', 'warn', 'error').
109
+ *
110
+ * By default, audit events are mapped to log levels as follows:
111
+ * - `low`: `debug`
112
+ * - `medium`: `info`
113
+ * - `high`: `info`
114
+ * - `critical`: `info`
115
+ */
116
+ severityLogLevelMappings?: {
117
+ low?: 'debug' | 'info' | 'warn' | 'error';
118
+ medium?: 'debug' | 'info' | 'warn' | 'error';
119
+ high?: 'debug' | 'info' | 'warn' | 'error';
120
+ critical?: 'debug' | 'info' | 'warn' | 'error';
121
+ };
122
+ };
123
+
85
124
  /**
86
125
  * Options used by the default auth, httpAuth and userInfo services.
87
126
  */
@@ -704,18 +743,62 @@ export interface Config {
704
743
  */
705
744
  discovery?: {
706
745
  /**
707
- * A list of target baseUrls and the associated plugins.
746
+ * A list of target base URLs and their associated plugins.
747
+ *
748
+ * @example
749
+ *
750
+ * ```yaml
751
+ * discovery:
752
+ * endpoints:
753
+ * - target: https://internal.example.com/internal-catalog
754
+ * plugins: [catalog]
755
+ * - target: https://internal.example.com/secure/api/{{pluginId}}
756
+ * plugins: [auth, permission]
757
+ * - target:
758
+ * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/search
759
+ * external: https://example.com/search
760
+ * plugins: [search]
761
+ * ```
708
762
  */
709
763
  endpoints: Array<{
710
764
  /**
711
- * The target base URL to use for the plugin.
765
+ * The target base URL to use for the given set of plugins. Note that this
766
+ * needs to be a full URL including the protocol and path parts that fully
767
+ * address the root of a plugin's API endpoints.
768
+ *
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.
712
774
  *
713
- * Can be either a string or an object with internal and external keys.
714
- * Targets with `{{pluginId}}` or `{{ pluginId }} in the URL will be replaced with the plugin ID.
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)
715
796
  */
716
797
  target: string | { internal?: string; external?: string };
717
798
  /**
718
- * Array of plugins which use the target base URL.
799
+ * Array of plugins which use that target base URL.
800
+ *
801
+ * The special value `*` can be used to match all plugins.
719
802
  */
720
803
  plugins: string[];
721
804
  }>;
@@ -1,5 +1,5 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
- import { DiscoveryService, RootConfigService } from '@backstage/backend-plugin-api';
2
+ import { LoggerService, DiscoveryService, RootConfigService } from '@backstage/backend-plugin-api';
3
3
 
4
4
  /**
5
5
  * Service discovery for inter-plugin communication.
@@ -13,49 +13,118 @@ import { DiscoveryService, RootConfigService } from '@backstage/backend-plugin-a
13
13
  declare const discoveryServiceFactory: _backstage_backend_plugin_api.ServiceFactory<_backstage_backend_plugin_api.DiscoveryService, "plugin", "singleton">;
14
14
 
15
15
  /**
16
- * HostDiscovery is a basic DiscoveryService implementation
17
- * that can handle plugins that are hosted in a single or multiple deployments.
16
+ * A list of target base URLs and their associated plugins.
18
17
  *
19
- * The deployment may be scaled horizontally, as long as the external URL
20
- * is the same for all instances. However, internal URLs will always be
21
- * resolved to the same host, so there won't be any balancing of internal traffic.
18
+ * @public
19
+ */
20
+ interface HostDiscoveryEndpoint {
21
+ /**
22
+ * The target base URL to use for the given set of plugins. Note that this
23
+ * needs to be a full URL _including_ the protocol and path parts that fully
24
+ * address the root of a plugin's API endpoints.
25
+ *
26
+ * @remarks
27
+ *
28
+ * Can be either a single URL or an object where you can explicitly give a
29
+ * dedicated URL for internal (as seen from the backend) and/or external (as
30
+ * seen from the frontend) lookups.
31
+ *
32
+ * The default behavior is to use the backend base URL for external lookups,
33
+ * and a URL formed from the `.listen` and `.https` configs for internal
34
+ * lookups. Adding discovery endpoints as described here overrides one or both
35
+ * of those behaviors for a given set of plugins.
36
+ *
37
+ * URLs can be in the form of a regular HTTP or HTTPS URL if you are using
38
+ * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if
39
+ * you add `+src` to the protocol part then the hostname is treated as an SRV
40
+ * record name and resolved. For example, if you pass in
41
+ * `http+srv://<record>/path` then the record part is resolved into an
42
+ * actual host and port (with random weighted choice as usual when there is
43
+ * more than one match).
44
+ *
45
+ * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them
46
+ * will have them replaced with the plugin ID.
47
+ *
48
+ * Example URLs:
49
+ *
50
+ * - `https://internal.example.com/secure/api/{{ pluginId }}`
51
+ * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`
52
+ * (can only be used in the `internal` key)
53
+ */
54
+ target: string | {
55
+ internal?: string;
56
+ external?: string;
57
+ };
58
+ /**
59
+ * Array of plugins which use that target base URL.
60
+ *
61
+ * The special value `*` can be used to match all plugins.
62
+ */
63
+ plugins: string[];
64
+ }
65
+ /**
66
+ * Options for the {@link HostDiscovery} class.
22
67
  *
23
68
  * @public
24
69
  */
25
- declare class HostDiscovery implements DiscoveryService {
26
- private readonly internalBaseUrl;
27
- private readonly externalBaseUrl;
28
- private readonly discoveryConfig;
70
+ interface HostDiscoveryOptions {
71
+ /**
72
+ * The logger to use.
73
+ */
74
+ logger: LoggerService;
29
75
  /**
30
- * Creates a new HostDiscovery discovery instance by reading
31
- * from the `backend` config section, specifically the `.baseUrl` for
32
- * discovering the external URL, and the `.listen` and `.https` config
33
- * for the internal one.
34
- *
35
- * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.
36
- * eg.
37
- *
38
- * ```yaml
39
- * discovery:
40
- * endpoints:
41
- * - target: https://internal.example.com/internal-catalog
42
- * plugins: [catalog]
43
- * - target: https://internal.example.com/secure/api/{{pluginId}}
44
- * plugins: [auth, permission]
45
- * - target:
46
- * internal: https://internal.example.com/search
47
- * external: https://example.com/search
48
- * plugins: [search]
49
- * ```
50
- *
51
- * The fixed base path is `/api`, meaning the default full internal
52
- * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
76
+ * A default set of endpoints to use.
77
+ *
78
+ * @remarks
79
+ *
80
+ * These endpoints have lower priority than any that are defined in
81
+ * app-config, but higher priority than the fallback ones.
82
+ *
83
+ * This parameter is usedful for example if you want to provide a shared
84
+ * library of core services to your plugin developers, which is set up for the
85
+ * default behaviors in your org. This alleviates the need for replicating any
86
+ * given set of endpoint config in every backend that you deploy.
53
87
  */
54
- static fromConfig(config: RootConfigService): HostDiscovery;
88
+ defaultEndpoints?: HostDiscoveryEndpoint[];
89
+ }
90
+ /**
91
+ * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation
92
+ * that can handle plugins that are hosted in a single or multiple deployments.
93
+ *
94
+ * @public
95
+ * @remarks
96
+ *
97
+ * Configuration is read from the `backend` config section, specifically the
98
+ * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`
99
+ * config for the internal one. The fixed base path for these is `/api`, meaning
100
+ * for example the default full internal path for the `catalog` plugin typically
101
+ * will be `http://localhost:7007/api/catalog`.
102
+ *
103
+ * Those defaults can be overridden by providing a target and corresponding
104
+ * plugins in `discovery.endpoints`, e.g.:
105
+ *
106
+ * ```yaml
107
+ * discovery:
108
+ * endpoints:
109
+ * # Set a static internal and external base URL for a plugin
110
+ * - target: https://internal.example.com/internal-catalog
111
+ * plugins: [catalog]
112
+ * # Sets a dynamic internal and external base URL pattern for two plugins
113
+ * - target: https://internal.example.com/secure/api/{{pluginId}}
114
+ * plugins: [auth, permission]
115
+ * # Sets a dynamic base URL pattern for only the internal resolution for all
116
+ * # other plugins, while leaving the external resolution unaffected
117
+ * - target:
118
+ * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}
119
+ * plugins: [*]
120
+ * ```
121
+ */
122
+ declare class HostDiscovery implements DiscoveryService {
123
+ #private;
124
+ static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions): HostDiscovery;
55
125
  private constructor();
56
- private getTargetFromConfig;
57
126
  getBaseUrl(pluginId: string): Promise<string>;
58
127
  getExternalBaseUrl(pluginId: string): Promise<string>;
59
128
  }
60
129
 
61
- export { HostDiscovery, discoveryServiceFactory };
130
+ export { HostDiscovery, type HostDiscoveryEndpoint, type HostDiscoveryOptions, discoveryServiceFactory };
@@ -2,25 +2,51 @@
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
  var DefaultAuditorService = require('./DefaultAuditorService.cjs.js');
5
+ var zod = require('zod');
6
+ var errors = require('@backstage/errors');
5
7
 
8
+ const CONFIG_ROOT_KEY = "backend.auditor";
9
+ const severityLogLevelMappingsSchema = zod.z.record(
10
+ zod.z.enum(["low", "medium", "high", "critical"]),
11
+ zod.z.enum(["debug", "info", "warn", "error"])
12
+ );
6
13
  const auditorServiceFactory = backendPluginApi.createServiceFactory({
7
14
  service: backendPluginApi.coreServices.auditor,
8
15
  deps: {
16
+ config: backendPluginApi.coreServices.rootConfig,
9
17
  logger: backendPluginApi.coreServices.logger,
10
18
  auth: backendPluginApi.coreServices.auth,
11
19
  httpAuth: backendPluginApi.coreServices.httpAuth,
12
20
  plugin: backendPluginApi.coreServices.pluginMetadata
13
21
  },
14
- factory({ logger, plugin, auth, httpAuth }) {
22
+ factory({ config, logger, plugin, auth, httpAuth }) {
15
23
  const auditLogger = logger.child({ isAuditEvent: true });
24
+ const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY);
25
+ const severityLogLevelMappings = {
26
+ low: auditorConfig?.getOptionalString("severityLogLevelMappings.low") ?? "debug",
27
+ medium: auditorConfig?.getOptionalString("severityLogLevelMappings.medium") ?? "info",
28
+ high: auditorConfig?.getOptionalString("severityLogLevelMappings.high") ?? "info",
29
+ critical: auditorConfig?.getOptionalString("severityLogLevelMappings.critical") ?? "info"
30
+ };
31
+ const res = severityLogLevelMappingsSchema.safeParse(
32
+ severityLogLevelMappings
33
+ );
34
+ if (!res.success) {
35
+ const key = res.error.issues.at(0)?.path.at(0);
36
+ const value = res.error.issues.at(0).received;
37
+ const validKeys = res.error.issues.at(0).options;
38
+ throw new errors.InputError(
39
+ `The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join(
40
+ ", "
41
+ )}'.`
42
+ );
43
+ }
16
44
  return DefaultAuditorService.DefaultAuditorService.create(
17
45
  (event) => {
18
- const message = `${event.plugin}.${event.eventId}`;
19
- if (event.severityLevel === "low") {
20
- auditLogger.debug(message, event);
21
- } else {
22
- auditLogger.info(message, event);
23
- }
46
+ auditLogger[severityLogLevelMappings[event.severityLevel]](
47
+ `${event.plugin}.${event.eventId}`,
48
+ event
49
+ );
24
50
  },
25
51
  { plugin, auth, httpAuth }
26
52
  );
@@ -1 +1 @@
1
- {"version":3,"file":"auditorServiceFactory.cjs.js","sources":["../../../src/entrypoints/auditor/auditorServiceFactory.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} from '@backstage/backend-plugin-api';\nimport { DefaultAuditorService } from './DefaultAuditorService';\n\n/**\n * Plugin-level auditing.\n *\n * See {@link @backstage/code-plugin-api#AuditorService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auditor | the service docs}\n * for more information.\n *\n * @public\n */\nexport const auditorServiceFactory = createServiceFactory({\n service: coreServices.auditor,\n deps: {\n logger: coreServices.logger,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n plugin: coreServices.pluginMetadata,\n },\n factory({ logger, plugin, auth, httpAuth }) {\n const auditLogger = logger.child({ isAuditEvent: true });\n return DefaultAuditorService.create(\n event => {\n const message = `${event.plugin}.${event.eventId}`;\n if (event.severityLevel === 'low') {\n auditLogger.debug(message, event);\n } else {\n auditLogger.info(message, event);\n }\n },\n { plugin, auth, httpAuth },\n );\n },\n});\n"],"names":["createServiceFactory","coreServices","DefaultAuditorService"],"mappings":";;;;;AA+BO,MAAM,wBAAwBA,qCAAqB,CAAA;AAAA,EACxD,SAASC,6BAAa,CAAA,OAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,MAAA;AAAA,IACrB,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,IACvB,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,QAAQ,EAAE,MAAA,EAAQ,MAAQ,EAAA,IAAA,EAAM,UAAY,EAAA;AAC1C,IAAA,MAAM,cAAc,MAAO,CAAA,KAAA,CAAM,EAAE,YAAA,EAAc,MAAM,CAAA;AACvD,IAAA,OAAOC,2CAAsB,CAAA,MAAA;AAAA,MAC3B,CAAS,KAAA,KAAA;AACP,QAAA,MAAM,UAAU,CAAG,EAAA,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI,MAAM,OAAO,CAAA,CAAA;AAChD,QAAI,IAAA,KAAA,CAAM,kBAAkB,KAAO,EAAA;AACjC,UAAY,WAAA,CAAA,KAAA,CAAM,SAAS,KAAK,CAAA;AAAA,SAC3B,MAAA;AACL,UAAY,WAAA,CAAA,IAAA,CAAK,SAAS,KAAK,CAAA;AAAA;AACjC,OACF;AAAA,MACA,EAAE,MAAQ,EAAA,IAAA,EAAM,QAAS;AAAA,KAC3B;AAAA;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"auditorServiceFactory.cjs.js","sources":["../../../src/entrypoints/auditor/auditorServiceFactory.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} from '@backstage/backend-plugin-api';\nimport { DefaultAuditorService } from './DefaultAuditorService';\nimport { z } from 'zod';\nimport { InputError } from '@backstage/errors';\n\nconst CONFIG_ROOT_KEY = 'backend.auditor';\n\nconst severityLogLevelMappingsSchema = z.record(\n z.enum(['low', 'medium', 'high', 'critical']),\n z.enum(['debug', 'info', 'warn', 'error']),\n);\n\n/**\n * Plugin-level auditing.\n *\n * See {@link @backstage/code-plugin-api#AuditorService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auditor | the service docs}\n * for more information.\n *\n * @public\n */\nexport const auditorServiceFactory = createServiceFactory({\n service: coreServices.auditor,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n plugin: coreServices.pluginMetadata,\n },\n factory({ config, logger, plugin, auth, httpAuth }) {\n const auditLogger = logger.child({ isAuditEvent: true });\n const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY);\n\n const severityLogLevelMappings = {\n low:\n auditorConfig?.getOptionalString('severityLogLevelMappings.low') ??\n 'debug',\n medium:\n auditorConfig?.getOptionalString('severityLogLevelMappings.medium') ??\n 'info',\n high:\n auditorConfig?.getOptionalString('severityLogLevelMappings.high') ??\n 'info',\n critical:\n auditorConfig?.getOptionalString('severityLogLevelMappings.critical') ??\n 'info',\n } as Required<z.infer<typeof severityLogLevelMappingsSchema>>;\n\n const res = severityLogLevelMappingsSchema.safeParse(\n severityLogLevelMappings,\n );\n if (!res.success) {\n const key = res.error.issues.at(0)?.path.at(0) as string;\n const value = (\n res.error.issues.at(0) as unknown as Record<PropertyKey, unknown>\n ).received as string;\n const validKeys = (\n res.error.issues.at(0) as unknown as Record<PropertyKey, unknown>\n ).options as string[];\n throw new InputError(\n `The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join(\n ', ',\n )}'.`,\n );\n }\n\n return DefaultAuditorService.create(\n event => {\n auditLogger[severityLogLevelMappings[event.severityLevel]](\n `${event.plugin}.${event.eventId}`,\n event,\n );\n },\n { plugin, auth, httpAuth },\n );\n },\n});\n"],"names":["z","createServiceFactory","coreServices","InputError","DefaultAuditorService"],"mappings":";;;;;;;AAwBA,MAAM,eAAkB,GAAA,iBAAA;AAExB,MAAM,iCAAiCA,KAAE,CAAA,MAAA;AAAA,EACvCA,MAAE,IAAK,CAAA,CAAC,OAAO,QAAU,EAAA,MAAA,EAAQ,UAAU,CAAC,CAAA;AAAA,EAC5CA,MAAE,IAAK,CAAA,CAAC,SAAS,MAAQ,EAAA,MAAA,EAAQ,OAAO,CAAC;AAC3C,CAAA;AAWO,MAAM,wBAAwBC,qCAAqB,CAAA;AAAA,EACxD,SAASC,6BAAa,CAAA,OAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,IACrB,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,IACvB,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,QAAQ,EAAE,MAAA,EAAQ,QAAQ,MAAQ,EAAA,IAAA,EAAM,UAAY,EAAA;AAClD,IAAA,MAAM,cAAc,MAAO,CAAA,KAAA,CAAM,EAAE,YAAA,EAAc,MAAM,CAAA;AACvD,IAAM,MAAA,aAAA,GAAgB,MAAO,CAAA,iBAAA,CAAkB,eAAe,CAAA;AAE9D,IAAA,MAAM,wBAA2B,GAAA;AAAA,MAC/B,GACE,EAAA,aAAA,EAAe,iBAAkB,CAAA,8BAA8B,CAC/D,IAAA,OAAA;AAAA,MACF,MACE,EAAA,aAAA,EAAe,iBAAkB,CAAA,iCAAiC,CAClE,IAAA,MAAA;AAAA,MACF,IACE,EAAA,aAAA,EAAe,iBAAkB,CAAA,+BAA+B,CAChE,IAAA,MAAA;AAAA,MACF,QACE,EAAA,aAAA,EAAe,iBAAkB,CAAA,mCAAmC,CACpE,IAAA;AAAA,KACJ;AAEA,IAAA,MAAM,MAAM,8BAA+B,CAAA,SAAA;AAAA,MACzC;AAAA,KACF;AACA,IAAI,IAAA,CAAC,IAAI,OAAS,EAAA;AAChB,MAAM,MAAA,GAAA,GAAM,IAAI,KAAM,CAAA,MAAA,CAAO,GAAG,CAAC,CAAA,EAAG,IAAK,CAAA,EAAA,CAAG,CAAC,CAAA;AAC7C,MAAA,MAAM,QACJ,GAAI,CAAA,KAAA,CAAM,MAAO,CAAA,EAAA,CAAG,CAAC,CACrB,CAAA,QAAA;AACF,MAAA,MAAM,YACJ,GAAI,CAAA,KAAA,CAAM,MAAO,CAAA,EAAA,CAAG,CAAC,CACrB,CAAA,OAAA;AACF,MAAA,MAAM,IAAIC,iBAAA;AAAA,QACR,CAAyE,sEAAA,EAAA,GAAG,CAAkC,+BAAA,EAAA,KAAK,mDAAmD,SAAU,CAAA,IAAA;AAAA,UAC9K;AAAA,SACD,CAAA,EAAA;AAAA,OACH;AAAA;AAGF,IAAA,OAAOC,2CAAsB,CAAA,MAAA;AAAA,MAC3B,CAAS,KAAA,KAAA;AACP,QAAY,WAAA,CAAA,wBAAA,CAAyB,KAAM,CAAA,aAAa,CAAC,CAAA;AAAA,UACvD,CAAG,EAAA,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI,MAAM,OAAO,CAAA,CAAA;AAAA,UAChC;AAAA,SACF;AAAA,OACF;AAAA,MACA,EAAE,MAAQ,EAAA,IAAA,EAAM,QAAS;AAAA,KAC3B;AAAA;AAEJ,CAAC;;;;"}
@@ -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":";;;;;;AAuCO,MAAM,kBAA0C,CAAA;AAAA,EACrD,YACmB,gBACA,EAAA,kBAAA,EACA,oBACA,EAAA,QAAA,EACA,0BACA,eACjB,EAAA;AANiB,IAAA,IAAA,CAAA,gBAAA,GAAA,gBAAA;AACA,IAAA,IAAA,CAAA,kBAAA,GAAA,kBAAA;AACA,IAAA,IAAA,CAAA,oBAAA,GAAA,oBAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,wBAAA,GAAA,wBAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AAAA;AAChB,EAEH,MAAM,YACJ,CAAA,KAAA,EACA,OAG+B,EAAA;AAC/B,IAAA,MAAM,YAAe,GAAA,MAAM,IAAK,CAAA,kBAAA,CAAmB,YAAY,KAAK,CAAA;AACpE,IAAA,IAAI,YAAc,EAAA;AAChB,MAAA,IAAI,aAAa,gBAAkB,EAAA;AACjC,QAAMA,MAAAA,WAAAA,GAAa,MAAM,IAAA,CAAK,gBAAiB,CAAA,WAAA;AAAA,UAC7C,YAAa,CAAA;AAAA,SACf;AACA,QAAA,IAAI,CAACA,WAAY,EAAA;AACf,UAAA,MAAM,IAAIC,0BAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAO,OAAAC,0CAAA;AAAA,UACLF,WAAW,CAAA,aAAA;AAAA,UACX,YAAa,CAAA,gBAAA;AAAA,UACb,IAAA,CAAK,iBAAkB,CAAA,YAAA,CAAa,gBAAgB;AAAA,SACtD;AAAA;AAEF,MAAO,OAAAG,6CAAA,CAAsC,aAAa,OAAO,CAAA;AAAA;AAGnE,IAAA,MAAM,UAAa,GAAA,MAAM,IAAK,CAAA,gBAAA,CAAiB,YAAY,KAAK,CAAA;AAChE,IAAA,IAAI,UAAY,EAAA;AACd,MAAA,IACE,CAAC,OAAS,EAAA,kBAAA,IACV,KAAK,gBAAiB,CAAA,kBAAA,CAAmB,KAAK,CAC9C,EAAA;AACA,QAAM,MAAA,IAAIF,2BAAoB,4BAA4B,CAAA;AAAA;AAG5D,MAAO,OAAAC,0CAAA;AAAA,QACL,UAAW,CAAA,aAAA;AAAA,QACX,KAAA;AAAA,QACA,IAAA,CAAK,kBAAkB,KAAK;AAAA,OAC9B;AAAA;AAGF,IAAA,MAAM,cAAiB,GAAA,MAAM,IAAK,CAAA,oBAAA,CAAqB,YAAY,KAAK,CAAA;AACxE,IAAA,IAAI,cAAgB,EAAA;AAClB,MAAO,OAAAC,6CAAA;AAAA,QACL,cAAe,CAAA,OAAA;AAAA,QACf,KAAA,CAAA;AAAA,QACA,cAAe,CAAA;AAAA,OACjB;AAAA;AAGF,IAAM,MAAA,IAAIF,2BAAoB,eAAe,CAAA;AAAA;AAC/C,EAEA,WAAA,CACE,aACA,IACqE,EAAA;AACrE,IAAA,MAAM,YAAY,WAAY,CAAA,SAAA;AAI9B,IAAA,IAAI,SAAS,SAAW,EAAA;AACtB,MAAO,OAAA,IAAA;AAAA;AAGT,IAAI,IAAA,SAAA,CAAU,SAAS,IAAM,EAAA;AAC3B,MAAO,OAAA,KAAA;AAAA;AAGT,IAAO,OAAA,IAAA;AAAA;AACT,EAEA,MAAM,kBAEJ,GAAA;AACA,IAAA,OAAOG,0CAAmC,EAAA;AAAA;AAC5C,EAEA,MAAM,wBAEJ,GAAA;AACA,IAAA,OAAOD,6CAAsC,CAAA,CAAA,OAAA,EAAU,IAAK,CAAA,QAAQ,CAAE,CAAA,CAAA;AAAA;AACxE,EAEA,MAAM,sBAAsB,OAGG,EAAA;AAC7B,IAAM,MAAA,EAAE,gBAAmB,GAAA,OAAA;AAC3B,IAAM,MAAA,eAAA,GAAkBE,sCAA+B,CAAA,OAAA,CAAQ,UAAU,CAAA;AACzE,IAAM,MAAA,EAAE,IAAK,EAAA,GAAI,eAAgB,CAAA,SAAA;AAOjC,IAAI,IAAA,IAAA,KAAS,MAAU,IAAA,IAAA,CAAK,wBAA0B,EAAA;AACpD,MAAO,OAAA,EAAE,OAAO,EAAG,EAAA;AAAA;AAKrB,IAAA,QAAQ,IAAM;AAAA;AAAA,MAEZ,KAAK,SAAA;AACH,QAAO,OAAA,IAAA,CAAK,mBAAmB,UAAW,CAAA;AAAA,UACxC,UAAU,IAAK,CAAA,QAAA;AAAA,UACf;AAAA,SACD,CAAA;AAAA,MACH,KAAK,MAAQ,EAAA;AACX,QAAM,MAAA,EAAE,OAAU,GAAA,eAAA;AAClB,QAAA,IAAI,CAAC,KAAO,EAAA;AACV,UAAM,MAAA,IAAI,MAAM,gDAAgD,CAAA;AAAA;AAElE,QAAM,MAAA,UAAA,GAAa,MAAM,IAAA,CAAK,gBAAiB,CAAA,sBAAA;AAAA,UAC7C;AAAA,SACF;AACA,QAAO,OAAA,IAAA,CAAK,mBAAmB,UAAW,CAAA;AAAA,UACxC,UAAU,IAAK,CAAA,QAAA;AAAA,UACf,cAAA;AAAA,UACA,UAAY,EAAA;AAAA,YACV,kBAAkB,UAAW,CAAA,KAAA;AAAA,YAC7B,WAAW,UAAW,CAAA;AAAA;AACxB,SACD,CAAA;AAAA;AACH,MACA;AACE,QAAA,MAAM,IAAIJ,0BAAA;AAAA,UACR,uDAAuD,IAAI,CAAA,CAAA;AAAA,SAC7D;AAAA;AACJ;AACF,EAEA,MAAM,oBACJ,WAC6C,EAAA;AAC7C,IAAA,MAAM,EAAE,KAAA,EAAO,cAAe,EAAA,GAC5BI,uCAA+B,WAAW,CAAA;AAC5C,IAAA,IAAI,CAAC,cAAgB,EAAA;AACnB,MAAA,MAAM,IAAIJ,0BAAA;AAAA,QACR;AAAA,OACF;AAAA;AAGF,IAAO,OAAA,IAAA,CAAK,gBAAiB,CAAA,sBAAA,CAAuB,cAAc,CAAA;AAAA;AACpE,EAEA,MAAM,qBAAyD,GAAA;AAC7D,IAAA,MAAM,EAAE,IAAK,EAAA,GAAI,MAAM,IAAA,CAAK,gBAAgB,QAAS,EAAA;AACrD,IAAO,OAAA,EAAE,MAAM,IAAK,CAAA,GAAA,CAAI,CAAC,EAAE,GAAA,EAAU,KAAA,GAAG,CAAE,EAAA;AAAA;AAC5C,EAEA,kBAAkB,KAAe,EAAA;AAC/B,IAAA,MAAM,EAAE,GAAA,EAAQ,GAAAK,cAAA,CAAU,KAAK,CAAA;AAC/B,IAAA,IAAI,CAAC,GAAK,EAAA;AACR,MAAM,MAAA,IAAIL,2BAAoB,kCAAkC,CAAA;AAAA;AAElE,IAAO,OAAA,IAAI,IAAK,CAAA,GAAA,GAAM,GAAI,CAAA;AAAA;AAE9B;;;;"}
1
+ {"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n constructor(\n private readonly userTokenHandler: UserTokenHandler,\n private readonly pluginTokenHandler: PluginTokenHandler,\n private readonly externalTokenHandler: ExternalTokenHandler,\n private readonly pluginId: string,\n private readonly disableDefaultAuthPolicy: boolean,\n private readonly pluginKeySource: PluginKeySource,\n ) {}\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existance.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":"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"}
@@ -19,7 +19,7 @@ function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
19
19
  }
20
20
  );
21
21
  }
22
- function createCredentialsWithUserPrincipal(sub, token, expiresAt) {
22
+ function createCredentialsWithUserPrincipal(sub, token, expiresAt, actor) {
23
23
  return Object.defineProperty(
24
24
  {
25
25
  $$type: "@backstage/BackstageCredentials",
@@ -27,7 +27,10 @@ function createCredentialsWithUserPrincipal(sub, token, expiresAt) {
27
27
  expiresAt,
28
28
  principal: {
29
29
  type: "user",
30
- userEntityRef: sub
30
+ userEntityRef: sub,
31
+ ...actor && {
32
+ actor: { type: "service", subject: actor }
33
+ }
31
34
  }
32
35
  },
33
36
  "token",
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'service',\n subject: sub,\n accessRestrictions,\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal: {\n type: 'user',\n userEntityRef: sub,\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n return {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'none',\n },\n };\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n"],"names":[],"mappings":";;AAyBgB,SAAA,qCAAA,CACd,GACA,EAAA,KAAA,EACA,kBACyD,EAAA;AACzD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,SAAA;AAAA,QACN,OAAS,EAAA,GAAA;AAAA,QACT;AAAA;AACF,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEgB,SAAA,kCAAA,CACd,GACA,EAAA,KAAA,EACA,SACsD,EAAA;AACtD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAA;AAAA,MACA,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,MAAA;AAAA,QACN,aAAe,EAAA;AAAA;AACjB,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEO,SAAS,kCAA2F,GAAA;AACzG,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,iCAAA;AAAA,IACR,OAAS,EAAA,IAAA;AAAA,IACT,SAAW,EAAA;AAAA,MACT,IAAM,EAAA;AAAA;AACR,GACF;AACF;AAEO,SAAS,+BACd,WAGA,EAAA;AACA,EAAI,IAAA,WAAA,CAAY,WAAW,iCAAmC,EAAA;AAC5D,IAAM,MAAA,IAAI,MAAM,yBAAyB,CAAA;AAAA;AAG3C,EAAA,MAAM,mBAAsB,GAAA,WAAA;AAI5B,EAAI,IAAA,mBAAA,CAAoB,YAAY,IAAM,EAAA;AACxC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,oBAAoB,OAAO,CAAA;AAAA,KAC3D;AAAA;AAGF,EAAO,OAAA,mBAAA;AACT;;;;;;;"}
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'service',\n subject: sub,\n accessRestrictions,\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n actor?: string,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n return Object.defineProperty(\n {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal: {\n type: 'user',\n userEntityRef: sub,\n ...(actor && {\n actor: { type: 'service', subject: actor },\n }),\n },\n },\n 'token',\n {\n enumerable: false,\n configurable: true,\n value: token,\n },\n );\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n return {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'none',\n },\n };\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n"],"names":[],"mappings":";;AAyBgB,SAAA,qCAAA,CACd,GACA,EAAA,KAAA,EACA,kBACyD,EAAA;AACzD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,SAAA;AAAA,QACN,OAAS,EAAA,GAAA;AAAA,QACT;AAAA;AACF,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEO,SAAS,kCACd,CAAA,GAAA,EACA,KACA,EAAA,SAAA,EACA,KACsD,EAAA;AACtD,EAAA,OAAO,MAAO,CAAA,cAAA;AAAA,IACZ;AAAA,MACE,MAAQ,EAAA,iCAAA;AAAA,MACR,OAAS,EAAA,IAAA;AAAA,MACT,SAAA;AAAA,MACA,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,MAAA;AAAA,QACN,aAAe,EAAA,GAAA;AAAA,QACf,GAAI,KAAS,IAAA;AAAA,UACX,KAAO,EAAA,EAAE,IAAM,EAAA,SAAA,EAAW,SAAS,KAAM;AAAA;AAC3C;AACF,KACF;AAAA,IACA,OAAA;AAAA,IACA;AAAA,MACE,UAAY,EAAA,KAAA;AAAA,MACZ,YAAc,EAAA,IAAA;AAAA,MACd,KAAO,EAAA;AAAA;AACT,GACF;AACF;AAEO,SAAS,kCAA2F,GAAA;AACzG,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,iCAAA;AAAA,IACR,OAAS,EAAA,IAAA;AAAA,IACT,SAAW,EAAA;AAAA,MACT,IAAM,EAAA;AAAA;AACR,GACF;AACF;AAEO,SAAS,+BACd,WAGA,EAAA;AACA,EAAI,IAAA,WAAA,CAAY,WAAW,iCAAmC,EAAA;AAC5D,IAAM,MAAA,IAAI,MAAM,yBAAyB,CAAA;AAAA;AAG3C,EAAA,MAAM,mBAAsB,GAAA,WAAA;AAI5B,EAAI,IAAA,mBAAA,CAAoB,YAAY,IAAM,EAAA;AACxC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,oBAAoB,OAAO,CAAA;AAAA,KAC3D;AAAA;AAGF,EAAO,OAAA,mBAAA;AACT;;;;;;;"}