@backstage/backend-app-api 0.7.5 → 0.7.6-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -4
- package/alpha/package.json +1 -1
- package/config.d.ts +121 -0
- package/dist/alpha.cjs.js +1 -2
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/index.cjs.js +970 -1204
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +52 -15
- package/package.json +9 -9
package/dist/index.cjs.js
CHANGED
|
@@ -73,22 +73,16 @@ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
|
73
73
|
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
74
74
|
var trimEnd__default = /*#__PURE__*/_interopDefaultCompat(trimEnd);
|
|
75
75
|
|
|
76
|
-
var __defProp$1 = Object.defineProperty;
|
|
77
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
78
|
-
var __publicField$1 = (obj, key, value) => {
|
|
79
|
-
__defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
80
|
-
return value;
|
|
81
|
-
};
|
|
82
76
|
class ObservableConfigProxy {
|
|
83
77
|
constructor(parent, parentKey) {
|
|
84
78
|
this.parent = parent;
|
|
85
79
|
this.parentKey = parentKey;
|
|
86
|
-
__publicField$1(this, "config", new config.ConfigReader({}));
|
|
87
|
-
__publicField$1(this, "subscribers", []);
|
|
88
80
|
if (parent && !parentKey) {
|
|
89
81
|
throw new Error("parentKey is required if parent is set");
|
|
90
82
|
}
|
|
91
83
|
}
|
|
84
|
+
config = new config.ConfigReader({});
|
|
85
|
+
subscribers = [];
|
|
92
86
|
setConfig(config) {
|
|
93
87
|
if (this.parent) {
|
|
94
88
|
throw new Error("immutable");
|
|
@@ -117,36 +111,31 @@ class ObservableConfigProxy {
|
|
|
117
111
|
};
|
|
118
112
|
}
|
|
119
113
|
select(required) {
|
|
120
|
-
var _a;
|
|
121
114
|
if (this.parent && this.parentKey) {
|
|
122
115
|
if (required) {
|
|
123
116
|
return this.parent.select(true).getConfig(this.parentKey);
|
|
124
117
|
}
|
|
125
|
-
return
|
|
118
|
+
return this.parent.select(false)?.getOptionalConfig(this.parentKey);
|
|
126
119
|
}
|
|
127
120
|
return this.config;
|
|
128
121
|
}
|
|
129
122
|
has(key) {
|
|
130
|
-
|
|
131
|
-
return (_b = (_a = this.select(false)) == null ? void 0 : _a.has(key)) != null ? _b : false;
|
|
123
|
+
return this.select(false)?.has(key) ?? false;
|
|
132
124
|
}
|
|
133
125
|
keys() {
|
|
134
|
-
|
|
135
|
-
return (_b = (_a = this.select(false)) == null ? void 0 : _a.keys()) != null ? _b : [];
|
|
126
|
+
return this.select(false)?.keys() ?? [];
|
|
136
127
|
}
|
|
137
128
|
get(key) {
|
|
138
129
|
return this.select(true).get(key);
|
|
139
130
|
}
|
|
140
131
|
getOptional(key) {
|
|
141
|
-
|
|
142
|
-
return (_a = this.select(false)) == null ? void 0 : _a.getOptional(key);
|
|
132
|
+
return this.select(false)?.getOptional(key);
|
|
143
133
|
}
|
|
144
134
|
getConfig(key) {
|
|
145
135
|
return new ObservableConfigProxy(this, key);
|
|
146
136
|
}
|
|
147
137
|
getOptionalConfig(key) {
|
|
148
|
-
|
|
149
|
-
if ((_a = this.select(false)) == null ? void 0 : _a.has(key)) {
|
|
138
|
+
if (this.select(false)?.has(key)) {
|
|
150
139
|
return new ObservableConfigProxy(this, key);
|
|
151
140
|
}
|
|
152
141
|
return void 0;
|
|
@@ -155,36 +144,31 @@ class ObservableConfigProxy {
|
|
|
155
144
|
return this.select(true).getConfigArray(key);
|
|
156
145
|
}
|
|
157
146
|
getOptionalConfigArray(key) {
|
|
158
|
-
|
|
159
|
-
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalConfigArray(key);
|
|
147
|
+
return this.select(false)?.getOptionalConfigArray(key);
|
|
160
148
|
}
|
|
161
149
|
getNumber(key) {
|
|
162
150
|
return this.select(true).getNumber(key);
|
|
163
151
|
}
|
|
164
152
|
getOptionalNumber(key) {
|
|
165
|
-
|
|
166
|
-
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalNumber(key);
|
|
153
|
+
return this.select(false)?.getOptionalNumber(key);
|
|
167
154
|
}
|
|
168
155
|
getBoolean(key) {
|
|
169
156
|
return this.select(true).getBoolean(key);
|
|
170
157
|
}
|
|
171
158
|
getOptionalBoolean(key) {
|
|
172
|
-
|
|
173
|
-
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalBoolean(key);
|
|
159
|
+
return this.select(false)?.getOptionalBoolean(key);
|
|
174
160
|
}
|
|
175
161
|
getString(key) {
|
|
176
162
|
return this.select(true).getString(key);
|
|
177
163
|
}
|
|
178
164
|
getOptionalString(key) {
|
|
179
|
-
|
|
180
|
-
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalString(key);
|
|
165
|
+
return this.select(false)?.getOptionalString(key);
|
|
181
166
|
}
|
|
182
167
|
getStringArray(key) {
|
|
183
168
|
return this.select(true).getStringArray(key);
|
|
184
169
|
}
|
|
185
170
|
getOptionalStringArray(key) {
|
|
186
|
-
|
|
187
|
-
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalStringArray(key);
|
|
171
|
+
return this.select(false)?.getOptionalStringArray(key);
|
|
188
172
|
}
|
|
189
173
|
}
|
|
190
174
|
|
|
@@ -198,16 +182,14 @@ function isValidUrl(url) {
|
|
|
198
182
|
}
|
|
199
183
|
|
|
200
184
|
async function createConfigSecretEnumerator(options) {
|
|
201
|
-
var _a;
|
|
202
185
|
const { logger, dir = process.cwd() } = options;
|
|
203
186
|
const { packages } = await getPackages.getPackages(dir);
|
|
204
|
-
const schema =
|
|
187
|
+
const schema = options.schema ?? await configLoader.loadConfigSchema({
|
|
205
188
|
dependencies: packages.map((p) => p.packageJson.name)
|
|
206
189
|
});
|
|
207
190
|
return (config) => {
|
|
208
|
-
var _a2;
|
|
209
191
|
const [secretsData] = schema.process(
|
|
210
|
-
[{ data:
|
|
192
|
+
[{ data: config.getOptional() ?? {}, context: "schema-enumerator" }],
|
|
211
193
|
{
|
|
212
194
|
visibility: ["secret"],
|
|
213
195
|
ignoreSchemaErrors: true
|
|
@@ -225,9 +207,8 @@ async function createConfigSecretEnumerator(options) {
|
|
|
225
207
|
};
|
|
226
208
|
}
|
|
227
209
|
async function loadBackendConfig(options) {
|
|
228
|
-
var _a, _b;
|
|
229
210
|
const args = parseArgs__default.default(options.argv);
|
|
230
|
-
const configTargets = [
|
|
211
|
+
const configTargets = [args.config ?? []].flat().map((arg) => isValidUrl(arg) ? { url: arg } : { path: path.resolve(arg) });
|
|
231
212
|
const paths = cliCommon.findPaths(__dirname);
|
|
232
213
|
let currentCancelFunc = void 0;
|
|
233
214
|
const config$1 = new ObservableConfigProxy();
|
|
@@ -235,7 +216,7 @@ async function loadBackendConfig(options) {
|
|
|
235
216
|
configRoot: paths.targetRoot,
|
|
236
217
|
configTargets,
|
|
237
218
|
remote: options.remote,
|
|
238
|
-
watch:
|
|
219
|
+
watch: options.watch ?? true ? {
|
|
239
220
|
onChange(newConfigs) {
|
|
240
221
|
console.info(
|
|
241
222
|
`Reloaded config from ${newConfigs.map((c) => c.context).join(", ")}`
|
|
@@ -277,8 +258,7 @@ function readHttpServerOptions(config) {
|
|
|
277
258
|
};
|
|
278
259
|
}
|
|
279
260
|
function readHttpListenOptions(config) {
|
|
280
|
-
|
|
281
|
-
const listen = config == null ? void 0 : config.getOptional("listen");
|
|
261
|
+
const listen = config?.getOptional("listen");
|
|
282
262
|
if (typeof listen === "string") {
|
|
283
263
|
const parts = String(listen).split(":");
|
|
284
264
|
const port = parseInt(parts[parts.length - 1], 10);
|
|
@@ -294,18 +274,18 @@ function readHttpListenOptions(config) {
|
|
|
294
274
|
`Unable to parse listen address ${listen}, expected <port> or <host>:<port>`
|
|
295
275
|
);
|
|
296
276
|
}
|
|
297
|
-
const host =
|
|
277
|
+
const host = config?.getOptional("listen.host") ?? DEFAULT_HOST;
|
|
298
278
|
if (typeof host !== "string") {
|
|
299
|
-
config
|
|
279
|
+
config?.getOptionalString("listen.host");
|
|
300
280
|
throw new Error("unreachable");
|
|
301
281
|
}
|
|
302
282
|
return {
|
|
303
|
-
port:
|
|
283
|
+
port: config?.getOptionalNumber("listen.port") ?? DEFAULT_PORT,
|
|
304
284
|
host
|
|
305
285
|
};
|
|
306
286
|
}
|
|
307
287
|
function readHttpsOptions(config) {
|
|
308
|
-
const https = config
|
|
288
|
+
const https = config?.getOptional("https");
|
|
309
289
|
if (https === true) {
|
|
310
290
|
const baseUrl = config.getString("baseUrl");
|
|
311
291
|
let hostname;
|
|
@@ -316,7 +296,7 @@ function readHttpsOptions(config) {
|
|
|
316
296
|
}
|
|
317
297
|
return { certificate: { type: "generated", hostname } };
|
|
318
298
|
}
|
|
319
|
-
const cc = config
|
|
299
|
+
const cc = config?.getOptionalConfig("https");
|
|
320
300
|
if (!cc) {
|
|
321
301
|
return void 0;
|
|
322
302
|
}
|
|
@@ -519,7 +499,7 @@ function readHelmetOptions(config) {
|
|
|
519
499
|
};
|
|
520
500
|
}
|
|
521
501
|
function readCspDirectives(config) {
|
|
522
|
-
const cc = config
|
|
502
|
+
const cc = config?.getOptionalConfig("csp");
|
|
523
503
|
if (!cc) {
|
|
524
504
|
return void 0;
|
|
525
505
|
}
|
|
@@ -551,7 +531,7 @@ function applyCspDirectives(directives) {
|
|
|
551
531
|
}
|
|
552
532
|
|
|
553
533
|
function readCorsOptions(config) {
|
|
554
|
-
const cc = config
|
|
534
|
+
const cc = config?.getOptionalConfig("cors");
|
|
555
535
|
if (!cc) {
|
|
556
536
|
return { origin: false };
|
|
557
537
|
}
|
|
@@ -590,7 +570,7 @@ function createCorsOriginMatcher(allowedOriginPatterns) {
|
|
|
590
570
|
return (origin, callback) => {
|
|
591
571
|
return callback(
|
|
592
572
|
null,
|
|
593
|
-
allowedOriginMatchers.some((pattern) => pattern.match(origin
|
|
573
|
+
allowedOriginMatchers.some((pattern) => pattern.match(origin ?? ""))
|
|
594
574
|
);
|
|
595
575
|
};
|
|
596
576
|
}
|
|
@@ -616,37 +596,18 @@ function applyInternalErrorFilter(error, logger) {
|
|
|
616
596
|
return error;
|
|
617
597
|
}
|
|
618
598
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
};
|
|
623
|
-
var __privateGet$c = (obj, member, getter) => {
|
|
624
|
-
__accessCheck$e(obj, member, "read from private field");
|
|
625
|
-
return member.get(obj);
|
|
626
|
-
};
|
|
627
|
-
var __privateAdd$e = (obj, member, value) => {
|
|
628
|
-
if (member.has(obj))
|
|
629
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
630
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
631
|
-
};
|
|
632
|
-
var __privateSet$a = (obj, member, value, setter) => {
|
|
633
|
-
__accessCheck$e(obj, member, "write to private field");
|
|
634
|
-
member.set(obj, value);
|
|
635
|
-
return value;
|
|
636
|
-
};
|
|
637
|
-
var _config, _logger;
|
|
638
|
-
const _MiddlewareFactory = class _MiddlewareFactory {
|
|
639
|
-
constructor(options) {
|
|
640
|
-
__privateAdd$e(this, _config, void 0);
|
|
641
|
-
__privateAdd$e(this, _logger, void 0);
|
|
642
|
-
__privateSet$a(this, _config, options.config);
|
|
643
|
-
__privateSet$a(this, _logger, options.logger);
|
|
644
|
-
}
|
|
599
|
+
class MiddlewareFactory {
|
|
600
|
+
#config;
|
|
601
|
+
#logger;
|
|
645
602
|
/**
|
|
646
603
|
* Creates a new {@link MiddlewareFactory}.
|
|
647
604
|
*/
|
|
648
605
|
static create(options) {
|
|
649
|
-
return new
|
|
606
|
+
return new MiddlewareFactory(options);
|
|
607
|
+
}
|
|
608
|
+
constructor(options) {
|
|
609
|
+
this.#config = options.config;
|
|
610
|
+
this.#logger = options.logger;
|
|
650
611
|
}
|
|
651
612
|
/**
|
|
652
613
|
* Returns a middleware that unconditionally produces a 404 error response.
|
|
@@ -686,7 +647,7 @@ const _MiddlewareFactory = class _MiddlewareFactory {
|
|
|
686
647
|
* @returns An Express request handler
|
|
687
648
|
*/
|
|
688
649
|
logging() {
|
|
689
|
-
const logger =
|
|
650
|
+
const logger = this.#logger.child({
|
|
690
651
|
type: "incomingRequest"
|
|
691
652
|
});
|
|
692
653
|
return morgan__default.default("combined", {
|
|
@@ -710,7 +671,7 @@ const _MiddlewareFactory = class _MiddlewareFactory {
|
|
|
710
671
|
* @returns An Express request handler
|
|
711
672
|
*/
|
|
712
673
|
helmet() {
|
|
713
|
-
return helmet__default.default(readHelmetOptions(
|
|
674
|
+
return helmet__default.default(readHelmetOptions(this.#config.getOptionalConfig("backend")));
|
|
714
675
|
}
|
|
715
676
|
/**
|
|
716
677
|
* Returns a middleware that implements the cors library.
|
|
@@ -725,7 +686,7 @@ const _MiddlewareFactory = class _MiddlewareFactory {
|
|
|
725
686
|
* @returns An Express request handler
|
|
726
687
|
*/
|
|
727
688
|
cors() {
|
|
728
|
-
return cors__default.default(readCorsOptions(
|
|
689
|
+
return cors__default.default(readCorsOptions(this.#config.getOptionalConfig("backend")));
|
|
729
690
|
}
|
|
730
691
|
/**
|
|
731
692
|
* Express middleware to handle errors during request processing.
|
|
@@ -748,9 +709,8 @@ const _MiddlewareFactory = class _MiddlewareFactory {
|
|
|
748
709
|
* @returns An Express error request handler
|
|
749
710
|
*/
|
|
750
711
|
error(options = {}) {
|
|
751
|
-
|
|
752
|
-
const
|
|
753
|
-
const logger = __privateGet$c(this, _logger).child({
|
|
712
|
+
const showStackTraces = options.showStackTraces ?? process.env.NODE_ENV === "development";
|
|
713
|
+
const logger = this.#logger.child({
|
|
754
714
|
type: "errorHandler"
|
|
755
715
|
});
|
|
756
716
|
return (rawError, req, res, next) => {
|
|
@@ -771,10 +731,7 @@ const _MiddlewareFactory = class _MiddlewareFactory {
|
|
|
771
731
|
res.status(statusCode).json(body);
|
|
772
732
|
};
|
|
773
733
|
}
|
|
774
|
-
}
|
|
775
|
-
_config = new WeakMap();
|
|
776
|
-
_logger = new WeakMap();
|
|
777
|
-
let MiddlewareFactory = _MiddlewareFactory;
|
|
734
|
+
}
|
|
778
735
|
function getStatusCode(error) {
|
|
779
736
|
const knownStatusCodeFields = ["statusCode", "status"];
|
|
780
737
|
for (const field of knownStatusCodeFields) {
|
|
@@ -809,51 +766,27 @@ const escapeRegExp = (text) => {
|
|
|
809
766
|
return text.replace(/[.*+?^${}(\)|[\]\\]/g, "\\$&");
|
|
810
767
|
};
|
|
811
768
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
};
|
|
816
|
-
var __privateGet$b = (obj, member, getter) => {
|
|
817
|
-
__accessCheck$d(obj, member, "read from private field");
|
|
818
|
-
return member.get(obj);
|
|
819
|
-
};
|
|
820
|
-
var __privateAdd$d = (obj, member, value) => {
|
|
821
|
-
if (member.has(obj))
|
|
822
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
823
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
824
|
-
};
|
|
825
|
-
var __privateSet$9 = (obj, member, value, setter) => {
|
|
826
|
-
__accessCheck$d(obj, member, "write to private field");
|
|
827
|
-
member.set(obj, value);
|
|
828
|
-
return value;
|
|
829
|
-
};
|
|
830
|
-
var _winston, _addRedactions;
|
|
831
|
-
const _WinstonLogger = class _WinstonLogger {
|
|
832
|
-
constructor(winston, addRedactions) {
|
|
833
|
-
__privateAdd$d(this, _winston, void 0);
|
|
834
|
-
__privateAdd$d(this, _addRedactions, void 0);
|
|
835
|
-
__privateSet$9(this, _winston, winston);
|
|
836
|
-
__privateSet$9(this, _addRedactions, addRedactions);
|
|
837
|
-
}
|
|
769
|
+
class WinstonLogger {
|
|
770
|
+
#winston;
|
|
771
|
+
#addRedactions;
|
|
838
772
|
/**
|
|
839
773
|
* Creates a {@link WinstonLogger} instance.
|
|
840
774
|
*/
|
|
841
775
|
static create(options) {
|
|
842
|
-
|
|
843
|
-
const
|
|
844
|
-
const defaultFormatter = process.env.NODE_ENV === "production" ? winston.format.json() : _WinstonLogger.colorFormat();
|
|
776
|
+
const redacter = WinstonLogger.redacter();
|
|
777
|
+
const defaultFormatter = process.env.NODE_ENV === "production" ? winston.format.json() : WinstonLogger.colorFormat();
|
|
845
778
|
let logger = winston.createLogger({
|
|
846
779
|
level: process.env.LOG_LEVEL || options.level || "info",
|
|
847
780
|
format: winston.format.combine(
|
|
848
|
-
|
|
781
|
+
options.format ?? defaultFormatter,
|
|
849
782
|
redacter.format
|
|
850
783
|
),
|
|
851
|
-
transports:
|
|
784
|
+
transports: options.transports ?? new winston.transports.Console()
|
|
852
785
|
});
|
|
853
786
|
if (options.meta) {
|
|
854
787
|
logger = logger.child(options.meta);
|
|
855
788
|
}
|
|
856
|
-
return new
|
|
789
|
+
return new WinstonLogger(logger, redacter.add);
|
|
857
790
|
}
|
|
858
791
|
/**
|
|
859
792
|
* Creates a winston log formatter for redacting secrets.
|
|
@@ -863,11 +796,10 @@ const _WinstonLogger = class _WinstonLogger {
|
|
|
863
796
|
let redactionPattern = void 0;
|
|
864
797
|
return {
|
|
865
798
|
format: winston.format((obj) => {
|
|
866
|
-
var _a, _b;
|
|
867
799
|
if (!redactionPattern || !obj) {
|
|
868
800
|
return obj;
|
|
869
801
|
}
|
|
870
|
-
obj[tripleBeam.MESSAGE] =
|
|
802
|
+
obj[tripleBeam.MESSAGE] = obj[tripleBeam.MESSAGE]?.replace?.(redactionPattern, "***");
|
|
871
803
|
return obj;
|
|
872
804
|
})(),
|
|
873
805
|
add(newRedactions) {
|
|
@@ -916,227 +848,30 @@ const _WinstonLogger = class _WinstonLogger {
|
|
|
916
848
|
})
|
|
917
849
|
);
|
|
918
850
|
}
|
|
851
|
+
constructor(winston, addRedactions) {
|
|
852
|
+
this.#winston = winston;
|
|
853
|
+
this.#addRedactions = addRedactions;
|
|
854
|
+
}
|
|
919
855
|
error(message, meta) {
|
|
920
|
-
|
|
856
|
+
this.#winston.error(message, meta);
|
|
921
857
|
}
|
|
922
858
|
warn(message, meta) {
|
|
923
|
-
|
|
859
|
+
this.#winston.warn(message, meta);
|
|
924
860
|
}
|
|
925
861
|
info(message, meta) {
|
|
926
|
-
|
|
862
|
+
this.#winston.info(message, meta);
|
|
927
863
|
}
|
|
928
864
|
debug(message, meta) {
|
|
929
|
-
|
|
865
|
+
this.#winston.debug(message, meta);
|
|
930
866
|
}
|
|
931
867
|
child(meta) {
|
|
932
|
-
return new
|
|
868
|
+
return new WinstonLogger(this.#winston.child(meta));
|
|
933
869
|
}
|
|
934
870
|
addRedactions(redactions) {
|
|
935
|
-
|
|
936
|
-
(_a = __privateGet$b(this, _addRedactions)) == null ? void 0 : _a.call(this, redactions);
|
|
937
|
-
}
|
|
938
|
-
};
|
|
939
|
-
_winston = new WeakMap();
|
|
940
|
-
_addRedactions = new WeakMap();
|
|
941
|
-
let WinstonLogger = _WinstonLogger;
|
|
942
|
-
|
|
943
|
-
var __accessCheck$c = (obj, member, msg) => {
|
|
944
|
-
if (!member.has(obj))
|
|
945
|
-
throw TypeError("Cannot " + msg);
|
|
946
|
-
};
|
|
947
|
-
var __privateGet$a = (obj, member, getter) => {
|
|
948
|
-
__accessCheck$c(obj, member, "read from private field");
|
|
949
|
-
return member.get(obj);
|
|
950
|
-
};
|
|
951
|
-
var __privateAdd$c = (obj, member, value) => {
|
|
952
|
-
if (member.has(obj))
|
|
953
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
954
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
955
|
-
};
|
|
956
|
-
var __privateSet$8 = (obj, member, value, setter) => {
|
|
957
|
-
__accessCheck$c(obj, member, "write to private field");
|
|
958
|
-
member.set(obj, value);
|
|
959
|
-
return value;
|
|
960
|
-
};
|
|
961
|
-
var _hasStarted$1, _startupTasks$1, _hasShutdown, _shutdownTasks;
|
|
962
|
-
class BackendLifecycleImpl {
|
|
963
|
-
constructor(logger) {
|
|
964
|
-
this.logger = logger;
|
|
965
|
-
__privateAdd$c(this, _hasStarted$1, false);
|
|
966
|
-
__privateAdd$c(this, _startupTasks$1, []);
|
|
967
|
-
__privateAdd$c(this, _hasShutdown, false);
|
|
968
|
-
__privateAdd$c(this, _shutdownTasks, []);
|
|
969
|
-
}
|
|
970
|
-
addStartupHook(hook, options) {
|
|
971
|
-
if (__privateGet$a(this, _hasStarted$1)) {
|
|
972
|
-
throw new Error("Attempted to add startup hook after startup");
|
|
973
|
-
}
|
|
974
|
-
__privateGet$a(this, _startupTasks$1).push({ hook, options });
|
|
975
|
-
}
|
|
976
|
-
async startup() {
|
|
977
|
-
if (__privateGet$a(this, _hasStarted$1)) {
|
|
978
|
-
return;
|
|
979
|
-
}
|
|
980
|
-
__privateSet$8(this, _hasStarted$1, true);
|
|
981
|
-
this.logger.debug(`Running ${__privateGet$a(this, _startupTasks$1).length} startup tasks...`);
|
|
982
|
-
await Promise.all(
|
|
983
|
-
__privateGet$a(this, _startupTasks$1).map(async ({ hook, options }) => {
|
|
984
|
-
var _a;
|
|
985
|
-
const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : this.logger;
|
|
986
|
-
try {
|
|
987
|
-
await hook();
|
|
988
|
-
logger.debug(`Startup hook succeeded`);
|
|
989
|
-
} catch (error) {
|
|
990
|
-
logger.error(`Startup hook failed, ${error}`);
|
|
991
|
-
}
|
|
992
|
-
})
|
|
993
|
-
);
|
|
994
|
-
}
|
|
995
|
-
addShutdownHook(hook, options) {
|
|
996
|
-
if (__privateGet$a(this, _hasShutdown)) {
|
|
997
|
-
throw new Error("Attempted to add shutdown hook after shutdown");
|
|
998
|
-
}
|
|
999
|
-
__privateGet$a(this, _shutdownTasks).push({ hook, options });
|
|
1000
|
-
}
|
|
1001
|
-
async shutdown() {
|
|
1002
|
-
if (__privateGet$a(this, _hasShutdown)) {
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
__privateSet$8(this, _hasShutdown, true);
|
|
1006
|
-
this.logger.debug(
|
|
1007
|
-
`Running ${__privateGet$a(this, _shutdownTasks).length} shutdown tasks...`
|
|
1008
|
-
);
|
|
1009
|
-
await Promise.all(
|
|
1010
|
-
__privateGet$a(this, _shutdownTasks).map(async ({ hook, options }) => {
|
|
1011
|
-
var _a;
|
|
1012
|
-
const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : this.logger;
|
|
1013
|
-
try {
|
|
1014
|
-
await hook();
|
|
1015
|
-
logger.debug(`Shutdown hook succeeded`);
|
|
1016
|
-
} catch (error) {
|
|
1017
|
-
logger.error(`Shutdown hook failed, ${error}`);
|
|
1018
|
-
}
|
|
1019
|
-
})
|
|
1020
|
-
);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
_hasStarted$1 = new WeakMap();
|
|
1024
|
-
_startupTasks$1 = new WeakMap();
|
|
1025
|
-
_hasShutdown = new WeakMap();
|
|
1026
|
-
_shutdownTasks = new WeakMap();
|
|
1027
|
-
const rootLifecycleServiceFactory = backendPluginApi.createServiceFactory({
|
|
1028
|
-
service: backendPluginApi.coreServices.rootLifecycle,
|
|
1029
|
-
deps: {
|
|
1030
|
-
logger: backendPluginApi.coreServices.rootLogger
|
|
1031
|
-
},
|
|
1032
|
-
async factory({ logger }) {
|
|
1033
|
-
return new BackendLifecycleImpl(logger);
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
var __accessCheck$b = (obj, member, msg) => {
|
|
1038
|
-
if (!member.has(obj))
|
|
1039
|
-
throw TypeError("Cannot " + msg);
|
|
1040
|
-
};
|
|
1041
|
-
var __privateGet$9 = (obj, member, getter) => {
|
|
1042
|
-
__accessCheck$b(obj, member, "read from private field");
|
|
1043
|
-
return member.get(obj);
|
|
1044
|
-
};
|
|
1045
|
-
var __privateAdd$b = (obj, member, value) => {
|
|
1046
|
-
if (member.has(obj))
|
|
1047
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
1048
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
1049
|
-
};
|
|
1050
|
-
var __privateSet$7 = (obj, member, value, setter) => {
|
|
1051
|
-
__accessCheck$b(obj, member, "write to private field");
|
|
1052
|
-
member.set(obj, value);
|
|
1053
|
-
return value;
|
|
1054
|
-
};
|
|
1055
|
-
var _hasStarted, _startupTasks;
|
|
1056
|
-
class BackendPluginLifecycleImpl {
|
|
1057
|
-
constructor(logger, rootLifecycle, pluginMetadata) {
|
|
1058
|
-
this.logger = logger;
|
|
1059
|
-
this.rootLifecycle = rootLifecycle;
|
|
1060
|
-
this.pluginMetadata = pluginMetadata;
|
|
1061
|
-
__privateAdd$b(this, _hasStarted, false);
|
|
1062
|
-
__privateAdd$b(this, _startupTasks, []);
|
|
1063
|
-
}
|
|
1064
|
-
addStartupHook(hook, options) {
|
|
1065
|
-
if (__privateGet$9(this, _hasStarted)) {
|
|
1066
|
-
throw new Error("Attempted to add startup hook after startup");
|
|
1067
|
-
}
|
|
1068
|
-
__privateGet$9(this, _startupTasks).push({ hook, options });
|
|
1069
|
-
}
|
|
1070
|
-
async startup() {
|
|
1071
|
-
if (__privateGet$9(this, _hasStarted)) {
|
|
1072
|
-
return;
|
|
1073
|
-
}
|
|
1074
|
-
__privateSet$7(this, _hasStarted, true);
|
|
1075
|
-
this.logger.debug(
|
|
1076
|
-
`Running ${__privateGet$9(this, _startupTasks).length} plugin startup tasks...`
|
|
1077
|
-
);
|
|
1078
|
-
await Promise.all(
|
|
1079
|
-
__privateGet$9(this, _startupTasks).map(async ({ hook, options }) => {
|
|
1080
|
-
var _a;
|
|
1081
|
-
const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : this.logger;
|
|
1082
|
-
try {
|
|
1083
|
-
await hook();
|
|
1084
|
-
logger.debug(`Plugin startup hook succeeded`);
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
logger.error(`Plugin startup hook failed, ${error}`);
|
|
1087
|
-
}
|
|
1088
|
-
})
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
addShutdownHook(hook, options) {
|
|
1092
|
-
var _a, _b;
|
|
1093
|
-
const plugin = this.pluginMetadata.getId();
|
|
1094
|
-
this.rootLifecycle.addShutdownHook(hook, {
|
|
1095
|
-
logger: (_b = (_a = options == null ? void 0 : options.logger) == null ? void 0 : _a.child({ plugin })) != null ? _b : this.logger
|
|
1096
|
-
});
|
|
871
|
+
this.#addRedactions?.(redactions);
|
|
1097
872
|
}
|
|
1098
873
|
}
|
|
1099
|
-
_hasStarted = new WeakMap();
|
|
1100
|
-
_startupTasks = new WeakMap();
|
|
1101
|
-
const lifecycleServiceFactory = backendPluginApi.createServiceFactory({
|
|
1102
|
-
service: backendPluginApi.coreServices.lifecycle,
|
|
1103
|
-
deps: {
|
|
1104
|
-
logger: backendPluginApi.coreServices.logger,
|
|
1105
|
-
rootLifecycle: backendPluginApi.coreServices.rootLifecycle,
|
|
1106
|
-
pluginMetadata: backendPluginApi.coreServices.pluginMetadata
|
|
1107
|
-
},
|
|
1108
|
-
async factory({ rootLifecycle, logger, pluginMetadata }) {
|
|
1109
|
-
return new BackendPluginLifecycleImpl(
|
|
1110
|
-
logger,
|
|
1111
|
-
rootLifecycle,
|
|
1112
|
-
pluginMetadata
|
|
1113
|
-
);
|
|
1114
|
-
}
|
|
1115
|
-
});
|
|
1116
874
|
|
|
1117
|
-
var __accessCheck$a = (obj, member, msg) => {
|
|
1118
|
-
if (!member.has(obj))
|
|
1119
|
-
throw TypeError("Cannot " + msg);
|
|
1120
|
-
};
|
|
1121
|
-
var __privateGet$8 = (obj, member, getter) => {
|
|
1122
|
-
__accessCheck$a(obj, member, "read from private field");
|
|
1123
|
-
return member.get(obj);
|
|
1124
|
-
};
|
|
1125
|
-
var __privateAdd$a = (obj, member, value) => {
|
|
1126
|
-
if (member.has(obj))
|
|
1127
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
1128
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
1129
|
-
};
|
|
1130
|
-
var __privateSet$6 = (obj, member, value, setter) => {
|
|
1131
|
-
__accessCheck$a(obj, member, "write to private field");
|
|
1132
|
-
member.set(obj, value);
|
|
1133
|
-
return value;
|
|
1134
|
-
};
|
|
1135
|
-
var __privateMethod$7 = (obj, member, method) => {
|
|
1136
|
-
__accessCheck$a(obj, member, "access private method");
|
|
1137
|
-
return method;
|
|
1138
|
-
};
|
|
1139
|
-
var _nodeIds, _cycleKeys, _getCycleKey, getCycleKey_fn, _nodes, _allProvided;
|
|
1140
875
|
class Node {
|
|
1141
876
|
constructor(value, consumes, provides) {
|
|
1142
877
|
this.value = value;
|
|
@@ -1151,45 +886,29 @@ class Node {
|
|
|
1151
886
|
);
|
|
1152
887
|
}
|
|
1153
888
|
}
|
|
1154
|
-
|
|
1155
|
-
constructor(nodes) {
|
|
1156
|
-
__privateAdd$a(this, _getCycleKey);
|
|
1157
|
-
__privateAdd$a(this, _nodeIds, void 0);
|
|
1158
|
-
__privateAdd$a(this, _cycleKeys, void 0);
|
|
1159
|
-
__privateSet$6(this, _nodeIds, new Map(nodes.map((n, i) => [n.value, i])));
|
|
1160
|
-
__privateSet$6(this, _cycleKeys, /* @__PURE__ */ new Set());
|
|
1161
|
-
}
|
|
889
|
+
class CycleKeySet {
|
|
1162
890
|
static from(nodes) {
|
|
1163
|
-
return new
|
|
891
|
+
return new CycleKeySet(nodes);
|
|
892
|
+
}
|
|
893
|
+
#nodeIds;
|
|
894
|
+
#cycleKeys;
|
|
895
|
+
constructor(nodes) {
|
|
896
|
+
this.#nodeIds = new Map(nodes.map((n, i) => [n.value, i]));
|
|
897
|
+
this.#cycleKeys = /* @__PURE__ */ new Set();
|
|
1164
898
|
}
|
|
1165
899
|
tryAdd(path) {
|
|
1166
|
-
const cycleKey =
|
|
1167
|
-
if (
|
|
900
|
+
const cycleKey = this.#getCycleKey(path);
|
|
901
|
+
if (this.#cycleKeys.has(cycleKey)) {
|
|
1168
902
|
return false;
|
|
1169
903
|
}
|
|
1170
|
-
|
|
904
|
+
this.#cycleKeys.add(cycleKey);
|
|
1171
905
|
return true;
|
|
1172
906
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
_cycleKeys = new WeakMap();
|
|
1176
|
-
_getCycleKey = new WeakSet();
|
|
1177
|
-
getCycleKey_fn = function(path) {
|
|
1178
|
-
return path.map((n) => __privateGet$8(this, _nodeIds).get(n)).sort().join(",");
|
|
1179
|
-
};
|
|
1180
|
-
let CycleKeySet = _CycleKeySet;
|
|
1181
|
-
const _DependencyGraph = class _DependencyGraph {
|
|
1182
|
-
constructor(nodes) {
|
|
1183
|
-
__privateAdd$a(this, _nodes, void 0);
|
|
1184
|
-
__privateAdd$a(this, _allProvided, void 0);
|
|
1185
|
-
__privateSet$6(this, _nodes, nodes);
|
|
1186
|
-
__privateSet$6(this, _allProvided, /* @__PURE__ */ new Set());
|
|
1187
|
-
for (const node of __privateGet$8(this, _nodes).values()) {
|
|
1188
|
-
for (const produced of node.provides) {
|
|
1189
|
-
__privateGet$8(this, _allProvided).add(produced);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
907
|
+
#getCycleKey(path) {
|
|
908
|
+
return path.map((n) => this.#nodeIds.get(n)).sort().join(",");
|
|
1192
909
|
}
|
|
910
|
+
}
|
|
911
|
+
class DependencyGraph {
|
|
1193
912
|
static fromMap(nodes) {
|
|
1194
913
|
return this.fromIterable(
|
|
1195
914
|
Object.entries(nodes).map(([key, node]) => ({
|
|
@@ -1203,16 +922,27 @@ const _DependencyGraph = class _DependencyGraph {
|
|
|
1203
922
|
for (const nodeInput of nodeInputs) {
|
|
1204
923
|
nodes.push(Node.from(nodeInput));
|
|
1205
924
|
}
|
|
1206
|
-
return new
|
|
925
|
+
return new DependencyGraph(nodes);
|
|
926
|
+
}
|
|
927
|
+
#nodes;
|
|
928
|
+
#allProvided;
|
|
929
|
+
constructor(nodes) {
|
|
930
|
+
this.#nodes = nodes;
|
|
931
|
+
this.#allProvided = /* @__PURE__ */ new Set();
|
|
932
|
+
for (const node of this.#nodes.values()) {
|
|
933
|
+
for (const produced of node.provides) {
|
|
934
|
+
this.#allProvided.add(produced);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
1207
937
|
}
|
|
1208
938
|
/**
|
|
1209
939
|
* Find all nodes that consume dependencies that are not provided by any other node.
|
|
1210
940
|
*/
|
|
1211
941
|
findUnsatisfiedDeps() {
|
|
1212
942
|
const unsatisfiedDependencies = [];
|
|
1213
|
-
for (const node of
|
|
943
|
+
for (const node of this.#nodes.values()) {
|
|
1214
944
|
const unsatisfied = Array.from(node.consumes).filter(
|
|
1215
|
-
(id) => !
|
|
945
|
+
(id) => !this.#allProvided.has(id)
|
|
1216
946
|
);
|
|
1217
947
|
if (unsatisfied.length > 0) {
|
|
1218
948
|
unsatisfiedDependencies.push({ value: node.value, unsatisfied });
|
|
@@ -1232,8 +962,8 @@ const _DependencyGraph = class _DependencyGraph {
|
|
|
1232
962
|
* form a cycle, with the same node as the first and last element of the array.
|
|
1233
963
|
*/
|
|
1234
964
|
*detectCircularDependencies() {
|
|
1235
|
-
const cycleKeys = CycleKeySet.from(
|
|
1236
|
-
for (const startNode of
|
|
965
|
+
const cycleKeys = CycleKeySet.from(this.#nodes);
|
|
966
|
+
for (const startNode of this.#nodes) {
|
|
1237
967
|
const visited = /* @__PURE__ */ new Set();
|
|
1238
968
|
const stack = new Array([
|
|
1239
969
|
startNode,
|
|
@@ -1246,7 +976,7 @@ const _DependencyGraph = class _DependencyGraph {
|
|
|
1246
976
|
}
|
|
1247
977
|
visited.add(node);
|
|
1248
978
|
for (const consumed of node.consumes) {
|
|
1249
|
-
const providerNodes =
|
|
979
|
+
const providerNodes = this.#nodes.filter(
|
|
1250
980
|
(other) => other.provides.has(consumed)
|
|
1251
981
|
);
|
|
1252
982
|
for (const provider of providerNodes) {
|
|
@@ -1275,9 +1005,9 @@ const _DependencyGraph = class _DependencyGraph {
|
|
|
1275
1005
|
* Dependencies of nodes that are not produced by any other nodes will be ignored.
|
|
1276
1006
|
*/
|
|
1277
1007
|
async parallelTopologicalTraversal(fn) {
|
|
1278
|
-
const allProvided =
|
|
1008
|
+
const allProvided = this.#allProvided;
|
|
1279
1009
|
const producedSoFar = /* @__PURE__ */ new Set();
|
|
1280
|
-
const waiting = new Set(
|
|
1010
|
+
const waiting = new Set(this.#nodes.values());
|
|
1281
1011
|
const visited = /* @__PURE__ */ new Set();
|
|
1282
1012
|
const results = new Array();
|
|
1283
1013
|
let inFlight = 0;
|
|
@@ -1318,34 +1048,8 @@ const _DependencyGraph = class _DependencyGraph {
|
|
|
1318
1048
|
await processMoreNodes();
|
|
1319
1049
|
return results;
|
|
1320
1050
|
}
|
|
1321
|
-
}
|
|
1322
|
-
_nodes = new WeakMap();
|
|
1323
|
-
_allProvided = new WeakMap();
|
|
1324
|
-
let DependencyGraph = _DependencyGraph;
|
|
1051
|
+
}
|
|
1325
1052
|
|
|
1326
|
-
var __accessCheck$9 = (obj, member, msg) => {
|
|
1327
|
-
if (!member.has(obj))
|
|
1328
|
-
throw TypeError("Cannot " + msg);
|
|
1329
|
-
};
|
|
1330
|
-
var __privateGet$7 = (obj, member, getter) => {
|
|
1331
|
-
__accessCheck$9(obj, member, "read from private field");
|
|
1332
|
-
return member.get(obj);
|
|
1333
|
-
};
|
|
1334
|
-
var __privateAdd$9 = (obj, member, value) => {
|
|
1335
|
-
if (member.has(obj))
|
|
1336
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
1337
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
1338
|
-
};
|
|
1339
|
-
var __privateSet$5 = (obj, member, value, setter) => {
|
|
1340
|
-
__accessCheck$9(obj, member, "write to private field");
|
|
1341
|
-
member.set(obj, value);
|
|
1342
|
-
return value;
|
|
1343
|
-
};
|
|
1344
|
-
var __privateMethod$6 = (obj, member, method) => {
|
|
1345
|
-
__accessCheck$9(obj, member, "access private method");
|
|
1346
|
-
return method;
|
|
1347
|
-
};
|
|
1348
|
-
var _providedFactories, _loadedDefaultFactories, _implementations, _rootServiceImplementations, _addedFactoryIds, _instantiatedFactories, _resolveFactory, resolveFactory_fn, _checkForMissingDeps, checkForMissingDeps_fn;
|
|
1349
1053
|
function toInternalServiceFactory(factory) {
|
|
1350
1054
|
const f = factory;
|
|
1351
1055
|
if (f.$$type !== "@backstage/BackendFeature") {
|
|
@@ -1360,33 +1064,77 @@ const pluginMetadataServiceFactory = backendPluginApi.createServiceFactory(
|
|
|
1360
1064
|
(options) => ({
|
|
1361
1065
|
service: backendPluginApi.coreServices.pluginMetadata,
|
|
1362
1066
|
deps: {},
|
|
1363
|
-
factory: async () => ({ getId: () => options
|
|
1067
|
+
factory: async () => ({ getId: () => options?.pluginId })
|
|
1364
1068
|
})
|
|
1365
1069
|
);
|
|
1366
|
-
|
|
1367
|
-
constructor(factories) {
|
|
1368
|
-
__privateAdd$9(this, _resolveFactory);
|
|
1369
|
-
__privateAdd$9(this, _checkForMissingDeps);
|
|
1370
|
-
__privateAdd$9(this, _providedFactories, void 0);
|
|
1371
|
-
__privateAdd$9(this, _loadedDefaultFactories, void 0);
|
|
1372
|
-
__privateAdd$9(this, _implementations, void 0);
|
|
1373
|
-
__privateAdd$9(this, _rootServiceImplementations, /* @__PURE__ */ new Map());
|
|
1374
|
-
__privateAdd$9(this, _addedFactoryIds, /* @__PURE__ */ new Set());
|
|
1375
|
-
__privateAdd$9(this, _instantiatedFactories, /* @__PURE__ */ new Set());
|
|
1376
|
-
__privateSet$5(this, _providedFactories, new Map(
|
|
1377
|
-
factories.map((sf) => [sf.service.id, toInternalServiceFactory(sf)])
|
|
1378
|
-
));
|
|
1379
|
-
__privateSet$5(this, _loadedDefaultFactories, /* @__PURE__ */ new Map());
|
|
1380
|
-
__privateSet$5(this, _implementations, /* @__PURE__ */ new Map());
|
|
1381
|
-
}
|
|
1070
|
+
class ServiceRegistry {
|
|
1382
1071
|
static create(factories) {
|
|
1383
|
-
const registry = new
|
|
1072
|
+
const registry = new ServiceRegistry(factories);
|
|
1384
1073
|
registry.checkForCircularDeps();
|
|
1385
1074
|
return registry;
|
|
1386
1075
|
}
|
|
1076
|
+
#providedFactories;
|
|
1077
|
+
#loadedDefaultFactories;
|
|
1078
|
+
#implementations;
|
|
1079
|
+
#rootServiceImplementations = /* @__PURE__ */ new Map();
|
|
1080
|
+
#addedFactoryIds = /* @__PURE__ */ new Set();
|
|
1081
|
+
#instantiatedFactories = /* @__PURE__ */ new Set();
|
|
1082
|
+
constructor(factories) {
|
|
1083
|
+
this.#providedFactories = new Map(
|
|
1084
|
+
factories.map((sf) => [sf.service.id, toInternalServiceFactory(sf)])
|
|
1085
|
+
);
|
|
1086
|
+
this.#loadedDefaultFactories = /* @__PURE__ */ new Map();
|
|
1087
|
+
this.#implementations = /* @__PURE__ */ new Map();
|
|
1088
|
+
}
|
|
1089
|
+
#resolveFactory(ref, pluginId) {
|
|
1090
|
+
if (ref.id === backendPluginApi.coreServices.pluginMetadata.id) {
|
|
1091
|
+
return Promise.resolve(
|
|
1092
|
+
toInternalServiceFactory(pluginMetadataServiceFactory({ pluginId }))
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
let resolvedFactory = this.#providedFactories.get(ref.id);
|
|
1096
|
+
const { __defaultFactory: defaultFactory } = ref;
|
|
1097
|
+
if (!resolvedFactory && !defaultFactory) {
|
|
1098
|
+
return void 0;
|
|
1099
|
+
}
|
|
1100
|
+
if (!resolvedFactory) {
|
|
1101
|
+
let loadedFactory = this.#loadedDefaultFactories.get(defaultFactory);
|
|
1102
|
+
if (!loadedFactory) {
|
|
1103
|
+
loadedFactory = Promise.resolve().then(() => defaultFactory(ref)).then(
|
|
1104
|
+
(f) => toInternalServiceFactory(typeof f === "function" ? f() : f)
|
|
1105
|
+
);
|
|
1106
|
+
this.#loadedDefaultFactories.set(defaultFactory, loadedFactory);
|
|
1107
|
+
}
|
|
1108
|
+
resolvedFactory = loadedFactory.catch((error) => {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
`Failed to instantiate service '${ref.id}' because the default factory loader threw an error, ${errors.stringifyError(
|
|
1111
|
+
error
|
|
1112
|
+
)}`
|
|
1113
|
+
);
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
return Promise.resolve(resolvedFactory);
|
|
1117
|
+
}
|
|
1118
|
+
#checkForMissingDeps(factory, pluginId) {
|
|
1119
|
+
const missingDeps = Object.values(factory.deps).filter((ref) => {
|
|
1120
|
+
if (ref.id === backendPluginApi.coreServices.pluginMetadata.id) {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
if (this.#providedFactories.get(ref.id)) {
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
1126
|
+
return !ref.__defaultFactory;
|
|
1127
|
+
});
|
|
1128
|
+
if (missingDeps.length) {
|
|
1129
|
+
const missing = missingDeps.map((r) => `'${r.id}'`).join(", ");
|
|
1130
|
+
throw new Error(
|
|
1131
|
+
`Failed to instantiate service '${factory.service.id}' for '${pluginId}' because the following dependent services are missing: ${missing}`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1387
1135
|
checkForCircularDeps() {
|
|
1388
1136
|
const graph = DependencyGraph.fromIterable(
|
|
1389
|
-
Array.from(
|
|
1137
|
+
Array.from(this.#providedFactories).map(
|
|
1390
1138
|
([serviceId, serviceFactory]) => ({
|
|
1391
1139
|
value: serviceId,
|
|
1392
1140
|
provides: [serviceId],
|
|
@@ -1408,21 +1156,21 @@ const _ServiceRegistry = class _ServiceRegistry {
|
|
|
1408
1156
|
`The ${backendPluginApi.coreServices.pluginMetadata.id} service cannot be overridden`
|
|
1409
1157
|
);
|
|
1410
1158
|
}
|
|
1411
|
-
if (
|
|
1159
|
+
if (this.#addedFactoryIds.has(factoryId)) {
|
|
1412
1160
|
throw new Error(
|
|
1413
1161
|
`Duplicate service implementations provided for ${factoryId}`
|
|
1414
1162
|
);
|
|
1415
1163
|
}
|
|
1416
|
-
if (
|
|
1164
|
+
if (this.#instantiatedFactories.has(factoryId)) {
|
|
1417
1165
|
throw new Error(
|
|
1418
1166
|
`Unable to set service factory with id ${factoryId}, service has already been instantiated`
|
|
1419
1167
|
);
|
|
1420
1168
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1169
|
+
this.#addedFactoryIds.add(factoryId);
|
|
1170
|
+
this.#providedFactories.set(factoryId, toInternalServiceFactory(factory));
|
|
1423
1171
|
}
|
|
1424
1172
|
async initializeEagerServicesWithScope(scope, pluginId = "root") {
|
|
1425
|
-
for (const factory of
|
|
1173
|
+
for (const factory of this.#providedFactories.values()) {
|
|
1426
1174
|
if (factory.service.scope === scope) {
|
|
1427
1175
|
if (scope === "root" && factory.initialization !== "lazy") {
|
|
1428
1176
|
await this.get(factory.service, pluginId);
|
|
@@ -1433,13 +1181,12 @@ const _ServiceRegistry = class _ServiceRegistry {
|
|
|
1433
1181
|
}
|
|
1434
1182
|
}
|
|
1435
1183
|
get(ref, pluginId) {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
return (_a = __privateMethod$6(this, _resolveFactory, resolveFactory_fn).call(this, ref, pluginId)) == null ? void 0 : _a.then((factory) => {
|
|
1184
|
+
this.#instantiatedFactories.add(ref.id);
|
|
1185
|
+
return this.#resolveFactory(ref, pluginId)?.then((factory) => {
|
|
1439
1186
|
if (factory.service.scope === "root") {
|
|
1440
|
-
let existing =
|
|
1187
|
+
let existing = this.#rootServiceImplementations.get(factory);
|
|
1441
1188
|
if (!existing) {
|
|
1442
|
-
|
|
1189
|
+
this.#checkForMissingDeps(factory, pluginId);
|
|
1443
1190
|
const rootDeps = new Array();
|
|
1444
1191
|
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
|
1445
1192
|
if (serviceRef.scope !== "root") {
|
|
@@ -1453,13 +1200,13 @@ const _ServiceRegistry = class _ServiceRegistry {
|
|
|
1453
1200
|
existing = Promise.all(rootDeps).then(
|
|
1454
1201
|
(entries) => factory.factory(Object.fromEntries(entries), void 0)
|
|
1455
1202
|
);
|
|
1456
|
-
|
|
1203
|
+
this.#rootServiceImplementations.set(factory, existing);
|
|
1457
1204
|
}
|
|
1458
1205
|
return existing;
|
|
1459
1206
|
}
|
|
1460
|
-
let implementation =
|
|
1207
|
+
let implementation = this.#implementations.get(factory);
|
|
1461
1208
|
if (!implementation) {
|
|
1462
|
-
|
|
1209
|
+
this.#checkForMissingDeps(factory, pluginId);
|
|
1463
1210
|
const rootDeps = new Array();
|
|
1464
1211
|
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
|
1465
1212
|
if (serviceRef.scope === "root") {
|
|
@@ -1469,10 +1216,7 @@ const _ServiceRegistry = class _ServiceRegistry {
|
|
|
1469
1216
|
}
|
|
1470
1217
|
implementation = {
|
|
1471
1218
|
context: Promise.all(rootDeps).then(
|
|
1472
|
-
(entries) =>
|
|
1473
|
-
var _a2;
|
|
1474
|
-
return (_a2 = factory.createRootContext) == null ? void 0 : _a2.call(factory, Object.fromEntries(entries));
|
|
1475
|
-
}
|
|
1219
|
+
(entries) => factory.createRootContext?.(Object.fromEntries(entries))
|
|
1476
1220
|
).catch((error) => {
|
|
1477
1221
|
const cause = errors.stringifyError(error);
|
|
1478
1222
|
throw new Error(
|
|
@@ -1481,7 +1225,7 @@ const _ServiceRegistry = class _ServiceRegistry {
|
|
|
1481
1225
|
}),
|
|
1482
1226
|
byPlugin: /* @__PURE__ */ new Map()
|
|
1483
1227
|
};
|
|
1484
|
-
|
|
1228
|
+
this.#implementations.set(factory, implementation);
|
|
1485
1229
|
}
|
|
1486
1230
|
let result = implementation.byPlugin.get(pluginId);
|
|
1487
1231
|
if (!result) {
|
|
@@ -1505,72 +1249,17 @@ const _ServiceRegistry = class _ServiceRegistry {
|
|
|
1505
1249
|
return result;
|
|
1506
1250
|
});
|
|
1507
1251
|
}
|
|
1508
|
-
}
|
|
1509
|
-
_providedFactories = new WeakMap();
|
|
1510
|
-
_loadedDefaultFactories = new WeakMap();
|
|
1511
|
-
_implementations = new WeakMap();
|
|
1512
|
-
_rootServiceImplementations = new WeakMap();
|
|
1513
|
-
_addedFactoryIds = new WeakMap();
|
|
1514
|
-
_instantiatedFactories = new WeakMap();
|
|
1515
|
-
_resolveFactory = new WeakSet();
|
|
1516
|
-
resolveFactory_fn = function(ref, pluginId) {
|
|
1517
|
-
if (ref.id === backendPluginApi.coreServices.pluginMetadata.id) {
|
|
1518
|
-
return Promise.resolve(
|
|
1519
|
-
toInternalServiceFactory(pluginMetadataServiceFactory({ pluginId }))
|
|
1520
|
-
);
|
|
1521
|
-
}
|
|
1522
|
-
let resolvedFactory = __privateGet$7(this, _providedFactories).get(ref.id);
|
|
1523
|
-
const { __defaultFactory: defaultFactory } = ref;
|
|
1524
|
-
if (!resolvedFactory && !defaultFactory) {
|
|
1525
|
-
return void 0;
|
|
1526
|
-
}
|
|
1527
|
-
if (!resolvedFactory) {
|
|
1528
|
-
let loadedFactory = __privateGet$7(this, _loadedDefaultFactories).get(defaultFactory);
|
|
1529
|
-
if (!loadedFactory) {
|
|
1530
|
-
loadedFactory = Promise.resolve().then(() => defaultFactory(ref)).then(
|
|
1531
|
-
(f) => toInternalServiceFactory(typeof f === "function" ? f() : f)
|
|
1532
|
-
);
|
|
1533
|
-
__privateGet$7(this, _loadedDefaultFactories).set(defaultFactory, loadedFactory);
|
|
1534
|
-
}
|
|
1535
|
-
resolvedFactory = loadedFactory.catch((error) => {
|
|
1536
|
-
throw new Error(
|
|
1537
|
-
`Failed to instantiate service '${ref.id}' because the default factory loader threw an error, ${errors.stringifyError(
|
|
1538
|
-
error
|
|
1539
|
-
)}`
|
|
1540
|
-
);
|
|
1541
|
-
});
|
|
1542
|
-
}
|
|
1543
|
-
return Promise.resolve(resolvedFactory);
|
|
1544
|
-
};
|
|
1545
|
-
_checkForMissingDeps = new WeakSet();
|
|
1546
|
-
checkForMissingDeps_fn = function(factory, pluginId) {
|
|
1547
|
-
const missingDeps = Object.values(factory.deps).filter((ref) => {
|
|
1548
|
-
if (ref.id === backendPluginApi.coreServices.pluginMetadata.id) {
|
|
1549
|
-
return false;
|
|
1550
|
-
}
|
|
1551
|
-
if (__privateGet$7(this, _providedFactories).get(ref.id)) {
|
|
1552
|
-
return false;
|
|
1553
|
-
}
|
|
1554
|
-
return !ref.__defaultFactory;
|
|
1555
|
-
});
|
|
1556
|
-
if (missingDeps.length) {
|
|
1557
|
-
const missing = missingDeps.map((r) => `'${r.id}'`).join(", ");
|
|
1558
|
-
throw new Error(
|
|
1559
|
-
`Failed to instantiate service '${factory.service.id}' for '${pluginId}' because the following dependent services are missing: ${missing}`
|
|
1560
|
-
);
|
|
1561
|
-
}
|
|
1562
|
-
};
|
|
1563
|
-
let ServiceRegistry = _ServiceRegistry;
|
|
1252
|
+
}
|
|
1564
1253
|
|
|
1565
1254
|
const LOGGER_INTERVAL_MAX = 6e4;
|
|
1566
1255
|
function joinIds(ids) {
|
|
1567
1256
|
return [...ids].map((id) => `'${id}'`).join(", ");
|
|
1568
1257
|
}
|
|
1569
1258
|
function createInitializationLogger(pluginIds, rootLogger) {
|
|
1570
|
-
const logger = rootLogger
|
|
1259
|
+
const logger = rootLogger?.child({ type: "initialization" });
|
|
1571
1260
|
const starting = new Set(pluginIds);
|
|
1572
1261
|
const started = /* @__PURE__ */ new Set();
|
|
1573
|
-
logger
|
|
1262
|
+
logger?.info(`Plugin initialization started: ${joinIds(pluginIds)}`);
|
|
1574
1263
|
const getInitStatus = () => {
|
|
1575
1264
|
let status = "";
|
|
1576
1265
|
if (started.size > 0) {
|
|
@@ -1586,7 +1275,7 @@ function createInitializationLogger(pluginIds, rootLogger) {
|
|
|
1586
1275
|
let prevInterval = 0;
|
|
1587
1276
|
let timeout;
|
|
1588
1277
|
const onTimeout = () => {
|
|
1589
|
-
logger
|
|
1278
|
+
logger?.info(`Plugin initialization in progress${getInitStatus()}`);
|
|
1590
1279
|
const nextInterval = Math.min(interval + prevInterval, LOGGER_INTERVAL_MAX);
|
|
1591
1280
|
prevInterval = interval;
|
|
1592
1281
|
interval = nextInterval;
|
|
@@ -1599,7 +1288,7 @@ function createInitializationLogger(pluginIds, rootLogger) {
|
|
|
1599
1288
|
started.add(pluginId);
|
|
1600
1289
|
},
|
|
1601
1290
|
onAllStarted() {
|
|
1602
|
-
logger
|
|
1291
|
+
logger?.info(`Plugin initialization complete${getInitStatus()}`);
|
|
1603
1292
|
if (timeout) {
|
|
1604
1293
|
clearTimeout(timeout);
|
|
1605
1294
|
timeout = void 0;
|
|
@@ -1608,52 +1297,76 @@ function createInitializationLogger(pluginIds, rootLogger) {
|
|
|
1608
1297
|
};
|
|
1609
1298
|
}
|
|
1610
1299
|
|
|
1611
|
-
var __accessCheck$8 = (obj, member, msg) => {
|
|
1612
|
-
if (!member.has(obj))
|
|
1613
|
-
throw TypeError("Cannot " + msg);
|
|
1614
|
-
};
|
|
1615
|
-
var __privateGet$6 = (obj, member, getter) => {
|
|
1616
|
-
__accessCheck$8(obj, member, "read from private field");
|
|
1617
|
-
return member.get(obj);
|
|
1618
|
-
};
|
|
1619
|
-
var __privateAdd$8 = (obj, member, value) => {
|
|
1620
|
-
if (member.has(obj))
|
|
1621
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
1622
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
1623
|
-
};
|
|
1624
|
-
var __privateSet$4 = (obj, member, value, setter) => {
|
|
1625
|
-
__accessCheck$8(obj, member, "write to private field");
|
|
1626
|
-
member.set(obj, value);
|
|
1627
|
-
return value;
|
|
1628
|
-
};
|
|
1629
|
-
var __privateMethod$5 = (obj, member, method) => {
|
|
1630
|
-
__accessCheck$8(obj, member, "access private method");
|
|
1631
|
-
return method;
|
|
1632
|
-
};
|
|
1633
|
-
var _startPromise, _features, _extensionPoints, _serviceRegistry, _registeredFeatures, _getInitDeps, getInitDeps_fn, _addFeature, addFeature_fn, _doStart, doStart_fn, _getRootLifecycleImpl, getRootLifecycleImpl_fn, _getPluginLifecycleImpl, getPluginLifecycleImpl_fn;
|
|
1634
1300
|
class BackendInitializer {
|
|
1301
|
+
#startPromise;
|
|
1302
|
+
#features = new Array();
|
|
1303
|
+
#extensionPoints = /* @__PURE__ */ new Map();
|
|
1304
|
+
#serviceRegistry;
|
|
1305
|
+
#registeredFeatures = new Array();
|
|
1635
1306
|
constructor(defaultApiFactories) {
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1307
|
+
this.#serviceRegistry = ServiceRegistry.create([...defaultApiFactories]);
|
|
1308
|
+
}
|
|
1309
|
+
async #getInitDeps(deps, pluginId, moduleId) {
|
|
1310
|
+
const result = /* @__PURE__ */ new Map();
|
|
1311
|
+
const missingRefs = /* @__PURE__ */ new Set();
|
|
1312
|
+
for (const [name, ref] of Object.entries(deps)) {
|
|
1313
|
+
const ep = this.#extensionPoints.get(ref.id);
|
|
1314
|
+
if (ep) {
|
|
1315
|
+
if (ep.pluginId !== pluginId) {
|
|
1316
|
+
throw new Error(
|
|
1317
|
+
`Illegal dependency: Module '${moduleId}' for plugin '${pluginId}' attempted to depend on extension point '${ref.id}' for plugin '${ep.pluginId}'. Extension points can only be used within their plugin's scope.`
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
result.set(name, ep.impl);
|
|
1321
|
+
} else {
|
|
1322
|
+
const impl = await this.#serviceRegistry.get(
|
|
1323
|
+
ref,
|
|
1324
|
+
pluginId
|
|
1325
|
+
);
|
|
1326
|
+
if (impl) {
|
|
1327
|
+
result.set(name, impl);
|
|
1328
|
+
} else {
|
|
1329
|
+
missingRefs.add(ref);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
if (missingRefs.size > 0) {
|
|
1334
|
+
const missing = Array.from(missingRefs).join(", ");
|
|
1335
|
+
throw new Error(
|
|
1336
|
+
`No extension point or service available for the following ref(s): ${missing}`
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
return Object.fromEntries(result);
|
|
1648
1340
|
}
|
|
1649
1341
|
add(feature) {
|
|
1650
|
-
if (
|
|
1342
|
+
if (this.#startPromise) {
|
|
1651
1343
|
throw new Error("feature can not be added after the backend has started");
|
|
1652
1344
|
}
|
|
1653
|
-
|
|
1345
|
+
this.#registeredFeatures.push(Promise.resolve(feature));
|
|
1346
|
+
}
|
|
1347
|
+
#addFeature(feature) {
|
|
1348
|
+
if (feature.$$type !== "@backstage/BackendFeature") {
|
|
1349
|
+
throw new Error(
|
|
1350
|
+
`Failed to add feature, invalid type '${feature.$$type}'`
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
if (isServiceFactory(feature)) {
|
|
1354
|
+
this.#serviceRegistry.add(feature);
|
|
1355
|
+
} else if (isInternalBackendFeature(feature)) {
|
|
1356
|
+
if (feature.version !== "v1") {
|
|
1357
|
+
throw new Error(
|
|
1358
|
+
`Failed to add feature, invalid version '${feature.version}'`
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
this.#features.push(feature);
|
|
1362
|
+
} else {
|
|
1363
|
+
throw new Error(
|
|
1364
|
+
`Failed to add feature, invalid feature ${JSON.stringify(feature)}`
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1654
1367
|
}
|
|
1655
1368
|
async start() {
|
|
1656
|
-
if (
|
|
1369
|
+
if (this.#startPromise) {
|
|
1657
1370
|
throw new Error("Backend has already started");
|
|
1658
1371
|
}
|
|
1659
1372
|
const exitHandler = async () => {
|
|
@@ -1671,242 +1384,187 @@ class BackendInitializer {
|
|
|
1671
1384
|
process.addListener("SIGTERM", exitHandler);
|
|
1672
1385
|
process.addListener("SIGINT", exitHandler);
|
|
1673
1386
|
process.addListener("beforeExit", exitHandler);
|
|
1674
|
-
|
|
1675
|
-
await
|
|
1676
|
-
}
|
|
1677
|
-
async stop() {
|
|
1678
|
-
if (!__privateGet$6(this, _startPromise)) {
|
|
1679
|
-
return;
|
|
1680
|
-
}
|
|
1681
|
-
try {
|
|
1682
|
-
await __privateGet$6(this, _startPromise);
|
|
1683
|
-
} catch (error) {
|
|
1684
|
-
}
|
|
1685
|
-
const lifecycleService = await __privateMethod$5(this, _getRootLifecycleImpl, getRootLifecycleImpl_fn).call(this);
|
|
1686
|
-
await lifecycleService.shutdown();
|
|
1387
|
+
this.#startPromise = this.#doStart();
|
|
1388
|
+
await this.#startPromise;
|
|
1687
1389
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
_serviceRegistry = new WeakMap();
|
|
1693
|
-
_registeredFeatures = new WeakMap();
|
|
1694
|
-
_getInitDeps = new WeakSet();
|
|
1695
|
-
getInitDeps_fn = async function(deps, pluginId, moduleId) {
|
|
1696
|
-
const result = /* @__PURE__ */ new Map();
|
|
1697
|
-
const missingRefs = /* @__PURE__ */ new Set();
|
|
1698
|
-
for (const [name, ref] of Object.entries(deps)) {
|
|
1699
|
-
const ep = __privateGet$6(this, _extensionPoints).get(ref.id);
|
|
1700
|
-
if (ep) {
|
|
1701
|
-
if (ep.pluginId !== pluginId) {
|
|
1702
|
-
throw new Error(
|
|
1703
|
-
`Illegal dependency: Module '${moduleId}' for plugin '${pluginId}' attempted to depend on extension point '${ref.id}' for plugin '${ep.pluginId}'. Extension points can only be used within their plugin's scope.`
|
|
1704
|
-
);
|
|
1705
|
-
}
|
|
1706
|
-
result.set(name, ep.impl);
|
|
1707
|
-
} else {
|
|
1708
|
-
const impl = await __privateGet$6(this, _serviceRegistry).get(
|
|
1709
|
-
ref,
|
|
1710
|
-
pluginId
|
|
1711
|
-
);
|
|
1712
|
-
if (impl) {
|
|
1713
|
-
result.set(name, impl);
|
|
1714
|
-
} else {
|
|
1715
|
-
missingRefs.add(ref);
|
|
1716
|
-
}
|
|
1390
|
+
async #doStart() {
|
|
1391
|
+
this.#serviceRegistry.checkForCircularDeps();
|
|
1392
|
+
for (const feature of this.#registeredFeatures) {
|
|
1393
|
+
this.#addFeature(await feature);
|
|
1717
1394
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
throw new Error(
|
|
1722
|
-
`No extension point or service available for the following ref(s): ${missing}`
|
|
1395
|
+
const featureDiscovery = await this.#serviceRegistry.get(
|
|
1396
|
+
alpha.featureDiscoveryServiceRef,
|
|
1397
|
+
"root"
|
|
1723
1398
|
);
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
);
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const pluginInits = /* @__PURE__ */ new Map();
|
|
1768
|
-
const moduleInits = /* @__PURE__ */ new Map();
|
|
1769
|
-
for (const feature of __privateGet$6(this, _features)) {
|
|
1770
|
-
for (const r of feature.getRegistrations()) {
|
|
1771
|
-
const provides = /* @__PURE__ */ new Set();
|
|
1772
|
-
if (r.type === "plugin" || r.type === "module") {
|
|
1773
|
-
for (const [extRef, extImpl] of r.extensionPoints) {
|
|
1774
|
-
if (__privateGet$6(this, _extensionPoints).has(extRef.id)) {
|
|
1399
|
+
if (featureDiscovery) {
|
|
1400
|
+
const { features } = await featureDiscovery.getBackendFeatures();
|
|
1401
|
+
for (const feature of features) {
|
|
1402
|
+
this.#addFeature(feature);
|
|
1403
|
+
}
|
|
1404
|
+
this.#serviceRegistry.checkForCircularDeps();
|
|
1405
|
+
}
|
|
1406
|
+
await this.#serviceRegistry.initializeEagerServicesWithScope("root");
|
|
1407
|
+
const pluginInits = /* @__PURE__ */ new Map();
|
|
1408
|
+
const moduleInits = /* @__PURE__ */ new Map();
|
|
1409
|
+
for (const feature of this.#features) {
|
|
1410
|
+
for (const r of feature.getRegistrations()) {
|
|
1411
|
+
const provides = /* @__PURE__ */ new Set();
|
|
1412
|
+
if (r.type === "plugin" || r.type === "module") {
|
|
1413
|
+
for (const [extRef, extImpl] of r.extensionPoints) {
|
|
1414
|
+
if (this.#extensionPoints.has(extRef.id)) {
|
|
1415
|
+
throw new Error(
|
|
1416
|
+
`ExtensionPoint with ID '${extRef.id}' is already registered`
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
this.#extensionPoints.set(extRef.id, {
|
|
1420
|
+
impl: extImpl,
|
|
1421
|
+
pluginId: r.pluginId
|
|
1422
|
+
});
|
|
1423
|
+
provides.add(extRef);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
if (r.type === "plugin") {
|
|
1427
|
+
if (pluginInits.has(r.pluginId)) {
|
|
1428
|
+
throw new Error(`Plugin '${r.pluginId}' is already registered`);
|
|
1429
|
+
}
|
|
1430
|
+
pluginInits.set(r.pluginId, {
|
|
1431
|
+
provides,
|
|
1432
|
+
consumes: new Set(Object.values(r.init.deps)),
|
|
1433
|
+
init: r.init
|
|
1434
|
+
});
|
|
1435
|
+
} else {
|
|
1436
|
+
let modules = moduleInits.get(r.pluginId);
|
|
1437
|
+
if (!modules) {
|
|
1438
|
+
modules = /* @__PURE__ */ new Map();
|
|
1439
|
+
moduleInits.set(r.pluginId, modules);
|
|
1440
|
+
}
|
|
1441
|
+
if (modules.has(r.moduleId)) {
|
|
1775
1442
|
throw new Error(
|
|
1776
|
-
`
|
|
1443
|
+
`Module '${r.moduleId}' for plugin '${r.pluginId}' is already registered`
|
|
1777
1444
|
);
|
|
1778
1445
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1446
|
+
modules.set(r.moduleId, {
|
|
1447
|
+
provides,
|
|
1448
|
+
consumes: new Set(Object.values(r.init.deps)),
|
|
1449
|
+
init: r.init
|
|
1782
1450
|
});
|
|
1783
|
-
provides.add(extRef);
|
|
1784
1451
|
}
|
|
1785
1452
|
}
|
|
1786
|
-
if (r.type === "plugin") {
|
|
1787
|
-
if (pluginInits.has(r.pluginId)) {
|
|
1788
|
-
throw new Error(`Plugin '${r.pluginId}' is already registered`);
|
|
1789
|
-
}
|
|
1790
|
-
pluginInits.set(r.pluginId, {
|
|
1791
|
-
provides,
|
|
1792
|
-
consumes: new Set(Object.values(r.init.deps)),
|
|
1793
|
-
init: r.init
|
|
1794
|
-
});
|
|
1795
|
-
} else {
|
|
1796
|
-
let modules = moduleInits.get(r.pluginId);
|
|
1797
|
-
if (!modules) {
|
|
1798
|
-
modules = /* @__PURE__ */ new Map();
|
|
1799
|
-
moduleInits.set(r.pluginId, modules);
|
|
1800
|
-
}
|
|
1801
|
-
if (modules.has(r.moduleId)) {
|
|
1802
|
-
throw new Error(
|
|
1803
|
-
`Module '${r.moduleId}' for plugin '${r.pluginId}' is already registered`
|
|
1804
|
-
);
|
|
1805
|
-
}
|
|
1806
|
-
modules.set(r.moduleId, {
|
|
1807
|
-
provides,
|
|
1808
|
-
consumes: new Set(Object.values(r.init.deps)),
|
|
1809
|
-
init: r.init
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
1453
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
pluginId
|
|
1824
|
-
);
|
|
1825
|
-
const modules = moduleInits.get(pluginId);
|
|
1826
|
-
if (modules) {
|
|
1827
|
-
const tree = DependencyGraph.fromIterable(
|
|
1828
|
-
Array.from(modules).map(([moduleId, moduleInit]) => ({
|
|
1829
|
-
value: { moduleId, moduleInit },
|
|
1830
|
-
// Relationships are reversed at this point since we're only interested in the extension points.
|
|
1831
|
-
// If a modules provides extension point A we want it to be initialized AFTER all modules
|
|
1832
|
-
// that depend on extension point A, so that they can provide their extensions.
|
|
1833
|
-
consumes: Array.from(moduleInit.provides).map((p) => p.id),
|
|
1834
|
-
provides: Array.from(moduleInit.consumes).map((c) => c.id)
|
|
1835
|
-
}))
|
|
1454
|
+
const allPluginIds = [...pluginInits.keys()];
|
|
1455
|
+
const initLogger = createInitializationLogger(
|
|
1456
|
+
allPluginIds,
|
|
1457
|
+
await this.#serviceRegistry.get(backendPluginApi.coreServices.rootLogger, "root")
|
|
1458
|
+
);
|
|
1459
|
+
await Promise.all(
|
|
1460
|
+
allPluginIds.map(async (pluginId) => {
|
|
1461
|
+
await this.#serviceRegistry.initializeEagerServicesWithScope(
|
|
1462
|
+
"plugin",
|
|
1463
|
+
pluginId
|
|
1836
1464
|
);
|
|
1837
|
-
const
|
|
1838
|
-
if (
|
|
1839
|
-
|
|
1840
|
-
|
|
1465
|
+
const modules = moduleInits.get(pluginId);
|
|
1466
|
+
if (modules) {
|
|
1467
|
+
const tree = DependencyGraph.fromIterable(
|
|
1468
|
+
Array.from(modules).map(([moduleId, moduleInit]) => ({
|
|
1469
|
+
value: { moduleId, moduleInit },
|
|
1470
|
+
// Relationships are reversed at this point since we're only interested in the extension points.
|
|
1471
|
+
// If a modules provides extension point A we want it to be initialized AFTER all modules
|
|
1472
|
+
// that depend on extension point A, so that they can provide their extensions.
|
|
1473
|
+
consumes: Array.from(moduleInit.provides).map((p) => p.id),
|
|
1474
|
+
provides: Array.from(moduleInit.consumes).map((c) => c.id)
|
|
1475
|
+
}))
|
|
1841
1476
|
);
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
throw new errors.ForwardedError(
|
|
1848
|
-
`Module '${moduleId}' for plugin '${pluginId}' startup failed`,
|
|
1849
|
-
error
|
|
1850
|
-
);
|
|
1851
|
-
});
|
|
1477
|
+
const circular = tree.detectCircularDependency();
|
|
1478
|
+
if (circular) {
|
|
1479
|
+
throw new errors.ConflictError(
|
|
1480
|
+
`Circular dependency detected for modules of plugin '${pluginId}', ${circular.map(({ moduleId }) => `'${moduleId}'`).join(" -> ")}`
|
|
1481
|
+
);
|
|
1852
1482
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1483
|
+
await tree.parallelTopologicalTraversal(
|
|
1484
|
+
async ({ moduleId, moduleInit }) => {
|
|
1485
|
+
const moduleDeps = await this.#getInitDeps(
|
|
1486
|
+
moduleInit.init.deps,
|
|
1487
|
+
pluginId,
|
|
1488
|
+
moduleId
|
|
1489
|
+
);
|
|
1490
|
+
await moduleInit.init.func(moduleDeps).catch((error) => {
|
|
1491
|
+
throw new errors.ForwardedError(
|
|
1492
|
+
`Module '${moduleId}' for plugin '${pluginId}' startup failed`,
|
|
1493
|
+
error
|
|
1494
|
+
);
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1862
1497
|
);
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1498
|
+
}
|
|
1499
|
+
const pluginInit = pluginInits.get(pluginId);
|
|
1500
|
+
if (pluginInit) {
|
|
1501
|
+
const pluginDeps = await this.#getInitDeps(
|
|
1502
|
+
pluginInit.init.deps,
|
|
1503
|
+
pluginId
|
|
1504
|
+
);
|
|
1505
|
+
await pluginInit.init.func(pluginDeps).catch((error) => {
|
|
1506
|
+
throw new errors.ForwardedError(
|
|
1507
|
+
`Plugin '${pluginId}' startup failed`,
|
|
1508
|
+
error
|
|
1509
|
+
);
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
initLogger.onPluginStarted(pluginId);
|
|
1513
|
+
const lifecycleService2 = await this.#getPluginLifecycleImpl(pluginId);
|
|
1514
|
+
await lifecycleService2.startup();
|
|
1515
|
+
})
|
|
1877
1516
|
);
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1517
|
+
const lifecycleService = await this.#getRootLifecycleImpl();
|
|
1518
|
+
await lifecycleService.startup();
|
|
1519
|
+
initLogger.onAllStarted();
|
|
1520
|
+
if (process.env.NODE_ENV !== "test") {
|
|
1521
|
+
const rootLogger = await this.#serviceRegistry.get(
|
|
1522
|
+
backendPluginApi.coreServices.rootLogger,
|
|
1523
|
+
"root"
|
|
1524
|
+
);
|
|
1525
|
+
process.on("unhandledRejection", (reason) => {
|
|
1526
|
+
rootLogger?.child({ type: "unhandledRejection" })?.error("Unhandled rejection", reason);
|
|
1527
|
+
});
|
|
1528
|
+
process.on("uncaughtException", (error) => {
|
|
1529
|
+
rootLogger?.child({ type: "uncaughtException" })?.error("Uncaught exception", error);
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1886
1532
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1533
|
+
async stop() {
|
|
1534
|
+
if (!this.#startPromise) {
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
try {
|
|
1538
|
+
await this.#startPromise;
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
}
|
|
1541
|
+
const lifecycleService = await this.#getRootLifecycleImpl();
|
|
1542
|
+
await lifecycleService.shutdown();
|
|
1896
1543
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1544
|
+
// Bit of a hacky way to grab the lifecycle services, potentially find a nicer way to do this
|
|
1545
|
+
async #getRootLifecycleImpl() {
|
|
1546
|
+
const lifecycleService = await this.#serviceRegistry.get(
|
|
1547
|
+
backendPluginApi.coreServices.rootLifecycle,
|
|
1548
|
+
"root"
|
|
1549
|
+
);
|
|
1550
|
+
const service = lifecycleService;
|
|
1551
|
+
if (service && typeof service.startup === "function" && typeof service.shutdown === "function") {
|
|
1552
|
+
return service;
|
|
1553
|
+
}
|
|
1554
|
+
throw new Error("Unexpected root lifecycle service implementation");
|
|
1907
1555
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1556
|
+
async #getPluginLifecycleImpl(pluginId) {
|
|
1557
|
+
const lifecycleService = await this.#serviceRegistry.get(
|
|
1558
|
+
backendPluginApi.coreServices.lifecycle,
|
|
1559
|
+
pluginId
|
|
1560
|
+
);
|
|
1561
|
+
const service = lifecycleService;
|
|
1562
|
+
if (service && typeof service.startup === "function") {
|
|
1563
|
+
return service;
|
|
1564
|
+
}
|
|
1565
|
+
throw new Error("Unexpected plugin lifecycle service implementation");
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1910
1568
|
function isServiceFactory(feature) {
|
|
1911
1569
|
return !!feature.service;
|
|
1912
1570
|
}
|
|
@@ -1914,45 +1572,25 @@ function isInternalBackendFeature(feature) {
|
|
|
1914
1572
|
return typeof feature.getRegistrations === "function";
|
|
1915
1573
|
}
|
|
1916
1574
|
|
|
1917
|
-
var __accessCheck$7 = (obj, member, msg) => {
|
|
1918
|
-
if (!member.has(obj))
|
|
1919
|
-
throw TypeError("Cannot " + msg);
|
|
1920
|
-
};
|
|
1921
|
-
var __privateGet$5 = (obj, member, getter) => {
|
|
1922
|
-
__accessCheck$7(obj, member, "read from private field");
|
|
1923
|
-
return member.get(obj);
|
|
1924
|
-
};
|
|
1925
|
-
var __privateAdd$7 = (obj, member, value) => {
|
|
1926
|
-
if (member.has(obj))
|
|
1927
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
1928
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
1929
|
-
};
|
|
1930
|
-
var __privateSet$3 = (obj, member, value, setter) => {
|
|
1931
|
-
__accessCheck$7(obj, member, "write to private field");
|
|
1932
|
-
member.set(obj, value);
|
|
1933
|
-
return value;
|
|
1934
|
-
};
|
|
1935
|
-
var _initializer;
|
|
1936
1575
|
class BackstageBackend {
|
|
1576
|
+
#initializer;
|
|
1937
1577
|
constructor(defaultServiceFactories) {
|
|
1938
|
-
|
|
1939
|
-
__privateSet$3(this, _initializer, new BackendInitializer(defaultServiceFactories));
|
|
1578
|
+
this.#initializer = new BackendInitializer(defaultServiceFactories);
|
|
1940
1579
|
}
|
|
1941
1580
|
add(feature) {
|
|
1942
1581
|
if (isPromise(feature)) {
|
|
1943
|
-
|
|
1582
|
+
this.#initializer.add(feature.then((f) => unwrapFeature(f.default)));
|
|
1944
1583
|
} else {
|
|
1945
|
-
|
|
1584
|
+
this.#initializer.add(unwrapFeature(feature));
|
|
1946
1585
|
}
|
|
1947
1586
|
}
|
|
1948
1587
|
async start() {
|
|
1949
|
-
await
|
|
1588
|
+
await this.#initializer.start();
|
|
1950
1589
|
}
|
|
1951
1590
|
async stop() {
|
|
1952
|
-
await
|
|
1591
|
+
await this.#initializer.stop();
|
|
1953
1592
|
}
|
|
1954
1593
|
}
|
|
1955
|
-
_initializer = new WeakMap();
|
|
1956
1594
|
function isPromise(value) {
|
|
1957
1595
|
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
1958
1596
|
}
|
|
@@ -2013,10 +1651,9 @@ class DatabaseKeyStore {
|
|
|
2013
1651
|
this.logger = logger;
|
|
2014
1652
|
}
|
|
2015
1653
|
static async create(options) {
|
|
2016
|
-
var _a;
|
|
2017
1654
|
const { database, logger } = options;
|
|
2018
1655
|
const client = await database.getClient();
|
|
2019
|
-
if (!
|
|
1656
|
+
if (!database.migrations?.skip) {
|
|
2020
1657
|
await applyDatabaseMigrations(client);
|
|
2021
1658
|
}
|
|
2022
1659
|
return new DatabaseKeyStore(client, logger);
|
|
@@ -2060,14 +1697,15 @@ class DatabaseKeyStore {
|
|
|
2060
1697
|
}
|
|
2061
1698
|
}
|
|
2062
1699
|
|
|
2063
|
-
function createCredentialsWithServicePrincipal(sub, token) {
|
|
1700
|
+
function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
|
|
2064
1701
|
return {
|
|
2065
1702
|
$$type: "@backstage/BackstageCredentials",
|
|
2066
1703
|
version: "v1",
|
|
2067
1704
|
token,
|
|
2068
1705
|
principal: {
|
|
2069
1706
|
type: "service",
|
|
2070
|
-
subject: sub
|
|
1707
|
+
subject: sub,
|
|
1708
|
+
accessRestrictions
|
|
2071
1709
|
}
|
|
2072
1710
|
};
|
|
2073
1711
|
}
|
|
@@ -2105,20 +1743,6 @@ function toInternalBackstageCredentials(credentials) {
|
|
|
2105
1743
|
return internalCredentials;
|
|
2106
1744
|
}
|
|
2107
1745
|
|
|
2108
|
-
var __accessCheck$6 = (obj, member, msg) => {
|
|
2109
|
-
if (!member.has(obj))
|
|
2110
|
-
throw TypeError("Cannot " + msg);
|
|
2111
|
-
};
|
|
2112
|
-
var __privateAdd$6 = (obj, member, value) => {
|
|
2113
|
-
if (member.has(obj))
|
|
2114
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
2115
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
2116
|
-
};
|
|
2117
|
-
var __privateMethod$4 = (obj, member, method) => {
|
|
2118
|
-
__accessCheck$6(obj, member, "access private method");
|
|
2119
|
-
return method;
|
|
2120
|
-
};
|
|
2121
|
-
var _getJwtExpiration, getJwtExpiration_fn;
|
|
2122
1746
|
class DefaultAuthService {
|
|
2123
1747
|
constructor(userTokenHandler, pluginTokenHandler, externalTokenHandler, tokenManager, pluginId, disableDefaultAuthPolicy, publicKeyStore) {
|
|
2124
1748
|
this.userTokenHandler = userTokenHandler;
|
|
@@ -2128,7 +1752,6 @@ class DefaultAuthService {
|
|
|
2128
1752
|
this.pluginId = pluginId;
|
|
2129
1753
|
this.disableDefaultAuthPolicy = disableDefaultAuthPolicy;
|
|
2130
1754
|
this.publicKeyStore = publicKeyStore;
|
|
2131
|
-
__privateAdd$6(this, _getJwtExpiration);
|
|
2132
1755
|
}
|
|
2133
1756
|
// allowLimitedAccess is currently ignored, since we currently always use the full user tokens
|
|
2134
1757
|
async authenticate(token) {
|
|
@@ -2146,7 +1769,7 @@ class DefaultAuthService {
|
|
|
2146
1769
|
return createCredentialsWithUserPrincipal(
|
|
2147
1770
|
userResult2.userEntityRef,
|
|
2148
1771
|
pluginResult.limitedUserToken,
|
|
2149
|
-
|
|
1772
|
+
this.#getJwtExpiration(pluginResult.limitedUserToken)
|
|
2150
1773
|
);
|
|
2151
1774
|
}
|
|
2152
1775
|
return createCredentialsWithServicePrincipal(pluginResult.subject);
|
|
@@ -2156,12 +1779,16 @@ class DefaultAuthService {
|
|
|
2156
1779
|
return createCredentialsWithUserPrincipal(
|
|
2157
1780
|
userResult.userEntityRef,
|
|
2158
1781
|
token,
|
|
2159
|
-
|
|
1782
|
+
this.#getJwtExpiration(token)
|
|
2160
1783
|
);
|
|
2161
1784
|
}
|
|
2162
1785
|
const externalResult = await this.externalTokenHandler.verifyToken(token);
|
|
2163
1786
|
if (externalResult) {
|
|
2164
|
-
return createCredentialsWithServicePrincipal(
|
|
1787
|
+
return createCredentialsWithServicePrincipal(
|
|
1788
|
+
externalResult.subject,
|
|
1789
|
+
void 0,
|
|
1790
|
+
externalResult.accessRestrictions
|
|
1791
|
+
);
|
|
2165
1792
|
}
|
|
2166
1793
|
throw new errors.AuthenticationError("Illegal token");
|
|
2167
1794
|
}
|
|
@@ -2244,49 +1871,29 @@ class DefaultAuthService {
|
|
|
2244
1871
|
const { keys } = await this.publicKeyStore.listKeys();
|
|
2245
1872
|
return { keys: keys.map(({ key }) => key) };
|
|
2246
1873
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
1874
|
+
#getJwtExpiration(token) {
|
|
1875
|
+
const { exp } = jose.decodeJwt(token);
|
|
1876
|
+
if (!exp) {
|
|
1877
|
+
throw new errors.AuthenticationError("User token is missing expiration");
|
|
1878
|
+
}
|
|
1879
|
+
return new Date(exp * 1e3);
|
|
2253
1880
|
}
|
|
2254
|
-
|
|
2255
|
-
};
|
|
1881
|
+
}
|
|
2256
1882
|
|
|
2257
|
-
var __accessCheck$5 = (obj, member, msg) => {
|
|
2258
|
-
if (!member.has(obj))
|
|
2259
|
-
throw TypeError("Cannot " + msg);
|
|
2260
|
-
};
|
|
2261
|
-
var __privateGet$4 = (obj, member, getter) => {
|
|
2262
|
-
__accessCheck$5(obj, member, "read from private field");
|
|
2263
|
-
return member.get(obj);
|
|
2264
|
-
};
|
|
2265
|
-
var __privateAdd$5 = (obj, member, value) => {
|
|
2266
|
-
if (member.has(obj))
|
|
2267
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
2268
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
2269
|
-
};
|
|
2270
|
-
var __privateSet$2 = (obj, member, value, setter) => {
|
|
2271
|
-
__accessCheck$5(obj, member, "write to private field");
|
|
2272
|
-
member.set(obj, value);
|
|
2273
|
-
return value;
|
|
2274
|
-
};
|
|
2275
|
-
var _keyStore, _keyStoreUpdated;
|
|
2276
1883
|
const CLOCK_MARGIN_S = 10;
|
|
2277
1884
|
class JwksClient {
|
|
2278
1885
|
constructor(getEndpoint) {
|
|
2279
1886
|
this.getEndpoint = getEndpoint;
|
|
2280
|
-
__privateAdd$5(this, _keyStore, void 0);
|
|
2281
|
-
__privateAdd$5(this, _keyStoreUpdated, 0);
|
|
2282
1887
|
}
|
|
1888
|
+
#keyStore;
|
|
1889
|
+
#keyStoreUpdated = 0;
|
|
2283
1890
|
get getKey() {
|
|
2284
|
-
if (!
|
|
1891
|
+
if (!this.#keyStore) {
|
|
2285
1892
|
throw new errors.AuthenticationError(
|
|
2286
1893
|
"refreshKeyStore must be called before jwksClient.getKey"
|
|
2287
1894
|
);
|
|
2288
1895
|
}
|
|
2289
|
-
return
|
|
1896
|
+
return this.#keyStore;
|
|
2290
1897
|
}
|
|
2291
1898
|
/**
|
|
2292
1899
|
* If the last keystore refresh is stale, update the keystore URL to the latest
|
|
@@ -2296,9 +1903,9 @@ class JwksClient {
|
|
|
2296
1903
|
const header = await jose.decodeProtectedHeader(rawJwtToken);
|
|
2297
1904
|
let keyStoreHasKey;
|
|
2298
1905
|
try {
|
|
2299
|
-
if (
|
|
1906
|
+
if (this.#keyStore) {
|
|
2300
1907
|
const [_, rawPayload, rawSignature] = rawJwtToken.split(".");
|
|
2301
|
-
keyStoreHasKey = await
|
|
1908
|
+
keyStoreHasKey = await this.#keyStore(header, {
|
|
2302
1909
|
payload: rawPayload,
|
|
2303
1910
|
signature: rawSignature
|
|
2304
1911
|
});
|
|
@@ -2306,23 +1913,15 @@ class JwksClient {
|
|
|
2306
1913
|
} catch (error) {
|
|
2307
1914
|
keyStoreHasKey = false;
|
|
2308
1915
|
}
|
|
2309
|
-
const issuedAfterLastRefresh =
|
|
2310
|
-
if (!
|
|
1916
|
+
const issuedAfterLastRefresh = payload?.iat && payload.iat > this.#keyStoreUpdated - CLOCK_MARGIN_S;
|
|
1917
|
+
if (!this.#keyStore || !keyStoreHasKey && issuedAfterLastRefresh) {
|
|
2311
1918
|
const endpoint = await this.getEndpoint();
|
|
2312
|
-
|
|
2313
|
-
|
|
1919
|
+
this.#keyStore = jose.createRemoteJWKSet(endpoint);
|
|
1920
|
+
this.#keyStoreUpdated = Date.now() / 1e3;
|
|
2314
1921
|
}
|
|
2315
1922
|
}
|
|
2316
1923
|
}
|
|
2317
|
-
_keyStore = new WeakMap();
|
|
2318
|
-
_keyStoreUpdated = new WeakMap();
|
|
2319
1924
|
|
|
2320
|
-
var __defProp = Object.defineProperty;
|
|
2321
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
2322
|
-
var __publicField = (obj, key, value) => {
|
|
2323
|
-
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
2324
|
-
return value;
|
|
2325
|
-
};
|
|
2326
1925
|
const KEY_EXPIRATION_MARGIN_FACTOR = 3;
|
|
2327
1926
|
const SECONDS_IN_MS = 1e3;
|
|
2328
1927
|
const ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;
|
|
@@ -2334,21 +1933,20 @@ class PluginTokenHandler {
|
|
|
2334
1933
|
this.keyDurationSeconds = keyDurationSeconds;
|
|
2335
1934
|
this.algorithm = algorithm;
|
|
2336
1935
|
this.discovery = discovery;
|
|
2337
|
-
__publicField(this, "privateKeyPromise");
|
|
2338
|
-
__publicField(this, "keyExpiry");
|
|
2339
|
-
__publicField(this, "jwksMap", /* @__PURE__ */ new Map());
|
|
2340
|
-
// Tracking state for isTargetPluginSupported
|
|
2341
|
-
__publicField(this, "supportedTargetPlugins", /* @__PURE__ */ new Set());
|
|
2342
|
-
__publicField(this, "targetPluginInflightChecks", /* @__PURE__ */ new Map());
|
|
2343
1936
|
}
|
|
1937
|
+
privateKeyPromise;
|
|
1938
|
+
keyExpiry;
|
|
1939
|
+
jwksMap = /* @__PURE__ */ new Map();
|
|
1940
|
+
// Tracking state for isTargetPluginSupported
|
|
1941
|
+
supportedTargetPlugins = /* @__PURE__ */ new Set();
|
|
1942
|
+
targetPluginInflightChecks = /* @__PURE__ */ new Map();
|
|
2344
1943
|
static create(options) {
|
|
2345
|
-
var _a;
|
|
2346
1944
|
return new PluginTokenHandler(
|
|
2347
1945
|
options.logger,
|
|
2348
1946
|
options.ownPluginId,
|
|
2349
1947
|
options.publicKeyStore,
|
|
2350
1948
|
Math.round(types.durationToMilliseconds(options.keyDuration) / 1e3),
|
|
2351
|
-
|
|
1949
|
+
options.algorithm ?? "ES256",
|
|
2352
1950
|
options.discovery
|
|
2353
1951
|
);
|
|
2354
1952
|
}
|
|
@@ -2396,7 +1994,7 @@ class PluginTokenHandler {
|
|
|
2396
1994
|
ourExp,
|
|
2397
1995
|
Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS)
|
|
2398
1996
|
) : ourExp;
|
|
2399
|
-
const claims = { sub, aud, iat, exp, obo: onBehalfOf
|
|
1997
|
+
const claims = { sub, aud, iat, exp, obo: onBehalfOf?.token };
|
|
2400
1998
|
const token = await new jose.SignJWT(claims).setProtectedHeader({
|
|
2401
1999
|
typ: pluginAuthNode.tokenTypes.plugin.typParam,
|
|
2402
2000
|
alg: this.algorithm,
|
|
@@ -2502,34 +2100,19 @@ class PluginTokenHandler {
|
|
|
2502
2100
|
}
|
|
2503
2101
|
}
|
|
2504
2102
|
|
|
2505
|
-
|
|
2506
|
-
if (!member.has(obj))
|
|
2507
|
-
throw TypeError("Cannot " + msg);
|
|
2508
|
-
};
|
|
2509
|
-
var __privateAdd$4 = (obj, member, value) => {
|
|
2510
|
-
if (member.has(obj))
|
|
2511
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
2512
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
2513
|
-
};
|
|
2514
|
-
var __privateMethod$3 = (obj, member, method) => {
|
|
2515
|
-
__accessCheck$4(obj, member, "access private method");
|
|
2516
|
-
return method;
|
|
2517
|
-
};
|
|
2518
|
-
var _getTokenVerificationOptions, getTokenVerificationOptions_fn;
|
|
2519
|
-
const _UserTokenHandler = class _UserTokenHandler {
|
|
2103
|
+
class UserTokenHandler {
|
|
2520
2104
|
constructor(jwksClient) {
|
|
2521
2105
|
this.jwksClient = jwksClient;
|
|
2522
|
-
__privateAdd$4(this, _getTokenVerificationOptions);
|
|
2523
2106
|
}
|
|
2524
2107
|
static create(options) {
|
|
2525
2108
|
const jwksClient = new JwksClient(async () => {
|
|
2526
2109
|
const url = await options.discovery.getBaseUrl("auth");
|
|
2527
2110
|
return new URL(`${url}/.well-known/jwks.json`);
|
|
2528
2111
|
});
|
|
2529
|
-
return new
|
|
2112
|
+
return new UserTokenHandler(jwksClient);
|
|
2530
2113
|
}
|
|
2531
2114
|
async verifyToken(token) {
|
|
2532
|
-
const verifyOpts =
|
|
2115
|
+
const verifyOpts = this.#getTokenVerificationOptions(token);
|
|
2533
2116
|
if (!verifyOpts) {
|
|
2534
2117
|
return void 0;
|
|
2535
2118
|
}
|
|
@@ -2547,6 +2130,31 @@ const _UserTokenHandler = class _UserTokenHandler {
|
|
|
2547
2130
|
}
|
|
2548
2131
|
return { userEntityRef };
|
|
2549
2132
|
}
|
|
2133
|
+
#getTokenVerificationOptions(token) {
|
|
2134
|
+
try {
|
|
2135
|
+
const { typ } = jose.decodeProtectedHeader(token);
|
|
2136
|
+
if (typ === pluginAuthNode.tokenTypes.user.typParam) {
|
|
2137
|
+
return {
|
|
2138
|
+
requiredClaims: ["iat", "exp", "sub"],
|
|
2139
|
+
typ: pluginAuthNode.tokenTypes.user.typParam
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
if (typ === pluginAuthNode.tokenTypes.limitedUser.typParam) {
|
|
2143
|
+
return {
|
|
2144
|
+
requiredClaims: ["iat", "exp", "sub"],
|
|
2145
|
+
typ: pluginAuthNode.tokenTypes.limitedUser.typParam
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
const { aud } = jose.decodeJwt(token);
|
|
2149
|
+
if (aud === pluginAuthNode.tokenTypes.user.audClaim) {
|
|
2150
|
+
return {
|
|
2151
|
+
audience: pluginAuthNode.tokenTypes.user.audClaim
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
} catch {
|
|
2155
|
+
}
|
|
2156
|
+
return void 0;
|
|
2157
|
+
}
|
|
2550
2158
|
createLimitedUserToken(backstageToken) {
|
|
2551
2159
|
const [headerRaw, payloadRaw] = backstageToken.split(".");
|
|
2552
2160
|
const header = JSON.parse(
|
|
@@ -2592,64 +2200,133 @@ const _UserTokenHandler = class _UserTokenHandler {
|
|
|
2592
2200
|
return false;
|
|
2593
2201
|
}
|
|
2594
2202
|
}
|
|
2595
|
-
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
function readAccessRestrictionsFromConfig(externalAccessEntryConfig) {
|
|
2206
|
+
const configs = externalAccessEntryConfig.getOptionalConfigArray("accessRestrictions") ?? [];
|
|
2207
|
+
const result = /* @__PURE__ */ new Map();
|
|
2208
|
+
for (const config of configs) {
|
|
2209
|
+
const validKeys = ["plugin", "permission", "permissionAttribute"];
|
|
2210
|
+
for (const key of config.keys()) {
|
|
2211
|
+
if (!validKeys.includes(key)) {
|
|
2212
|
+
const valid = validKeys.map((k) => `'${k}'`).join(", ");
|
|
2213
|
+
throw new Error(
|
|
2214
|
+
`Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2605
2217
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2218
|
+
const pluginId = config.getString("plugin");
|
|
2219
|
+
const permissionNames = readPermissionNames(config);
|
|
2220
|
+
const permissionAttributes = readPermissionAttributes(config);
|
|
2221
|
+
if (result.has(pluginId)) {
|
|
2222
|
+
throw new Error(
|
|
2223
|
+
`Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`
|
|
2224
|
+
);
|
|
2611
2225
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2226
|
+
result.set(pluginId, {
|
|
2227
|
+
...permissionNames ? { permissionNames } : {},
|
|
2228
|
+
...permissionAttributes ? { permissionAttributes } : {}
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
return result.size ? result : void 0;
|
|
2232
|
+
}
|
|
2233
|
+
function readStringOrStringArrayFromConfig(root, key, validValues) {
|
|
2234
|
+
if (!root.has(key)) {
|
|
2235
|
+
return void 0;
|
|
2236
|
+
}
|
|
2237
|
+
const rawValues = Array.isArray(root.get(key)) ? root.getStringArray(key) : [root.getString(key)];
|
|
2238
|
+
const values = [
|
|
2239
|
+
...new Set(
|
|
2240
|
+
rawValues.map((v) => v.split(/[ ,]/)).flat().filter(Boolean)
|
|
2241
|
+
)
|
|
2242
|
+
];
|
|
2243
|
+
if (!values.length) {
|
|
2244
|
+
return void 0;
|
|
2245
|
+
}
|
|
2246
|
+
if (validValues?.length) {
|
|
2247
|
+
for (const value of values) {
|
|
2248
|
+
if (!validValues.includes(value)) {
|
|
2249
|
+
const valid = validValues.map((k) => `'${k}'`).join(", ");
|
|
2250
|
+
throw new Error(
|
|
2251
|
+
`Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2617
2254
|
}
|
|
2618
|
-
} catch {
|
|
2619
2255
|
}
|
|
2620
|
-
return
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2256
|
+
return values;
|
|
2257
|
+
}
|
|
2258
|
+
function readPermissionNames(externalAccessEntryConfig) {
|
|
2259
|
+
return readStringOrStringArrayFromConfig(
|
|
2260
|
+
externalAccessEntryConfig,
|
|
2261
|
+
"permission"
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
function readPermissionAttributes(externalAccessEntryConfig) {
|
|
2265
|
+
const config = externalAccessEntryConfig.getOptionalConfig(
|
|
2266
|
+
"permissionAttribute"
|
|
2267
|
+
);
|
|
2268
|
+
if (!config) {
|
|
2269
|
+
return void 0;
|
|
2270
|
+
}
|
|
2271
|
+
const validKeys = ["action"];
|
|
2272
|
+
for (const key of config.keys()) {
|
|
2273
|
+
if (!validKeys.includes(key)) {
|
|
2274
|
+
const valid = validKeys.map((k) => `'${k}'`).join(", ");
|
|
2275
|
+
throw new Error(
|
|
2276
|
+
`Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
const action = readStringOrStringArrayFromConfig(config, "action", [
|
|
2281
|
+
"create",
|
|
2282
|
+
"read",
|
|
2283
|
+
"update",
|
|
2284
|
+
"delete"
|
|
2285
|
+
]);
|
|
2286
|
+
const result = {
|
|
2287
|
+
...action ? { action } : {}
|
|
2288
|
+
};
|
|
2289
|
+
return Object.keys(result).length ? result : void 0;
|
|
2290
|
+
}
|
|
2623
2291
|
|
|
2624
|
-
var __accessCheck$3 = (obj, member, msg) => {
|
|
2625
|
-
if (!member.has(obj))
|
|
2626
|
-
throw TypeError("Cannot " + msg);
|
|
2627
|
-
};
|
|
2628
|
-
var __privateGet$3 = (obj, member, getter) => {
|
|
2629
|
-
__accessCheck$3(obj, member, "read from private field");
|
|
2630
|
-
return member.get(obj);
|
|
2631
|
-
};
|
|
2632
|
-
var __privateAdd$3 = (obj, member, value) => {
|
|
2633
|
-
if (member.has(obj))
|
|
2634
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
2635
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
2636
|
-
};
|
|
2637
|
-
var __privateMethod$2 = (obj, member, method) => {
|
|
2638
|
-
__accessCheck$3(obj, member, "access private method");
|
|
2639
|
-
return method;
|
|
2640
|
-
};
|
|
2641
|
-
var _entries$1, _doAdd, doAdd_fn;
|
|
2642
2292
|
class LegacyTokenHandler {
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2293
|
+
#entries = new Array();
|
|
2294
|
+
add(config) {
|
|
2295
|
+
const allAccessRestrictions = readAccessRestrictionsFromConfig(config);
|
|
2296
|
+
this.#doAdd(
|
|
2297
|
+
config.getString("options.secret"),
|
|
2298
|
+
config.getString("options.subject"),
|
|
2299
|
+
allAccessRestrictions
|
|
2300
|
+
);
|
|
2649
2301
|
}
|
|
2650
2302
|
// used only for the old backend.auth.keys array
|
|
2651
|
-
addOld(
|
|
2652
|
-
|
|
2303
|
+
addOld(config) {
|
|
2304
|
+
this.#doAdd(config.getString("secret"), "external:backstage-plugin");
|
|
2305
|
+
}
|
|
2306
|
+
#doAdd(secret, subject, allAccessRestrictions) {
|
|
2307
|
+
if (!secret.match(/^\S+$/)) {
|
|
2308
|
+
throw new Error("Illegal secret, must be a valid base64 string");
|
|
2309
|
+
} else if (!subject.match(/^\S+$/)) {
|
|
2310
|
+
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
2311
|
+
}
|
|
2312
|
+
let key;
|
|
2313
|
+
try {
|
|
2314
|
+
key = jose.base64url.decode(secret);
|
|
2315
|
+
} catch {
|
|
2316
|
+
throw new Error("Illegal secret, must be a valid base64 string");
|
|
2317
|
+
}
|
|
2318
|
+
if (this.#entries.some((e) => e.key === key)) {
|
|
2319
|
+
throw new Error(
|
|
2320
|
+
"Legacy externalAccess token was declared more than once"
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
this.#entries.push({
|
|
2324
|
+
key,
|
|
2325
|
+
result: {
|
|
2326
|
+
subject,
|
|
2327
|
+
allAccessRestrictions
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2653
2330
|
}
|
|
2654
2331
|
async verifyToken(token) {
|
|
2655
2332
|
try {
|
|
@@ -2664,10 +2341,10 @@ class LegacyTokenHandler {
|
|
|
2664
2341
|
} catch (e) {
|
|
2665
2342
|
return void 0;
|
|
2666
2343
|
}
|
|
2667
|
-
for (const
|
|
2344
|
+
for (const { key, result } of this.#entries) {
|
|
2668
2345
|
try {
|
|
2669
|
-
await jose.jwtVerify(token,
|
|
2670
|
-
return
|
|
2346
|
+
await jose.jwtVerify(token, key);
|
|
2347
|
+
return result;
|
|
2671
2348
|
} catch (e) {
|
|
2672
2349
|
if (e.code !== "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") {
|
|
2673
2350
|
throw e;
|
|
@@ -2677,85 +2354,109 @@ class LegacyTokenHandler {
|
|
|
2677
2354
|
return void 0;
|
|
2678
2355
|
}
|
|
2679
2356
|
}
|
|
2680
|
-
_entries$1 = new WeakMap();
|
|
2681
|
-
_doAdd = new WeakSet();
|
|
2682
|
-
doAdd_fn = function(secret, subject) {
|
|
2683
|
-
if (!secret.match(/^\S+$/)) {
|
|
2684
|
-
throw new Error("Illegal secret, must be a valid base64 string");
|
|
2685
|
-
}
|
|
2686
|
-
let key;
|
|
2687
|
-
try {
|
|
2688
|
-
key = jose.base64url.decode(secret);
|
|
2689
|
-
} catch {
|
|
2690
|
-
throw new Error("Illegal secret, must be a valid base64 string");
|
|
2691
|
-
}
|
|
2692
|
-
if (!subject.match(/^\S+$/)) {
|
|
2693
|
-
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
2694
|
-
}
|
|
2695
|
-
__privateGet$3(this, _entries$1).push({ key, subject });
|
|
2696
|
-
};
|
|
2697
2357
|
|
|
2698
|
-
var __accessCheck$2 = (obj, member, msg) => {
|
|
2699
|
-
if (!member.has(obj))
|
|
2700
|
-
throw TypeError("Cannot " + msg);
|
|
2701
|
-
};
|
|
2702
|
-
var __privateGet$2 = (obj, member, getter) => {
|
|
2703
|
-
__accessCheck$2(obj, member, "read from private field");
|
|
2704
|
-
return member.get(obj);
|
|
2705
|
-
};
|
|
2706
|
-
var __privateAdd$2 = (obj, member, value) => {
|
|
2707
|
-
if (member.has(obj))
|
|
2708
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
2709
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
2710
|
-
};
|
|
2711
|
-
var _entries;
|
|
2712
2358
|
const MIN_TOKEN_LENGTH = 8;
|
|
2713
2359
|
class StaticTokenHandler {
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
const
|
|
2360
|
+
#entries = /* @__PURE__ */ new Map();
|
|
2361
|
+
add(config) {
|
|
2362
|
+
const token = config.getString("options.token");
|
|
2363
|
+
const subject = config.getString("options.subject");
|
|
2364
|
+
const allAccessRestrictions = readAccessRestrictionsFromConfig(config);
|
|
2719
2365
|
if (!token.match(/^\S+$/)) {
|
|
2720
2366
|
throw new Error("Illegal token, must be a set of non-space characters");
|
|
2721
|
-
}
|
|
2722
|
-
if (token.length < MIN_TOKEN_LENGTH) {
|
|
2367
|
+
} else if (token.length < MIN_TOKEN_LENGTH) {
|
|
2723
2368
|
throw new Error(
|
|
2724
2369
|
`Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`
|
|
2725
2370
|
);
|
|
2726
|
-
}
|
|
2727
|
-
const subject = options.getString("subject");
|
|
2728
|
-
if (!subject.match(/^\S+$/)) {
|
|
2371
|
+
} else if (!subject.match(/^\S+$/)) {
|
|
2729
2372
|
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
2373
|
+
} else if (this.#entries.has(token)) {
|
|
2374
|
+
throw new Error(
|
|
2375
|
+
"Static externalAccess token was declared more than once"
|
|
2376
|
+
);
|
|
2730
2377
|
}
|
|
2731
|
-
|
|
2378
|
+
this.#entries.set(token, { subject, allAccessRestrictions });
|
|
2732
2379
|
}
|
|
2733
2380
|
async verifyToken(token) {
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2381
|
+
return this.#entries.get(token);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
class JWKSHandler {
|
|
2386
|
+
#entries = [];
|
|
2387
|
+
add(config) {
|
|
2388
|
+
if (!config.getString("options.url").match(/^\S+$/)) {
|
|
2389
|
+
throw new Error(
|
|
2390
|
+
"Illegal JWKS URL, must be a set of non-space characters"
|
|
2391
|
+
);
|
|
2737
2392
|
}
|
|
2738
|
-
|
|
2393
|
+
const algorithms = readStringOrStringArrayFromConfig(
|
|
2394
|
+
config,
|
|
2395
|
+
"options.algorithm"
|
|
2396
|
+
);
|
|
2397
|
+
const issuers = readStringOrStringArrayFromConfig(config, "options.issuer");
|
|
2398
|
+
const audiences = readStringOrStringArrayFromConfig(
|
|
2399
|
+
config,
|
|
2400
|
+
"options.audience"
|
|
2401
|
+
);
|
|
2402
|
+
const subjectPrefix = config.getOptionalString("options.subjectPrefix");
|
|
2403
|
+
const url = new URL(config.getString("options.url"));
|
|
2404
|
+
const jwks = jose.createRemoteJWKSet(url);
|
|
2405
|
+
const allAccessRestrictions = readAccessRestrictionsFromConfig(config);
|
|
2406
|
+
this.#entries.push({
|
|
2407
|
+
algorithms,
|
|
2408
|
+
audiences,
|
|
2409
|
+
issuers,
|
|
2410
|
+
jwks,
|
|
2411
|
+
subjectPrefix,
|
|
2412
|
+
url,
|
|
2413
|
+
allAccessRestrictions
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
async verifyToken(token) {
|
|
2417
|
+
for (const entry of this.#entries) {
|
|
2418
|
+
try {
|
|
2419
|
+
const {
|
|
2420
|
+
payload: { sub }
|
|
2421
|
+
} = await jose.jwtVerify(token, entry.jwks, {
|
|
2422
|
+
algorithms: entry.algorithms,
|
|
2423
|
+
issuer: entry.issuers,
|
|
2424
|
+
audience: entry.audiences
|
|
2425
|
+
});
|
|
2426
|
+
if (sub) {
|
|
2427
|
+
const prefix = entry.subjectPrefix ? `external:${entry.subjectPrefix}:` : "external:";
|
|
2428
|
+
return {
|
|
2429
|
+
subject: `${prefix}${sub}`,
|
|
2430
|
+
allAccessRestrictions: entry.allAccessRestrictions
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
} catch {
|
|
2434
|
+
continue;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
return void 0;
|
|
2739
2438
|
}
|
|
2740
2439
|
}
|
|
2741
|
-
_entries = new WeakMap();
|
|
2742
2440
|
|
|
2743
2441
|
const NEW_CONFIG_KEY = "backend.auth.externalAccess";
|
|
2744
2442
|
const OLD_CONFIG_KEY = "backend.auth.keys";
|
|
2443
|
+
let loggedDeprecationWarning = false;
|
|
2745
2444
|
class ExternalTokenHandler {
|
|
2746
|
-
constructor(handlers) {
|
|
2445
|
+
constructor(ownPluginId, handlers) {
|
|
2446
|
+
this.ownPluginId = ownPluginId;
|
|
2747
2447
|
this.handlers = handlers;
|
|
2748
2448
|
}
|
|
2749
2449
|
static create(options) {
|
|
2750
|
-
|
|
2751
|
-
const { config, logger } = options;
|
|
2450
|
+
const { ownPluginId, config, logger } = options;
|
|
2752
2451
|
const staticHandler = new StaticTokenHandler();
|
|
2753
2452
|
const legacyHandler = new LegacyTokenHandler();
|
|
2453
|
+
const jwksHandler = new JWKSHandler();
|
|
2754
2454
|
const handlers = {
|
|
2755
2455
|
static: staticHandler,
|
|
2756
|
-
legacy: legacyHandler
|
|
2456
|
+
legacy: legacyHandler,
|
|
2457
|
+
jwks: jwksHandler
|
|
2757
2458
|
};
|
|
2758
|
-
const handlerConfigs =
|
|
2459
|
+
const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];
|
|
2759
2460
|
for (const handlerConfig of handlerConfigs) {
|
|
2760
2461
|
const type = handlerConfig.getString("type");
|
|
2761
2462
|
const handler = handlers[type];
|
|
@@ -2765,10 +2466,11 @@ class ExternalTokenHandler {
|
|
|
2765
2466
|
`Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`
|
|
2766
2467
|
);
|
|
2767
2468
|
}
|
|
2768
|
-
handler.add(handlerConfig
|
|
2469
|
+
handler.add(handlerConfig);
|
|
2769
2470
|
}
|
|
2770
|
-
const legacyConfigs =
|
|
2771
|
-
if (legacyConfigs.length) {
|
|
2471
|
+
const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];
|
|
2472
|
+
if (legacyConfigs.length && !loggedDeprecationWarning) {
|
|
2473
|
+
loggedDeprecationWarning = true;
|
|
2772
2474
|
logger.warn(
|
|
2773
2475
|
`DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`
|
|
2774
2476
|
);
|
|
@@ -2776,13 +2478,29 @@ class ExternalTokenHandler {
|
|
|
2776
2478
|
for (const handlerConfig of legacyConfigs) {
|
|
2777
2479
|
legacyHandler.addOld(handlerConfig);
|
|
2778
2480
|
}
|
|
2779
|
-
return new ExternalTokenHandler(Object.values(handlers));
|
|
2481
|
+
return new ExternalTokenHandler(ownPluginId, Object.values(handlers));
|
|
2780
2482
|
}
|
|
2781
2483
|
async verifyToken(token) {
|
|
2782
2484
|
for (const handler of this.handlers) {
|
|
2783
2485
|
const result = await handler.verifyToken(token);
|
|
2784
2486
|
if (result) {
|
|
2785
|
-
|
|
2487
|
+
const { allAccessRestrictions, ...rest } = result;
|
|
2488
|
+
if (allAccessRestrictions) {
|
|
2489
|
+
const accessRestrictions = allAccessRestrictions.get(
|
|
2490
|
+
this.ownPluginId
|
|
2491
|
+
);
|
|
2492
|
+
if (!accessRestrictions) {
|
|
2493
|
+
const valid = [...allAccessRestrictions.keys()].map((k) => `'${k}'`).join(", ");
|
|
2494
|
+
throw new errors.NotAllowedError(
|
|
2495
|
+
`This token's access is restricted to plugin(s) ${valid}`
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
return {
|
|
2499
|
+
...rest,
|
|
2500
|
+
accessRestrictions
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
return rest;
|
|
2786
2504
|
}
|
|
2787
2505
|
}
|
|
2788
2506
|
return void 0;
|
|
@@ -2803,16 +2521,7 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
2803
2521
|
// new auth services in the new backend system.
|
|
2804
2522
|
tokenManager: backendPluginApi.coreServices.tokenManager
|
|
2805
2523
|
},
|
|
2806
|
-
async
|
|
2807
|
-
const externalTokens = ExternalTokenHandler.create({
|
|
2808
|
-
config,
|
|
2809
|
-
logger
|
|
2810
|
-
});
|
|
2811
|
-
return {
|
|
2812
|
-
externalTokens
|
|
2813
|
-
};
|
|
2814
|
-
},
|
|
2815
|
-
async factory({ config, discovery, plugin, tokenManager, logger, database }, { externalTokens }) {
|
|
2524
|
+
async factory({ config, discovery, plugin, tokenManager, logger, database }) {
|
|
2816
2525
|
const disableDefaultAuthPolicy = Boolean(
|
|
2817
2526
|
config.getOptionalBoolean(
|
|
2818
2527
|
"backend.auth.dangerouslyDisableDefaultAuthPolicy"
|
|
@@ -2832,6 +2541,11 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
2832
2541
|
publicKeyStore,
|
|
2833
2542
|
discovery
|
|
2834
2543
|
});
|
|
2544
|
+
const externalTokens = ExternalTokenHandler.create({
|
|
2545
|
+
ownPluginId: plugin.getId(),
|
|
2546
|
+
config,
|
|
2547
|
+
logger
|
|
2548
|
+
});
|
|
2835
2549
|
return new DefaultAuthService(
|
|
2836
2550
|
userTokens,
|
|
2837
2551
|
pluginTokens,
|
|
@@ -2848,10 +2562,11 @@ const cacheServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
2848
2562
|
service: backendPluginApi.coreServices.cache,
|
|
2849
2563
|
deps: {
|
|
2850
2564
|
config: backendPluginApi.coreServices.rootConfig,
|
|
2565
|
+
logger: backendPluginApi.coreServices.rootLogger,
|
|
2851
2566
|
plugin: backendPluginApi.coreServices.pluginMetadata
|
|
2852
2567
|
},
|
|
2853
|
-
async createRootContext({ config }) {
|
|
2854
|
-
return backendCommon.CacheManager.fromConfig(config);
|
|
2568
|
+
async createRootContext({ config, logger }) {
|
|
2569
|
+
return backendCommon.CacheManager.fromConfig(config, { logger });
|
|
2855
2570
|
},
|
|
2856
2571
|
async factory({ plugin }, manager) {
|
|
2857
2572
|
return manager.forPlugin(plugin.getId()).getClient();
|
|
@@ -2864,9 +2579,9 @@ const rootConfigServiceFactory = backendPluginApi.createServiceFactory(
|
|
|
2864
2579
|
deps: {},
|
|
2865
2580
|
async factory() {
|
|
2866
2581
|
const source = configLoader.ConfigSources.default({
|
|
2867
|
-
argv: options
|
|
2868
|
-
remote: options
|
|
2869
|
-
watch: options
|
|
2582
|
+
argv: options?.argv,
|
|
2583
|
+
remote: options?.remote,
|
|
2584
|
+
watch: options?.watch
|
|
2870
2585
|
});
|
|
2871
2586
|
console.log(`Loading config from ${source}`);
|
|
2872
2587
|
return await configLoader.ConfigSources.toConfig(source);
|
|
@@ -2929,8 +2644,7 @@ class HostDiscovery {
|
|
|
2929
2644
|
* path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
|
|
2930
2645
|
*/
|
|
2931
2646
|
static fromConfig(config, options) {
|
|
2932
|
-
|
|
2933
|
-
const basePath = (_a = options == null ? void 0 : options.basePath) != null ? _a : "/api";
|
|
2647
|
+
const basePath = options?.basePath ?? "/api";
|
|
2934
2648
|
const externalBaseUrl = config.getString("backend.baseUrl").replace(/\/+$/, "");
|
|
2935
2649
|
const {
|
|
2936
2650
|
listen: { host: listenHost = "::", port: listenPort }
|
|
@@ -2953,9 +2667,8 @@ class HostDiscovery {
|
|
|
2953
2667
|
);
|
|
2954
2668
|
}
|
|
2955
2669
|
getTargetFromConfig(pluginId, type) {
|
|
2956
|
-
|
|
2957
|
-
const
|
|
2958
|
-
const target = (_b = endpoints == null ? void 0 : endpoints.find((endpoint) => endpoint.getStringArray("plugins").includes(pluginId))) == null ? void 0 : _b.get("target");
|
|
2670
|
+
const endpoints = this.discoveryConfig?.getOptionalConfigArray("endpoints");
|
|
2671
|
+
const target = endpoints?.find((endpoint) => endpoint.getStringArray("plugins").includes(pluginId))?.get("target");
|
|
2959
2672
|
if (!target) {
|
|
2960
2673
|
const baseUrl = type === "external" ? this.externalBaseUrl : this.internalBaseUrl;
|
|
2961
2674
|
return `${baseUrl}/${encodeURIComponent(pluginId)}`;
|
|
@@ -2989,36 +2702,13 @@ const discoveryServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
2989
2702
|
}
|
|
2990
2703
|
});
|
|
2991
2704
|
|
|
2992
|
-
var __accessCheck$1 = (obj, member, msg) => {
|
|
2993
|
-
if (!member.has(obj))
|
|
2994
|
-
throw TypeError("Cannot " + msg);
|
|
2995
|
-
};
|
|
2996
|
-
var __privateGet$1 = (obj, member, getter) => {
|
|
2997
|
-
__accessCheck$1(obj, member, "read from private field");
|
|
2998
|
-
return member.get(obj);
|
|
2999
|
-
};
|
|
3000
|
-
var __privateAdd$1 = (obj, member, value) => {
|
|
3001
|
-
if (member.has(obj))
|
|
3002
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
3003
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
3004
|
-
};
|
|
3005
|
-
var __privateSet$1 = (obj, member, value, setter) => {
|
|
3006
|
-
__accessCheck$1(obj, member, "write to private field");
|
|
3007
|
-
member.set(obj, value);
|
|
3008
|
-
return value;
|
|
3009
|
-
};
|
|
3010
|
-
var __privateMethod$1 = (obj, member, method) => {
|
|
3011
|
-
__accessCheck$1(obj, member, "access private method");
|
|
3012
|
-
return method;
|
|
3013
|
-
};
|
|
3014
|
-
var _auth, _discovery, _pluginId, _extractCredentialsFromRequest, extractCredentialsFromRequest_fn, _extractLimitedCredentialsFromRequest, extractLimitedCredentialsFromRequest_fn, _getCredentials, getCredentials_fn, _getLimitedCredentials, getLimitedCredentials_fn, _getCookieOptions, getCookieOptions_fn, _existingCookieExpiration, existingCookieExpiration_fn;
|
|
3015
2705
|
const FIVE_MINUTES_MS = 5 * 60 * 1e3;
|
|
3016
2706
|
const BACKSTAGE_AUTH_COOKIE = "backstage-auth";
|
|
3017
2707
|
function getTokenFromRequest(req) {
|
|
3018
2708
|
const authHeader = req.headers.authorization;
|
|
3019
2709
|
if (typeof authHeader === "string") {
|
|
3020
2710
|
const matches = authHeader.match(/^Bearer[ ]+(\S+)$/i);
|
|
3021
|
-
const token = matches
|
|
2711
|
+
const token = matches?.[1];
|
|
3022
2712
|
if (token) {
|
|
3023
2713
|
return token;
|
|
3024
2714
|
}
|
|
@@ -3042,39 +2732,61 @@ function willExpireSoon(expiresAt) {
|
|
|
3042
2732
|
const credentialsSymbol = Symbol("backstage-credentials");
|
|
3043
2733
|
const limitedCredentialsSymbol = Symbol("backstage-limited-credentials");
|
|
3044
2734
|
class DefaultHttpAuthService {
|
|
2735
|
+
#auth;
|
|
2736
|
+
#discovery;
|
|
2737
|
+
#pluginId;
|
|
3045
2738
|
constructor(auth, discovery, pluginId) {
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
2739
|
+
this.#auth = auth;
|
|
2740
|
+
this.#discovery = discovery;
|
|
2741
|
+
this.#pluginId = pluginId;
|
|
2742
|
+
}
|
|
2743
|
+
async #extractCredentialsFromRequest(req) {
|
|
2744
|
+
const token = getTokenFromRequest(req);
|
|
2745
|
+
if (!token) {
|
|
2746
|
+
return await this.#auth.getNoneCredentials();
|
|
2747
|
+
}
|
|
2748
|
+
return await this.#auth.authenticate(token);
|
|
2749
|
+
}
|
|
2750
|
+
async #extractLimitedCredentialsFromRequest(req) {
|
|
2751
|
+
const token = getTokenFromRequest(req);
|
|
2752
|
+
if (token) {
|
|
2753
|
+
return await this.#auth.authenticate(token, {
|
|
2754
|
+
allowLimitedAccess: true
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
const cookie = getCookieFromRequest(req);
|
|
2758
|
+
if (cookie) {
|
|
2759
|
+
return await this.#auth.authenticate(cookie, {
|
|
2760
|
+
allowLimitedAccess: true
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
return await this.#auth.getNoneCredentials();
|
|
2764
|
+
}
|
|
2765
|
+
async #getCredentials(req) {
|
|
2766
|
+
return req[credentialsSymbol] ??= this.#extractCredentialsFromRequest(req);
|
|
2767
|
+
}
|
|
2768
|
+
async #getLimitedCredentials(req) {
|
|
2769
|
+
return req[limitedCredentialsSymbol] ??= this.#extractLimitedCredentialsFromRequest(req);
|
|
3058
2770
|
}
|
|
3059
2771
|
async credentials(req, options) {
|
|
3060
|
-
const credentials =
|
|
3061
|
-
const allowed = options
|
|
2772
|
+
const credentials = options?.allowLimitedAccess ? await this.#getLimitedCredentials(req) : await this.#getCredentials(req);
|
|
2773
|
+
const allowed = options?.allow;
|
|
3062
2774
|
if (!allowed) {
|
|
3063
2775
|
return credentials;
|
|
3064
2776
|
}
|
|
3065
|
-
if (
|
|
2777
|
+
if (this.#auth.isPrincipal(credentials, "none")) {
|
|
3066
2778
|
if (allowed.includes("none")) {
|
|
3067
2779
|
return credentials;
|
|
3068
2780
|
}
|
|
3069
2781
|
throw new errors.AuthenticationError("Missing credentials");
|
|
3070
|
-
} else if (
|
|
2782
|
+
} else if (this.#auth.isPrincipal(credentials, "user")) {
|
|
3071
2783
|
if (allowed.includes("user")) {
|
|
3072
2784
|
return credentials;
|
|
3073
2785
|
}
|
|
3074
2786
|
throw new errors.NotAllowedError(
|
|
3075
2787
|
`This endpoint does not allow 'user' credentials`
|
|
3076
2788
|
);
|
|
3077
|
-
} else if (
|
|
2789
|
+
} else if (this.#auth.isPrincipal(credentials, "service")) {
|
|
3078
2790
|
if (allowed.includes("service")) {
|
|
3079
2791
|
return credentials;
|
|
3080
2792
|
}
|
|
@@ -3091,15 +2803,15 @@ class DefaultHttpAuthService {
|
|
|
3091
2803
|
throw new Error("Failed to issue user cookie, headers were already sent");
|
|
3092
2804
|
}
|
|
3093
2805
|
let credentials;
|
|
3094
|
-
if (options
|
|
3095
|
-
if (
|
|
2806
|
+
if (options?.credentials) {
|
|
2807
|
+
if (this.#auth.isPrincipal(options.credentials, "none")) {
|
|
3096
2808
|
res.clearCookie(
|
|
3097
2809
|
BACKSTAGE_AUTH_COOKIE,
|
|
3098
|
-
await
|
|
2810
|
+
await this.#getCookieOptions(res.req)
|
|
3099
2811
|
);
|
|
3100
2812
|
return { expiresAt: /* @__PURE__ */ new Date() };
|
|
3101
2813
|
}
|
|
3102
|
-
if (!
|
|
2814
|
+
if (!this.#auth.isPrincipal(options.credentials, "user")) {
|
|
3103
2815
|
throw new errors.AuthenticationError(
|
|
3104
2816
|
"Refused to issue cookie for non-user principal"
|
|
3105
2817
|
);
|
|
@@ -3108,99 +2820,60 @@ class DefaultHttpAuthService {
|
|
|
3108
2820
|
} else {
|
|
3109
2821
|
credentials = await this.credentials(res.req, { allow: ["user"] });
|
|
3110
2822
|
}
|
|
3111
|
-
const existingExpiresAt = await
|
|
2823
|
+
const existingExpiresAt = await this.#existingCookieExpiration(res.req);
|
|
3112
2824
|
if (existingExpiresAt && !willExpireSoon(existingExpiresAt)) {
|
|
3113
2825
|
return { expiresAt: existingExpiresAt };
|
|
3114
2826
|
}
|
|
3115
|
-
const { token, expiresAt } = await
|
|
2827
|
+
const { token, expiresAt } = await this.#auth.getLimitedUserToken(
|
|
3116
2828
|
credentials
|
|
3117
2829
|
);
|
|
3118
2830
|
if (!token) {
|
|
3119
2831
|
throw new Error("User credentials is unexpectedly missing token");
|
|
3120
2832
|
}
|
|
3121
2833
|
res.cookie(BACKSTAGE_AUTH_COOKIE, token, {
|
|
3122
|
-
...await
|
|
2834
|
+
...await this.#getCookieOptions(res.req),
|
|
3123
2835
|
expires: expiresAt
|
|
3124
2836
|
});
|
|
3125
2837
|
return { expiresAt };
|
|
3126
2838
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
_pluginId = new WeakMap();
|
|
3131
|
-
_extractCredentialsFromRequest = new WeakSet();
|
|
3132
|
-
extractCredentialsFromRequest_fn = async function(req) {
|
|
3133
|
-
const token = getTokenFromRequest(req);
|
|
3134
|
-
if (!token) {
|
|
3135
|
-
return await __privateGet$1(this, _auth).getNoneCredentials();
|
|
3136
|
-
}
|
|
3137
|
-
return await __privateGet$1(this, _auth).authenticate(token);
|
|
3138
|
-
};
|
|
3139
|
-
_extractLimitedCredentialsFromRequest = new WeakSet();
|
|
3140
|
-
extractLimitedCredentialsFromRequest_fn = async function(req) {
|
|
3141
|
-
const token = getTokenFromRequest(req);
|
|
3142
|
-
if (token) {
|
|
3143
|
-
return await __privateGet$1(this, _auth).authenticate(token, {
|
|
3144
|
-
allowLimitedAccess: true
|
|
3145
|
-
});
|
|
3146
|
-
}
|
|
3147
|
-
const cookie = getCookieFromRequest(req);
|
|
3148
|
-
if (cookie) {
|
|
3149
|
-
return await __privateGet$1(this, _auth).authenticate(cookie, {
|
|
3150
|
-
allowLimitedAccess: true
|
|
3151
|
-
});
|
|
3152
|
-
}
|
|
3153
|
-
return await __privateGet$1(this, _auth).getNoneCredentials();
|
|
3154
|
-
};
|
|
3155
|
-
_getCredentials = new WeakSet();
|
|
3156
|
-
getCredentials_fn = async function(req) {
|
|
3157
|
-
var _a;
|
|
3158
|
-
return (_a = req[credentialsSymbol]) != null ? _a : req[credentialsSymbol] = __privateMethod$1(this, _extractCredentialsFromRequest, extractCredentialsFromRequest_fn).call(this, req);
|
|
3159
|
-
};
|
|
3160
|
-
_getLimitedCredentials = new WeakSet();
|
|
3161
|
-
getLimitedCredentials_fn = async function(req) {
|
|
3162
|
-
var _a;
|
|
3163
|
-
return (_a = req[limitedCredentialsSymbol]) != null ? _a : req[limitedCredentialsSymbol] = __privateMethod$1(this, _extractLimitedCredentialsFromRequest, extractLimitedCredentialsFromRequest_fn).call(this, req);
|
|
3164
|
-
};
|
|
3165
|
-
_getCookieOptions = new WeakSet();
|
|
3166
|
-
getCookieOptions_fn = async function(_req) {
|
|
3167
|
-
const externalBaseUrlStr = await __privateGet$1(this, _discovery).getExternalBaseUrl(
|
|
3168
|
-
__privateGet$1(this, _pluginId)
|
|
3169
|
-
);
|
|
3170
|
-
const externalBaseUrl = new URL(externalBaseUrlStr);
|
|
3171
|
-
const secure = externalBaseUrl.protocol === "https:" || externalBaseUrl.hostname === "localhost";
|
|
3172
|
-
return {
|
|
3173
|
-
domain: externalBaseUrl.hostname,
|
|
3174
|
-
httpOnly: true,
|
|
3175
|
-
secure,
|
|
3176
|
-
priority: "high",
|
|
3177
|
-
sameSite: secure ? "none" : "lax"
|
|
3178
|
-
};
|
|
3179
|
-
};
|
|
3180
|
-
_existingCookieExpiration = new WeakSet();
|
|
3181
|
-
existingCookieExpiration_fn = async function(req) {
|
|
3182
|
-
const existingCookie = getCookieFromRequest(req);
|
|
3183
|
-
if (!existingCookie) {
|
|
3184
|
-
return void 0;
|
|
3185
|
-
}
|
|
3186
|
-
try {
|
|
3187
|
-
const existingCredentials = await __privateGet$1(this, _auth).authenticate(
|
|
3188
|
-
existingCookie,
|
|
3189
|
-
{
|
|
3190
|
-
allowLimitedAccess: true
|
|
3191
|
-
}
|
|
2839
|
+
async #getCookieOptions(_req) {
|
|
2840
|
+
const externalBaseUrlStr = await this.#discovery.getExternalBaseUrl(
|
|
2841
|
+
this.#pluginId
|
|
3192
2842
|
);
|
|
3193
|
-
|
|
2843
|
+
const externalBaseUrl = new URL(externalBaseUrlStr);
|
|
2844
|
+
const secure = externalBaseUrl.protocol === "https:" || externalBaseUrl.hostname === "localhost";
|
|
2845
|
+
return {
|
|
2846
|
+
domain: externalBaseUrl.hostname,
|
|
2847
|
+
httpOnly: true,
|
|
2848
|
+
secure,
|
|
2849
|
+
priority: "high",
|
|
2850
|
+
sameSite: secure ? "none" : "lax"
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
async #existingCookieExpiration(req) {
|
|
2854
|
+
const existingCookie = getCookieFromRequest(req);
|
|
2855
|
+
if (!existingCookie) {
|
|
3194
2856
|
return void 0;
|
|
3195
2857
|
}
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
2858
|
+
try {
|
|
2859
|
+
const existingCredentials = await this.#auth.authenticate(
|
|
2860
|
+
existingCookie,
|
|
2861
|
+
{
|
|
2862
|
+
allowLimitedAccess: true
|
|
2863
|
+
}
|
|
2864
|
+
);
|
|
2865
|
+
if (!this.#auth.isPrincipal(existingCredentials, "user")) {
|
|
2866
|
+
return void 0;
|
|
2867
|
+
}
|
|
2868
|
+
return existingCredentials.expiresAt;
|
|
2869
|
+
} catch (error) {
|
|
2870
|
+
if (error.name === "AuthenticationError") {
|
|
2871
|
+
return void 0;
|
|
2872
|
+
}
|
|
2873
|
+
throw error;
|
|
3200
2874
|
}
|
|
3201
|
-
throw error;
|
|
3202
2875
|
}
|
|
3203
|
-
}
|
|
2876
|
+
}
|
|
3204
2877
|
const httpAuthServiceFactory = backendPluginApi.createServiceFactory({
|
|
3205
2878
|
service: backendPluginApi.coreServices.httpAuth,
|
|
3206
2879
|
deps: {
|
|
@@ -3360,13 +3033,12 @@ const httpRouterServiceFactory = backendPluginApi.createServiceFactory(
|
|
|
3360
3033
|
rootHttpRouter,
|
|
3361
3034
|
lifecycle
|
|
3362
3035
|
}) {
|
|
3363
|
-
|
|
3364
|
-
if (options == null ? void 0 : options.getPath) {
|
|
3036
|
+
if (options?.getPath) {
|
|
3365
3037
|
logger.warn(
|
|
3366
3038
|
`DEPRECATION WARNING: The 'getPath' option for HttpRouterService is deprecated. The ability to reconfigure the '/api/' path prefix for plugins will be removed in the future.`
|
|
3367
3039
|
);
|
|
3368
3040
|
}
|
|
3369
|
-
const getPath =
|
|
3041
|
+
const getPath = options?.getPath ?? ((id) => `/api/${id}`);
|
|
3370
3042
|
const path = getPath(plugin.getId());
|
|
3371
3043
|
const router = Router__default.default();
|
|
3372
3044
|
rootHttpRouter.use(path, router);
|
|
@@ -3402,6 +3074,63 @@ const identityServiceFactory = backendPluginApi.createServiceFactory(
|
|
|
3402
3074
|
})
|
|
3403
3075
|
);
|
|
3404
3076
|
|
|
3077
|
+
class BackendPluginLifecycleImpl {
|
|
3078
|
+
constructor(logger, rootLifecycle, pluginMetadata) {
|
|
3079
|
+
this.logger = logger;
|
|
3080
|
+
this.rootLifecycle = rootLifecycle;
|
|
3081
|
+
this.pluginMetadata = pluginMetadata;
|
|
3082
|
+
}
|
|
3083
|
+
#hasStarted = false;
|
|
3084
|
+
#startupTasks = [];
|
|
3085
|
+
addStartupHook(hook, options) {
|
|
3086
|
+
if (this.#hasStarted) {
|
|
3087
|
+
throw new Error("Attempted to add startup hook after startup");
|
|
3088
|
+
}
|
|
3089
|
+
this.#startupTasks.push({ hook, options });
|
|
3090
|
+
}
|
|
3091
|
+
async startup() {
|
|
3092
|
+
if (this.#hasStarted) {
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
this.#hasStarted = true;
|
|
3096
|
+
this.logger.debug(
|
|
3097
|
+
`Running ${this.#startupTasks.length} plugin startup tasks...`
|
|
3098
|
+
);
|
|
3099
|
+
await Promise.all(
|
|
3100
|
+
this.#startupTasks.map(async ({ hook, options }) => {
|
|
3101
|
+
const logger = options?.logger ?? this.logger;
|
|
3102
|
+
try {
|
|
3103
|
+
await hook();
|
|
3104
|
+
logger.debug(`Plugin startup hook succeeded`);
|
|
3105
|
+
} catch (error) {
|
|
3106
|
+
logger.error(`Plugin startup hook failed, ${error}`);
|
|
3107
|
+
}
|
|
3108
|
+
})
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
addShutdownHook(hook, options) {
|
|
3112
|
+
const plugin = this.pluginMetadata.getId();
|
|
3113
|
+
this.rootLifecycle.addShutdownHook(hook, {
|
|
3114
|
+
logger: options?.logger?.child({ plugin }) ?? this.logger
|
|
3115
|
+
});
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
const lifecycleServiceFactory = backendPluginApi.createServiceFactory({
|
|
3119
|
+
service: backendPluginApi.coreServices.lifecycle,
|
|
3120
|
+
deps: {
|
|
3121
|
+
logger: backendPluginApi.coreServices.logger,
|
|
3122
|
+
rootLifecycle: backendPluginApi.coreServices.rootLifecycle,
|
|
3123
|
+
pluginMetadata: backendPluginApi.coreServices.pluginMetadata
|
|
3124
|
+
},
|
|
3125
|
+
async factory({ rootLifecycle, logger, pluginMetadata }) {
|
|
3126
|
+
return new BackendPluginLifecycleImpl(
|
|
3127
|
+
logger,
|
|
3128
|
+
rootLifecycle,
|
|
3129
|
+
pluginMetadata
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
});
|
|
3133
|
+
|
|
3405
3134
|
const loggerServiceFactory = backendPluginApi.createServiceFactory({
|
|
3406
3135
|
service: backendPluginApi.coreServices.logger,
|
|
3407
3136
|
deps: {
|
|
@@ -3430,102 +3159,71 @@ const permissionsServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
3430
3159
|
}
|
|
3431
3160
|
});
|
|
3432
3161
|
|
|
3433
|
-
var __accessCheck = (obj, member, msg) => {
|
|
3434
|
-
if (!member.has(obj))
|
|
3435
|
-
throw TypeError("Cannot " + msg);
|
|
3436
|
-
};
|
|
3437
|
-
var __privateGet = (obj, member, getter) => {
|
|
3438
|
-
__accessCheck(obj, member, "read from private field");
|
|
3439
|
-
return member.get(obj);
|
|
3440
|
-
};
|
|
3441
|
-
var __privateAdd = (obj, member, value) => {
|
|
3442
|
-
if (member.has(obj))
|
|
3443
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
3444
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
3445
|
-
};
|
|
3446
|
-
var __privateSet = (obj, member, value, setter) => {
|
|
3447
|
-
__accessCheck(obj, member, "write to private field");
|
|
3448
|
-
member.set(obj, value);
|
|
3449
|
-
return value;
|
|
3450
|
-
};
|
|
3451
|
-
var __privateMethod = (obj, member, method) => {
|
|
3452
|
-
__accessCheck(obj, member, "access private method");
|
|
3453
|
-
return method;
|
|
3454
|
-
};
|
|
3455
|
-
var _indexPath, _router, _namedRoutes, _indexRouter, _existingPaths, _findConflictingPath, findConflictingPath_fn;
|
|
3456
3162
|
function normalizePath(path) {
|
|
3457
3163
|
return `${trimEnd__default.default(path, "/")}/`;
|
|
3458
3164
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
__privateAdd(this, _indexRouter, express.Router());
|
|
3466
|
-
__privateAdd(this, _existingPaths, new Array());
|
|
3467
|
-
__privateSet(this, _indexPath, indexPath);
|
|
3468
|
-
__privateGet(this, _router).use(__privateGet(this, _namedRoutes));
|
|
3469
|
-
__privateGet(this, _router).use("/api/", (_req, _res, next) => {
|
|
3470
|
-
next("router");
|
|
3471
|
-
});
|
|
3472
|
-
if (__privateGet(this, _indexPath)) {
|
|
3473
|
-
__privateGet(this, _router).use(__privateGet(this, _indexRouter));
|
|
3474
|
-
}
|
|
3475
|
-
}
|
|
3165
|
+
class DefaultRootHttpRouter {
|
|
3166
|
+
#indexPath;
|
|
3167
|
+
#router = express.Router();
|
|
3168
|
+
#namedRoutes = express.Router();
|
|
3169
|
+
#indexRouter = express.Router();
|
|
3170
|
+
#existingPaths = new Array();
|
|
3476
3171
|
static create(options) {
|
|
3477
3172
|
let indexPath;
|
|
3478
|
-
if (
|
|
3173
|
+
if (options?.indexPath === false) {
|
|
3479
3174
|
indexPath = void 0;
|
|
3480
|
-
} else if (
|
|
3175
|
+
} else if (options?.indexPath === void 0) {
|
|
3481
3176
|
indexPath = "/api/app";
|
|
3482
|
-
} else if (
|
|
3177
|
+
} else if (options?.indexPath === "") {
|
|
3483
3178
|
throw new Error("indexPath option may not be an empty string");
|
|
3484
3179
|
} else {
|
|
3485
3180
|
indexPath = options.indexPath;
|
|
3486
3181
|
}
|
|
3487
|
-
return new
|
|
3182
|
+
return new DefaultRootHttpRouter(indexPath);
|
|
3183
|
+
}
|
|
3184
|
+
constructor(indexPath) {
|
|
3185
|
+
this.#indexPath = indexPath;
|
|
3186
|
+
this.#router.use(this.#namedRoutes);
|
|
3187
|
+
this.#router.use("/api/", (_req, _res, next) => {
|
|
3188
|
+
next("router");
|
|
3189
|
+
});
|
|
3190
|
+
if (this.#indexPath) {
|
|
3191
|
+
this.#router.use(this.#indexRouter);
|
|
3192
|
+
}
|
|
3488
3193
|
}
|
|
3489
3194
|
use(path, handler) {
|
|
3490
3195
|
if (path.match(/^[/\s]*$/)) {
|
|
3491
3196
|
throw new Error(`Root router path may not be empty`);
|
|
3492
3197
|
}
|
|
3493
|
-
const conflictingPath =
|
|
3198
|
+
const conflictingPath = this.#findConflictingPath(path);
|
|
3494
3199
|
if (conflictingPath) {
|
|
3495
3200
|
throw new Error(
|
|
3496
3201
|
`Path ${path} conflicts with the existing path ${conflictingPath}`
|
|
3497
3202
|
);
|
|
3498
3203
|
}
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
if (
|
|
3502
|
-
|
|
3204
|
+
this.#existingPaths.push(path);
|
|
3205
|
+
this.#namedRoutes.use(path, handler);
|
|
3206
|
+
if (this.#indexPath === path) {
|
|
3207
|
+
this.#indexRouter.use(handler);
|
|
3503
3208
|
}
|
|
3504
3209
|
}
|
|
3505
3210
|
handler() {
|
|
3506
|
-
return
|
|
3507
|
-
}
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
const normalizedPath = normalizePath(path);
|
|
3519
|
-
if (normalizedPath.startsWith(normalizedNewPath)) {
|
|
3520
|
-
return path;
|
|
3521
|
-
}
|
|
3522
|
-
if (normalizedNewPath.startsWith(normalizedPath)) {
|
|
3523
|
-
return path;
|
|
3211
|
+
return this.#router;
|
|
3212
|
+
}
|
|
3213
|
+
#findConflictingPath(newPath) {
|
|
3214
|
+
const normalizedNewPath = normalizePath(newPath);
|
|
3215
|
+
for (const path of this.#existingPaths) {
|
|
3216
|
+
const normalizedPath = normalizePath(path);
|
|
3217
|
+
if (normalizedPath.startsWith(normalizedNewPath)) {
|
|
3218
|
+
return path;
|
|
3219
|
+
}
|
|
3220
|
+
if (normalizedNewPath.startsWith(normalizedPath)) {
|
|
3221
|
+
return path;
|
|
3222
|
+
}
|
|
3524
3223
|
}
|
|
3224
|
+
return void 0;
|
|
3525
3225
|
}
|
|
3526
|
-
|
|
3527
|
-
};
|
|
3528
|
-
let DefaultRootHttpRouter = _DefaultRootHttpRouter;
|
|
3226
|
+
}
|
|
3529
3227
|
|
|
3530
3228
|
function defaultConfigure({ applyDefaults }) {
|
|
3531
3229
|
applyDefaults();
|
|
@@ -3539,7 +3237,7 @@ const rootHttpRouterServiceFactory = backendPluginApi.createServiceFactory(
|
|
|
3539
3237
|
lifecycle: backendPluginApi.coreServices.rootLifecycle
|
|
3540
3238
|
},
|
|
3541
3239
|
async factory({ config, rootLogger, lifecycle }) {
|
|
3542
|
-
const { indexPath, configure = defaultConfigure } = options
|
|
3240
|
+
const { indexPath, configure = defaultConfigure } = options ?? {};
|
|
3543
3241
|
const logger = rootLogger.child({ service: "rootHttpRouter" });
|
|
3544
3242
|
const app = express__default.default();
|
|
3545
3243
|
const router = DefaultRootHttpRouter.create({ indexPath });
|
|
@@ -3575,13 +3273,81 @@ const rootHttpRouterServiceFactory = backendPluginApi.createServiceFactory(
|
|
|
3575
3273
|
})
|
|
3576
3274
|
);
|
|
3577
3275
|
|
|
3276
|
+
class BackendLifecycleImpl {
|
|
3277
|
+
constructor(logger) {
|
|
3278
|
+
this.logger = logger;
|
|
3279
|
+
}
|
|
3280
|
+
#hasStarted = false;
|
|
3281
|
+
#startupTasks = [];
|
|
3282
|
+
addStartupHook(hook, options) {
|
|
3283
|
+
if (this.#hasStarted) {
|
|
3284
|
+
throw new Error("Attempted to add startup hook after startup");
|
|
3285
|
+
}
|
|
3286
|
+
this.#startupTasks.push({ hook, options });
|
|
3287
|
+
}
|
|
3288
|
+
async startup() {
|
|
3289
|
+
if (this.#hasStarted) {
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
this.#hasStarted = true;
|
|
3293
|
+
this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`);
|
|
3294
|
+
await Promise.all(
|
|
3295
|
+
this.#startupTasks.map(async ({ hook, options }) => {
|
|
3296
|
+
const logger = options?.logger ?? this.logger;
|
|
3297
|
+
try {
|
|
3298
|
+
await hook();
|
|
3299
|
+
logger.debug(`Startup hook succeeded`);
|
|
3300
|
+
} catch (error) {
|
|
3301
|
+
logger.error(`Startup hook failed, ${error}`);
|
|
3302
|
+
}
|
|
3303
|
+
})
|
|
3304
|
+
);
|
|
3305
|
+
}
|
|
3306
|
+
#hasShutdown = false;
|
|
3307
|
+
#shutdownTasks = [];
|
|
3308
|
+
addShutdownHook(hook, options) {
|
|
3309
|
+
if (this.#hasShutdown) {
|
|
3310
|
+
throw new Error("Attempted to add shutdown hook after shutdown");
|
|
3311
|
+
}
|
|
3312
|
+
this.#shutdownTasks.push({ hook, options });
|
|
3313
|
+
}
|
|
3314
|
+
async shutdown() {
|
|
3315
|
+
if (this.#hasShutdown) {
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
this.#hasShutdown = true;
|
|
3319
|
+
this.logger.debug(
|
|
3320
|
+
`Running ${this.#shutdownTasks.length} shutdown tasks...`
|
|
3321
|
+
);
|
|
3322
|
+
await Promise.all(
|
|
3323
|
+
this.#shutdownTasks.map(async ({ hook, options }) => {
|
|
3324
|
+
const logger = options?.logger ?? this.logger;
|
|
3325
|
+
try {
|
|
3326
|
+
await hook();
|
|
3327
|
+
logger.debug(`Shutdown hook succeeded`);
|
|
3328
|
+
} catch (error) {
|
|
3329
|
+
logger.error(`Shutdown hook failed, ${error}`);
|
|
3330
|
+
}
|
|
3331
|
+
})
|
|
3332
|
+
);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
const rootLifecycleServiceFactory = backendPluginApi.createServiceFactory({
|
|
3336
|
+
service: backendPluginApi.coreServices.rootLifecycle,
|
|
3337
|
+
deps: {
|
|
3338
|
+
logger: backendPluginApi.coreServices.rootLogger
|
|
3339
|
+
},
|
|
3340
|
+
async factory({ logger }) {
|
|
3341
|
+
return new BackendLifecycleImpl(logger);
|
|
3342
|
+
}
|
|
3343
|
+
});
|
|
3344
|
+
|
|
3578
3345
|
const rootLoggerServiceFactory = backendPluginApi.createServiceFactory({
|
|
3579
3346
|
service: backendPluginApi.coreServices.rootLogger,
|
|
3580
3347
|
deps: {
|
|
3581
3348
|
config: backendPluginApi.coreServices.rootConfig
|
|
3582
3349
|
},
|
|
3583
3350
|
async factory({ config }) {
|
|
3584
|
-
var _a;
|
|
3585
3351
|
const logger = WinstonLogger.create({
|
|
3586
3352
|
meta: {
|
|
3587
3353
|
service: "backstage"
|
|
@@ -3592,27 +3358,11 @@ const rootLoggerServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
3592
3358
|
});
|
|
3593
3359
|
const secretEnumerator = await createConfigSecretEnumerator({ logger });
|
|
3594
3360
|
logger.addRedactions(secretEnumerator(config));
|
|
3595
|
-
|
|
3361
|
+
config.subscribe?.(() => logger.addRedactions(secretEnumerator(config)));
|
|
3596
3362
|
return logger;
|
|
3597
3363
|
}
|
|
3598
3364
|
});
|
|
3599
3365
|
|
|
3600
|
-
const schedulerServiceFactory = backendPluginApi.createServiceFactory({
|
|
3601
|
-
service: backendPluginApi.coreServices.scheduler,
|
|
3602
|
-
deps: {
|
|
3603
|
-
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
3604
|
-
databaseManager: backendPluginApi.coreServices.database,
|
|
3605
|
-
logger: backendPluginApi.coreServices.logger
|
|
3606
|
-
},
|
|
3607
|
-
async factory({ plugin, databaseManager, logger }) {
|
|
3608
|
-
return backendTasks.TaskScheduler.forPlugin({
|
|
3609
|
-
pluginId: plugin.getId(),
|
|
3610
|
-
databaseManager,
|
|
3611
|
-
logger
|
|
3612
|
-
});
|
|
3613
|
-
}
|
|
3614
|
-
});
|
|
3615
|
-
|
|
3616
3366
|
const tokenManagerServiceFactory = backendPluginApi.createServiceFactory({
|
|
3617
3367
|
service: backendPluginApi.coreServices.tokenManager,
|
|
3618
3368
|
deps: {
|
|
@@ -3673,6 +3423,22 @@ const userInfoServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
3673
3423
|
}
|
|
3674
3424
|
});
|
|
3675
3425
|
|
|
3426
|
+
const schedulerServiceFactory = backendPluginApi.createServiceFactory({
|
|
3427
|
+
service: backendPluginApi.coreServices.scheduler,
|
|
3428
|
+
deps: {
|
|
3429
|
+
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
3430
|
+
databaseManager: backendPluginApi.coreServices.database,
|
|
3431
|
+
logger: backendPluginApi.coreServices.logger
|
|
3432
|
+
},
|
|
3433
|
+
async factory({ plugin, databaseManager, logger }) {
|
|
3434
|
+
return backendTasks.TaskScheduler.forPlugin({
|
|
3435
|
+
pluginId: plugin.getId(),
|
|
3436
|
+
databaseManager,
|
|
3437
|
+
logger
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
3441
|
+
|
|
3676
3442
|
exports.DefaultRootHttpRouter = DefaultRootHttpRouter;
|
|
3677
3443
|
exports.HostDiscovery = HostDiscovery;
|
|
3678
3444
|
exports.MiddlewareFactory = MiddlewareFactory;
|