@pstefans/kuadrant-backstage-plugin-backend-dynamic 0.0.2-dev

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.
@@ -0,0 +1,1151 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var pluginPermissionCommon = require('@backstage/plugin-permission-common');
5
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
6
+ var zod = require('zod');
7
+ var express = require('express');
8
+ var Router = require('express-promise-router');
9
+ var cors = require('cors');
10
+ var crypto = require('crypto');
11
+ var k8sClient = require('./k8s-client.cjs.js');
12
+ var module$1 = require('./module.cjs.js');
13
+ var permissions = require('./permissions.cjs.js');
14
+
15
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
16
+
17
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
18
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
19
+ var cors__default = /*#__PURE__*/_interopDefaultCompat(cors);
20
+
21
+ const secretKey = "api_key";
22
+ function extractNameFromEntityRef(entityRef) {
23
+ const parts = entityRef.split("/");
24
+ return parts[parts.length - 1];
25
+ }
26
+ async function getUserIdentity(req, httpAuth, userInfo) {
27
+ const credentials = await httpAuth.credentials(req);
28
+ if (!credentials || !credentials.principal) {
29
+ throw new errors.NotAllowedError("authentication required");
30
+ }
31
+ const info = await userInfo.getUserInfo(credentials);
32
+ const groups = info.ownershipEntityRefs || [];
33
+ console.log(`user identity resolved: userEntityRef=${info.userEntityRef}, groups=${groups.join(",")}`);
34
+ return {
35
+ userEntityRef: info.userEntityRef,
36
+ groups
37
+ };
38
+ }
39
+ async function createRouter({
40
+ httpAuth,
41
+ userInfo,
42
+ config,
43
+ permissions: permissions$1
44
+ }) {
45
+ const router = Router__default.default();
46
+ router.use(cors__default.default({
47
+ origin: "http://localhost:3000",
48
+ credentials: true
49
+ }));
50
+ router.use(express__default.default.json());
51
+ const k8sClient$1 = new k8sClient.KuadrantK8sClient(config);
52
+ router.get("/apiproducts", async (req, res) => {
53
+ try {
54
+ const credentials = await httpAuth.credentials(req);
55
+ const listDecision = await permissions$1.authorize(
56
+ [{ permission: permissions.kuadrantApiProductListPermission }],
57
+ { credentials }
58
+ );
59
+ if (listDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
60
+ throw new errors.NotAllowedError("unauthorised");
61
+ }
62
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
63
+ const data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apiproducts");
64
+ const readAllDecision = await permissions$1.authorize(
65
+ [{ permission: permissions.kuadrantApiProductReadAllPermission }],
66
+ { credentials }
67
+ );
68
+ if (readAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW) {
69
+ res.json(data);
70
+ } else {
71
+ const readOwnDecision = await permissions$1.authorize(
72
+ [{ permission: permissions.kuadrantApiProductReadOwnPermission }],
73
+ { credentials }
74
+ );
75
+ if (readOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
76
+ throw new errors.NotAllowedError("unauthorised");
77
+ }
78
+ const ownedItems = (data.items || []).filter((item) => {
79
+ const owner = item.metadata?.annotations?.["backstage.io/owner"];
80
+ return owner === userEntityRef;
81
+ });
82
+ res.json({ ...data, items: ownedItems });
83
+ }
84
+ } catch (error) {
85
+ console.error("error fetching apiproducts:", error);
86
+ if (error instanceof errors.NotAllowedError) {
87
+ res.status(403).json({ error: error.message });
88
+ } else {
89
+ res.status(500).json({ error: "failed to fetch apiproducts" });
90
+ }
91
+ }
92
+ });
93
+ router.get("/apiproducts/:namespace/:name", async (req, res) => {
94
+ try {
95
+ const credentials = await httpAuth.credentials(req);
96
+ const { namespace, name } = req.params;
97
+ const readAllDecision = await permissions$1.authorize(
98
+ [{ permission: permissions.kuadrantApiProductReadAllPermission }],
99
+ { credentials }
100
+ );
101
+ if (readAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
102
+ const readOwnDecision = await permissions$1.authorize(
103
+ [{ permission: permissions.kuadrantApiProductReadOwnPermission }],
104
+ { credentials }
105
+ );
106
+ if (readOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
107
+ throw new errors.NotAllowedError("unauthorised");
108
+ }
109
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
110
+ const data = await k8sClient$1.getCustomResource("devportal.kuadrant.io", "v1alpha1", namespace, "apiproducts", name);
111
+ const owner = data.metadata?.annotations?.["backstage.io/owner"];
112
+ if (owner !== userEntityRef) {
113
+ throw new errors.NotAllowedError("you can only read your own api products");
114
+ }
115
+ res.json(data);
116
+ } else {
117
+ const data = await k8sClient$1.getCustomResource("devportal.kuadrant.io", "v1alpha1", namespace, "apiproducts", name);
118
+ res.json(data);
119
+ }
120
+ } catch (error) {
121
+ console.error("error fetching apiproduct:", error);
122
+ if (error instanceof errors.NotAllowedError) {
123
+ res.status(403).json({ error: error.message });
124
+ } else {
125
+ res.status(500).json({ error: "failed to fetch apiproduct" });
126
+ }
127
+ }
128
+ });
129
+ router.post("/apiproducts", async (req, res) => {
130
+ try {
131
+ const credentials = await httpAuth.credentials(req);
132
+ const decision = await permissions$1.authorize(
133
+ [{ permission: permissions.kuadrantApiProductCreatePermission }],
134
+ { credentials }
135
+ );
136
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
137
+ throw new errors.NotAllowedError("unauthorised");
138
+ }
139
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
140
+ const apiProduct = req.body;
141
+ const targetRef = apiProduct.spec?.targetRef;
142
+ if (!targetRef?.name || !targetRef?.kind || !targetRef?.namespace) {
143
+ throw new errors.InputError("targetRef with name, kind, and namespace is required");
144
+ }
145
+ const namespace = targetRef.namespace;
146
+ apiProduct.metadata.namespace = namespace;
147
+ if (!apiProduct.metadata.annotations) {
148
+ apiProduct.metadata.annotations = {};
149
+ }
150
+ apiProduct.metadata.annotations["backstage.io/owner"] = userEntityRef;
151
+ const created = await k8sClient$1.createCustomResource(
152
+ "devportal.kuadrant.io",
153
+ "v1alpha1",
154
+ namespace,
155
+ "apiproducts",
156
+ apiProduct
157
+ );
158
+ const provider = module$1.getAPIProductEntityProvider();
159
+ if (provider) {
160
+ await provider.refresh();
161
+ }
162
+ res.status(201).json(created);
163
+ } catch (error) {
164
+ console.error("error creating apiproduct:", error);
165
+ const errorMessage = error instanceof Error ? error.message : String(error);
166
+ if (error instanceof errors.NotAllowedError) {
167
+ res.status(403).json({ error: error.message });
168
+ } else if (error instanceof errors.InputError) {
169
+ res.status(400).json({ error: error.message });
170
+ } else {
171
+ res.status(500).json({ error: errorMessage });
172
+ }
173
+ }
174
+ });
175
+ router.delete("/apiproducts/:namespace/:name", async (req, res) => {
176
+ try {
177
+ const credentials = await httpAuth.credentials(req);
178
+ const { namespace, name } = req.params;
179
+ const deleteAllDecision = await permissions$1.authorize(
180
+ [{ permission: permissions.kuadrantApiProductDeleteAllPermission }],
181
+ { credentials }
182
+ );
183
+ if (deleteAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
184
+ const deleteOwnDecision = await permissions$1.authorize(
185
+ [{ permission: permissions.kuadrantApiProductDeleteOwnPermission }],
186
+ { credentials }
187
+ );
188
+ if (deleteOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
189
+ throw new errors.NotAllowedError("unauthorised");
190
+ }
191
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
192
+ const existing = await k8sClient$1.getCustomResource("devportal.kuadrant.io", "v1alpha1", namespace, "apiproducts", name);
193
+ const owner = existing.metadata?.annotations?.["backstage.io/owner"];
194
+ if (owner !== userEntityRef) {
195
+ throw new errors.NotAllowedError("you can only delete your own api products");
196
+ }
197
+ }
198
+ console.log(`cascading delete: finding apikeys for ${namespace}/${name}`);
199
+ let allRequests;
200
+ try {
201
+ allRequests = await k8sClient$1.listCustomResources(
202
+ "devportal.kuadrant.io",
203
+ "v1alpha1",
204
+ "apikeys",
205
+ namespace
206
+ );
207
+ } catch (error) {
208
+ console.warn("failed to list apikeys during cascade delete:", error);
209
+ allRequests = { items: [] };
210
+ }
211
+ const relatedRequests = (allRequests.items || []).filter(
212
+ (req2) => req2.spec?.apiProductRef?.name === name
213
+ );
214
+ console.log(`found ${relatedRequests.length} apikeys to delete`);
215
+ const deletionResults = await Promise.allSettled(
216
+ relatedRequests.map(async (request) => {
217
+ const requestName = request.metadata.name;
218
+ console.log(`deleting apikey: ${namespace}/${requestName}`);
219
+ await k8sClient$1.deleteCustomResource(
220
+ "devportal.kuadrant.io",
221
+ "v1alpha1",
222
+ namespace,
223
+ "apikeys",
224
+ requestName
225
+ );
226
+ })
227
+ );
228
+ const failures = deletionResults.filter((r) => r.status === "rejected");
229
+ if (failures.length > 0) {
230
+ console.warn(
231
+ `${failures.length} apikeys failed to delete:`,
232
+ failures.map((f) => f.reason)
233
+ );
234
+ }
235
+ await k8sClient$1.deleteCustomResource(
236
+ "devportal.kuadrant.io",
237
+ "v1alpha1",
238
+ namespace,
239
+ "apiproducts",
240
+ name
241
+ );
242
+ const provider = module$1.getAPIProductEntityProvider();
243
+ if (provider) {
244
+ await provider.refresh();
245
+ }
246
+ res.status(204).send();
247
+ } catch (error) {
248
+ console.error("error deleting apiproduct:", error);
249
+ if (error instanceof errors.NotAllowedError) {
250
+ res.status(403).json({ error: error.message });
251
+ } else {
252
+ res.status(500).json({ error: "failed to delete apiproduct" });
253
+ }
254
+ }
255
+ });
256
+ router.get("/httproutes", async (req, res) => {
257
+ try {
258
+ const credentials = await httpAuth.credentials(req);
259
+ const decision = await permissions$1.authorize(
260
+ [{ permission: permissions.kuadrantApiProductListPermission }],
261
+ { credentials }
262
+ );
263
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
264
+ throw new errors.NotAllowedError("unauthorised");
265
+ }
266
+ const data = await k8sClient$1.listCustomResources("gateway.networking.k8s.io", "v1", "httproutes");
267
+ res.json(data);
268
+ } catch (error) {
269
+ console.error("error fetching httproutes:", error);
270
+ if (error instanceof errors.NotAllowedError) {
271
+ res.status(403).json({ error: error.message });
272
+ } else {
273
+ res.status(500).json({ error: "failed to fetch httproutes" });
274
+ }
275
+ }
276
+ });
277
+ router.patch("/apiproducts/:namespace/:name", async (req, res) => {
278
+ const patchSchema = zod.z.object({
279
+ spec: zod.z.object({
280
+ displayName: zod.z.string().optional(),
281
+ description: zod.z.string().optional(),
282
+ version: zod.z.string().optional(),
283
+ publishStatus: zod.z.enum(["Draft", "Published"]).optional(),
284
+ approvalMode: zod.z.enum(["automatic", "manual"]).optional(),
285
+ tags: zod.z.array(zod.z.string()).optional(),
286
+ contact: zod.z.object({
287
+ email: zod.z.string().optional(),
288
+ team: zod.z.string().optional(),
289
+ slack: zod.z.string().optional()
290
+ }).partial().optional(),
291
+ documentation: zod.z.object({
292
+ docsURL: zod.z.string().optional(),
293
+ openAPISpec: zod.z.string().optional()
294
+ }).partial().optional()
295
+ }).partial()
296
+ });
297
+ const parsed = patchSchema.safeParse(req.body);
298
+ if (!parsed.success) {
299
+ return res.status(400).json({ error: "invalid patch: " + parsed.error.toString() });
300
+ }
301
+ try {
302
+ const credentials = await httpAuth.credentials(req);
303
+ if (!credentials || !credentials.principal) {
304
+ throw new errors.NotAllowedError("authentication required");
305
+ }
306
+ const { namespace, name } = req.params;
307
+ const updateAllDecision = await permissions$1.authorize(
308
+ [{ permission: permissions.kuadrantApiProductUpdateAllPermission }],
309
+ { credentials }
310
+ );
311
+ if (updateAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
312
+ const updateOwnDecision = await permissions$1.authorize(
313
+ [{ permission: permissions.kuadrantApiProductUpdateOwnPermission }],
314
+ { credentials }
315
+ );
316
+ if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
317
+ throw new errors.NotAllowedError("unauthorised");
318
+ }
319
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
320
+ const existing = await k8sClient$1.getCustomResource("devportal.kuadrant.io", "v1alpha1", namespace, "apiproducts", name);
321
+ const owner = existing.metadata?.annotations?.["backstage.io/owner"];
322
+ if (owner !== userEntityRef) {
323
+ throw new errors.NotAllowedError("you can only update your own api products");
324
+ }
325
+ }
326
+ if (req.body.metadata?.annotations) {
327
+ delete req.body.metadata.annotations["backstage.io/owner"];
328
+ }
329
+ const updated = await k8sClient$1.patchCustomResource(
330
+ "devportal.kuadrant.io",
331
+ "v1alpha1",
332
+ namespace,
333
+ "apiproducts",
334
+ name,
335
+ parsed.data
336
+ );
337
+ const provider = module$1.getAPIProductEntityProvider();
338
+ if (provider) {
339
+ await provider.refresh();
340
+ }
341
+ return res.json(updated);
342
+ } catch (error) {
343
+ console.error("error updating apiproduct:", error);
344
+ const errorMessage = error instanceof Error ? error.message : String(error);
345
+ if (error instanceof errors.NotAllowedError) {
346
+ return res.status(403).json({ error: error.message });
347
+ } else if (error instanceof errors.InputError) {
348
+ return res.status(400).json({ error: error.message });
349
+ } else {
350
+ return res.status(500).json({ error: errorMessage });
351
+ }
352
+ }
353
+ });
354
+ router.get("/planpolicies", async (req, res) => {
355
+ try {
356
+ const credentials = await httpAuth.credentials(req);
357
+ const decision = await permissions$1.authorize(
358
+ [{ permission: permissions.kuadrantPlanPolicyListPermission }],
359
+ { credentials }
360
+ );
361
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
362
+ throw new errors.NotAllowedError("unauthorised");
363
+ }
364
+ const data = await k8sClient$1.listCustomResources("extensions.kuadrant.io", "v1alpha1", "planpolicies");
365
+ const filtered = {
366
+ items: (data.items || []).map((policy) => ({
367
+ metadata: {
368
+ name: policy.metadata.name,
369
+ namespace: policy.metadata.namespace
370
+ },
371
+ // only expose targetRef to allow UI to match PlanPolicy -> HTTPRoute
372
+ targetRef: policy.spec?.targetRef ? {
373
+ kind: policy.spec.targetRef.kind,
374
+ name: policy.spec.targetRef.name,
375
+ namespace: policy.spec.targetRef.namespace
376
+ } : void 0,
377
+ // only expose plan tier info, no other spec details
378
+ plans: (policy.spec?.plans || []).map((plan) => ({
379
+ tier: plan.tier,
380
+ description: plan.description,
381
+ limits: plan.limits
382
+ }))
383
+ }))
384
+ };
385
+ res.json(filtered);
386
+ } catch (error) {
387
+ console.error("error fetching planpolicies:", error);
388
+ if (error instanceof errors.NotAllowedError) {
389
+ res.status(403).json({ error: error.message });
390
+ } else {
391
+ res.status(500).json({ error: "failed to fetch planpolicies" });
392
+ }
393
+ }
394
+ });
395
+ router.get("/planpolicies/:namespace/:name", async (req, res) => {
396
+ try {
397
+ const credentials = await httpAuth.credentials(req);
398
+ const decision = await permissions$1.authorize(
399
+ [{ permission: permissions.kuadrantPlanPolicyReadPermission }],
400
+ { credentials }
401
+ );
402
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
403
+ throw new errors.NotAllowedError("unauthorised");
404
+ }
405
+ const { namespace, name } = req.params;
406
+ const data = await k8sClient$1.getCustomResource("extensions.kuadrant.io", "v1alpha1", namespace, "planpolicies", name);
407
+ res.json(data);
408
+ } catch (error) {
409
+ console.error("error fetching planpolicy:", error);
410
+ if (error instanceof errors.NotAllowedError) {
411
+ res.status(403).json({ error: error.message });
412
+ } else {
413
+ res.status(500).json({ error: "failed to fetch planpolicy" });
414
+ }
415
+ }
416
+ });
417
+ const requestSchema = zod.z.object({
418
+ apiProductName: zod.z.string(),
419
+ // name of the APIProduct
420
+ namespace: zod.z.string(),
421
+ // namespace where both APIProduct and APIKey live
422
+ planTier: zod.z.string(),
423
+ useCase: zod.z.string().optional(),
424
+ userEmail: zod.z.string().optional()
425
+ });
426
+ router.post("/requests", async (req, res) => {
427
+ const parsed = requestSchema.safeParse(req.body);
428
+ if (!parsed.success) {
429
+ throw new errors.InputError(parsed.error.toString());
430
+ }
431
+ try {
432
+ const credentials = await httpAuth.credentials(req);
433
+ const { apiProductName, namespace, planTier, useCase, userEmail } = parsed.data;
434
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
435
+ const resourceRef = `apiproduct:${namespace}/${apiProductName}`;
436
+ const decision = await permissions$1.authorize(
437
+ [{
438
+ permission: permissions.kuadrantApiKeyCreatePermission,
439
+ resourceRef
440
+ }],
441
+ { credentials }
442
+ );
443
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
444
+ throw new errors.NotAllowedError(`not authorised to request access to ${apiProductName}`);
445
+ }
446
+ const randomSuffix = crypto.randomBytes(4).toString("hex");
447
+ const userName = extractNameFromEntityRef(userEntityRef);
448
+ const requestName = `${userName}-${apiProductName}-${randomSuffix}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
449
+ const requestedBy = { userId: userEntityRef };
450
+ if (userEmail) {
451
+ requestedBy.email = userEmail;
452
+ }
453
+ const request = {
454
+ apiVersion: "devportal.kuadrant.io/v1alpha1",
455
+ kind: "APIKey",
456
+ metadata: {
457
+ name: requestName,
458
+ namespace
459
+ },
460
+ spec: {
461
+ apiProductRef: {
462
+ name: apiProductName
463
+ },
464
+ planTier,
465
+ useCase: useCase || "",
466
+ requestedBy
467
+ }
468
+ };
469
+ const created = await k8sClient$1.createCustomResource(
470
+ "devportal.kuadrant.io",
471
+ "v1alpha1",
472
+ namespace,
473
+ "apikeys",
474
+ request
475
+ );
476
+ res.status(201).json(created);
477
+ } catch (error) {
478
+ console.error("error creating api key request:", error);
479
+ if (error instanceof errors.NotAllowedError) {
480
+ res.status(403).json({ error: error.message });
481
+ } else {
482
+ res.status(500).json({ error: "failed to create api key request" });
483
+ }
484
+ }
485
+ });
486
+ router.get("/requests", async (req, res) => {
487
+ try {
488
+ const credentials = await httpAuth.credentials(req);
489
+ const readAllDecision = await permissions$1.authorize(
490
+ [{ permission: permissions.kuadrantApiKeyReadAllPermission }],
491
+ { credentials }
492
+ );
493
+ const canReadAll = readAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
494
+ if (!canReadAll) {
495
+ const readOwnDecision = await permissions$1.authorize(
496
+ [{ permission: permissions.kuadrantApiKeyReadOwnPermission }],
497
+ { credentials }
498
+ );
499
+ if (readOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
500
+ throw new errors.NotAllowedError("unauthorised");
501
+ }
502
+ }
503
+ const status = req.query.status;
504
+ const namespace = req.query.namespace;
505
+ let data;
506
+ if (namespace) {
507
+ data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeys", namespace);
508
+ } else {
509
+ data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeys");
510
+ }
511
+ let filteredItems = data.items || [];
512
+ if (!canReadAll) {
513
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
514
+ const apiproducts = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apiproducts");
515
+ const ownedApiProducts = (apiproducts.items || []).filter((product) => {
516
+ const owner = product.metadata?.annotations?.["backstage.io/owner"];
517
+ return owner === userEntityRef;
518
+ }).map((product) => product.metadata.name);
519
+ filteredItems = filteredItems.filter(
520
+ (req2) => ownedApiProducts.includes(req2.spec?.apiProductRef?.name)
521
+ );
522
+ }
523
+ if (status) {
524
+ filteredItems = filteredItems.filter((req2) => {
525
+ const phase = req2.status?.phase || "Pending";
526
+ return phase === status;
527
+ });
528
+ }
529
+ res.json({ items: filteredItems });
530
+ } catch (error) {
531
+ console.error("error fetching api key requests:", error);
532
+ if (error instanceof errors.NotAllowedError) {
533
+ res.status(403).json({ error: error.message });
534
+ } else {
535
+ res.status(500).json({ error: "failed to fetch api key requests" });
536
+ }
537
+ }
538
+ });
539
+ router.get("/requests/my", async (req, res) => {
540
+ try {
541
+ const credentials = await httpAuth.credentials(req);
542
+ const decision = await permissions$1.authorize(
543
+ [{ permission: permissions.kuadrantApiKeyReadOwnPermission }],
544
+ { credentials }
545
+ );
546
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
547
+ throw new errors.NotAllowedError("unauthorised");
548
+ }
549
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
550
+ const namespace = req.query.namespace;
551
+ let data;
552
+ if (namespace) {
553
+ data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeys", namespace);
554
+ } else {
555
+ data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeys");
556
+ }
557
+ const filteredItems = (data.items || []).filter(
558
+ (req2) => req2.spec?.requestedBy?.userId === userEntityRef
559
+ );
560
+ res.json({ items: filteredItems });
561
+ } catch (error) {
562
+ console.error("error fetching user api key requests:", error);
563
+ if (error instanceof errors.NotAllowedError) {
564
+ res.status(403).json({ error: error.message });
565
+ } else {
566
+ res.status(500).json({ error: "failed to fetch user api key requests" });
567
+ }
568
+ }
569
+ });
570
+ const approveRejectSchema = zod.z.object({
571
+ comment: zod.z.string().optional()
572
+ });
573
+ router.post("/requests/:namespace/:name/approve", async (req, res) => {
574
+ const parsed = approveRejectSchema.safeParse(req.body);
575
+ if (!parsed.success) {
576
+ throw new errors.InputError(parsed.error.toString());
577
+ }
578
+ try {
579
+ const credentials = await httpAuth.credentials(req);
580
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
581
+ const { namespace, name } = req.params;
582
+ const reviewedBy = userEntityRef;
583
+ const request = await k8sClient$1.getCustomResource(
584
+ "devportal.kuadrant.io",
585
+ "v1alpha1",
586
+ namespace,
587
+ "apikeys",
588
+ name
589
+ );
590
+ const spec = request.spec;
591
+ const apiProductName = spec.apiProductRef?.name;
592
+ if (!apiProductName) {
593
+ throw new errors.InputError("apiProductRef.name is required in APIKey spec");
594
+ }
595
+ const apiProduct = await k8sClient$1.getCustomResource(
596
+ "devportal.kuadrant.io",
597
+ "v1alpha1",
598
+ namespace,
599
+ "apiproducts",
600
+ apiProductName
601
+ );
602
+ const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
603
+ const updateAllDecision = await permissions$1.authorize(
604
+ [{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
605
+ { credentials }
606
+ );
607
+ if (updateAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
608
+ const updateOwnDecision = await permissions$1.authorize(
609
+ [{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
610
+ { credentials }
611
+ );
612
+ if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
613
+ throw new errors.NotAllowedError("unauthorised");
614
+ }
615
+ if (owner !== userEntityRef) {
616
+ throw new errors.NotAllowedError("you can only approve requests for your own api products");
617
+ }
618
+ }
619
+ const status = {
620
+ phase: "Approved",
621
+ reviewedBy,
622
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
623
+ };
624
+ await k8sClient$1.patchCustomResourceStatus(
625
+ "devportal.kuadrant.io",
626
+ "v1alpha1",
627
+ namespace,
628
+ "apikeys",
629
+ name,
630
+ status
631
+ );
632
+ res.json({ success: true });
633
+ } catch (error) {
634
+ console.error("error approving api key request:", error);
635
+ if (error instanceof errors.NotAllowedError) {
636
+ res.status(403).json({ error: error.message });
637
+ } else {
638
+ res.status(500).json({ error: "failed to approve api key request" });
639
+ }
640
+ }
641
+ });
642
+ router.post("/requests/:namespace/:name/reject", async (req, res) => {
643
+ const parsed = approveRejectSchema.safeParse(req.body);
644
+ if (!parsed.success) {
645
+ throw new errors.InputError(parsed.error.toString());
646
+ }
647
+ try {
648
+ const credentials = await httpAuth.credentials(req);
649
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
650
+ const { namespace, name } = req.params;
651
+ const reviewedBy = userEntityRef;
652
+ const request = await k8sClient$1.getCustomResource(
653
+ "devportal.kuadrant.io",
654
+ "v1alpha1",
655
+ namespace,
656
+ "apikeys",
657
+ name
658
+ );
659
+ const spec = request.spec;
660
+ const apiProductName = spec.apiProductRef?.name;
661
+ if (!apiProductName) {
662
+ throw new errors.InputError("apiProductRef.name is required in APIKey spec");
663
+ }
664
+ const apiProduct = await k8sClient$1.getCustomResource(
665
+ "devportal.kuadrant.io",
666
+ "v1alpha1",
667
+ namespace,
668
+ "apiproducts",
669
+ apiProductName
670
+ );
671
+ const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
672
+ const updateAllDecision = await permissions$1.authorize(
673
+ [{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
674
+ { credentials }
675
+ );
676
+ if (updateAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
677
+ const updateOwnDecision = await permissions$1.authorize(
678
+ [{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
679
+ { credentials }
680
+ );
681
+ if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
682
+ throw new errors.NotAllowedError("unauthorised");
683
+ }
684
+ if (owner !== userEntityRef) {
685
+ throw new errors.NotAllowedError("you can only reject requests for your own api products");
686
+ }
687
+ }
688
+ const status = {
689
+ phase: "Rejected",
690
+ reviewedBy,
691
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
692
+ };
693
+ await k8sClient$1.patchCustomResourceStatus(
694
+ "devportal.kuadrant.io",
695
+ "v1alpha1",
696
+ namespace,
697
+ "apikeys",
698
+ name,
699
+ status
700
+ );
701
+ res.status(204).send();
702
+ } catch (error) {
703
+ console.error("error rejecting api key request:", error);
704
+ if (error instanceof errors.NotAllowedError) {
705
+ res.status(403).json({ error: error.message });
706
+ } else {
707
+ res.status(500).json({ error: "failed to reject api key request" });
708
+ }
709
+ }
710
+ });
711
+ const bulkApproveSchema = zod.z.object({
712
+ requests: zod.z.array(zod.z.object({
713
+ namespace: zod.z.string(),
714
+ name: zod.z.string()
715
+ })),
716
+ comment: zod.z.string().optional()
717
+ });
718
+ router.post("/requests/bulk-approve", async (req, res) => {
719
+ const parsed = bulkApproveSchema.safeParse(req.body);
720
+ if (!parsed.success) {
721
+ throw new errors.InputError(parsed.error.toString());
722
+ }
723
+ try {
724
+ const credentials = await httpAuth.credentials(req);
725
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
726
+ const updateAllDecision = await permissions$1.authorize(
727
+ [{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
728
+ { credentials }
729
+ );
730
+ const canUpdateAll = updateAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
731
+ if (!canUpdateAll) {
732
+ const updateOwnDecision = await permissions$1.authorize(
733
+ [{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
734
+ { credentials }
735
+ );
736
+ if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
737
+ throw new errors.NotAllowedError("unauthorised");
738
+ }
739
+ }
740
+ const { requests } = parsed.data;
741
+ const reviewedBy = userEntityRef;
742
+ const results = [];
743
+ for (const reqRef of requests) {
744
+ try {
745
+ if (!canUpdateAll) {
746
+ const request = await k8sClient$1.getCustomResource(
747
+ "devportal.kuadrant.io",
748
+ "v1alpha1",
749
+ reqRef.namespace,
750
+ "apikeys",
751
+ reqRef.name
752
+ );
753
+ const apiProductName = request.spec?.apiProductRef?.name;
754
+ if (!apiProductName) {
755
+ results.push({
756
+ namespace: reqRef.namespace,
757
+ name: reqRef.name,
758
+ success: false,
759
+ error: "API key has no associated API product."
760
+ });
761
+ continue;
762
+ }
763
+ const apiProduct = await k8sClient$1.getCustomResource(
764
+ "devportal.kuadrant.io",
765
+ "v1alpha1",
766
+ reqRef.namespace,
767
+ "apiproducts",
768
+ apiProductName
769
+ );
770
+ const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
771
+ if (owner !== userEntityRef) {
772
+ results.push({
773
+ namespace: reqRef.namespace,
774
+ name: reqRef.name,
775
+ success: false,
776
+ error: "You can only approve requests for your own API products."
777
+ });
778
+ continue;
779
+ }
780
+ }
781
+ const status = {
782
+ phase: "Approved",
783
+ reviewedBy,
784
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
785
+ };
786
+ await k8sClient$1.patchCustomResourceStatus(
787
+ "devportal.kuadrant.io",
788
+ "v1alpha1",
789
+ reqRef.namespace,
790
+ "apikeys",
791
+ reqRef.name,
792
+ status
793
+ );
794
+ results.push({ namespace: reqRef.namespace, name: reqRef.name, success: true });
795
+ } catch (error) {
796
+ console.error(`error approving request ${reqRef.namespace}/${reqRef.name}:`, error);
797
+ results.push({
798
+ namespace: reqRef.namespace,
799
+ name: reqRef.name,
800
+ success: false,
801
+ error: error instanceof Error ? error.message : "unknown error"
802
+ });
803
+ }
804
+ }
805
+ res.json({ results });
806
+ } catch (error) {
807
+ console.error("error in bulk approve:", error);
808
+ if (error instanceof errors.NotAllowedError) {
809
+ res.status(403).json({ error: error.message });
810
+ } else {
811
+ res.status(500).json({ error: "failed to bulk approve api key requests" });
812
+ }
813
+ }
814
+ });
815
+ router.post("/requests/bulk-reject", async (req, res) => {
816
+ const parsed = bulkApproveSchema.safeParse(req.body);
817
+ if (!parsed.success) {
818
+ throw new errors.InputError(parsed.error.toString());
819
+ }
820
+ try {
821
+ const credentials = await httpAuth.credentials(req);
822
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
823
+ const updateAllDecision = await permissions$1.authorize(
824
+ [{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
825
+ { credentials }
826
+ );
827
+ const canUpdateAll = updateAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
828
+ if (!canUpdateAll) {
829
+ const updateOwnDecision = await permissions$1.authorize(
830
+ [{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
831
+ { credentials }
832
+ );
833
+ if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
834
+ throw new errors.NotAllowedError("unauthorised");
835
+ }
836
+ }
837
+ const { requests } = parsed.data;
838
+ const reviewedBy = userEntityRef;
839
+ const results = [];
840
+ for (const reqRef of requests) {
841
+ try {
842
+ const request = await k8sClient$1.getCustomResource(
843
+ "devportal.kuadrant.io",
844
+ "v1alpha1",
845
+ reqRef.namespace,
846
+ "apikeys",
847
+ reqRef.name
848
+ );
849
+ const spec = request.spec;
850
+ const apiProduct = await k8sClient$1.getCustomResource(
851
+ "devportal.kuadrant.io",
852
+ "v1alpha1",
853
+ reqRef.namespace,
854
+ "apiproducts",
855
+ spec.apiProductRef?.name
856
+ );
857
+ const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
858
+ if (!canUpdateAll && owner !== userEntityRef) {
859
+ results.push({
860
+ namespace: reqRef.namespace,
861
+ name: reqRef.name,
862
+ success: false,
863
+ error: "You can only reject requests for your own API products."
864
+ });
865
+ continue;
866
+ }
867
+ const status = {
868
+ phase: "Rejected",
869
+ reviewedBy,
870
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
871
+ };
872
+ await k8sClient$1.patchCustomResourceStatus(
873
+ "devportal.kuadrant.io",
874
+ "v1alpha1",
875
+ reqRef.namespace,
876
+ "apikeys",
877
+ reqRef.name,
878
+ status
879
+ );
880
+ results.push({ namespace: reqRef.namespace, name: reqRef.name, success: true });
881
+ } catch (error) {
882
+ console.error(`error rejecting request ${reqRef.namespace}/${reqRef.name}:`, error);
883
+ results.push({
884
+ namespace: reqRef.namespace,
885
+ name: reqRef.name,
886
+ success: false,
887
+ error: error instanceof Error ? error.message : "unknown error"
888
+ });
889
+ }
890
+ }
891
+ res.json({ results });
892
+ } catch (error) {
893
+ console.error("error in bulk reject:", error);
894
+ if (error instanceof errors.NotAllowedError) {
895
+ res.status(403).json({ error: error.message });
896
+ } else {
897
+ res.status(500).json({ error: "failed to bulk reject api key requests" });
898
+ }
899
+ }
900
+ });
901
+ router.delete("/requests/:namespace/:name", async (req, res) => {
902
+ try {
903
+ const credentials = await httpAuth.credentials(req);
904
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
905
+ const { namespace, name } = req.params;
906
+ const request = await k8sClient$1.getCustomResource(
907
+ "devportal.kuadrant.io",
908
+ "v1alpha1",
909
+ namespace,
910
+ "apikeys",
911
+ name
912
+ );
913
+ const requestUserId = request.spec?.requestedBy?.userId;
914
+ const deleteAllDecision = await permissions$1.authorize(
915
+ [{ permission: permissions.kuadrantApiKeyDeleteAllPermission }],
916
+ { credentials }
917
+ );
918
+ const canDeleteAll = deleteAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
919
+ if (!canDeleteAll) {
920
+ const deleteOwnDecision = await permissions$1.authorize(
921
+ [{ permission: permissions.kuadrantApiKeyDeleteOwnPermission }],
922
+ { credentials }
923
+ );
924
+ if (deleteOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
925
+ throw new errors.NotAllowedError("unauthorised");
926
+ }
927
+ if (requestUserId !== userEntityRef) {
928
+ throw new errors.NotAllowedError("you can only delete your own api key requests");
929
+ }
930
+ }
931
+ await k8sClient$1.deleteCustomResource(
932
+ "devportal.kuadrant.io",
933
+ "v1alpha1",
934
+ namespace,
935
+ "apikeys",
936
+ name
937
+ );
938
+ res.status(204).send();
939
+ } catch (error) {
940
+ console.error("error deleting api key request:", error);
941
+ if (error instanceof errors.NotAllowedError) {
942
+ res.status(403).json({ error: error.message });
943
+ } else {
944
+ res.status(500).json({ error: "failed to delete api key request" });
945
+ }
946
+ }
947
+ });
948
+ router.patch("/requests/:namespace/:name", async (req, res) => {
949
+ const patchSchema = zod.z.object({
950
+ spec: zod.z.object({
951
+ useCase: zod.z.string().optional(),
952
+ planTier: zod.z.string().optional()
953
+ }).partial()
954
+ });
955
+ const parsed = patchSchema.safeParse(req.body);
956
+ if (!parsed.success) {
957
+ throw new errors.InputError("invalid patch: " + parsed.error.toString());
958
+ }
959
+ try {
960
+ const credentials = await httpAuth.credentials(req);
961
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
962
+ const { namespace, name } = req.params;
963
+ const existing = await k8sClient$1.getCustomResource(
964
+ "devportal.kuadrant.io",
965
+ "v1alpha1",
966
+ namespace,
967
+ "apikeys",
968
+ name
969
+ );
970
+ const requestUserId = existing.spec?.requestedBy?.userId;
971
+ const currentPhase = existing.status?.phase || "Pending";
972
+ if (currentPhase !== "Pending") {
973
+ throw new errors.NotAllowedError("only pending requests can be edited");
974
+ }
975
+ const updateAllDecision = await permissions$1.authorize(
976
+ [{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
977
+ { credentials }
978
+ );
979
+ if (updateAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
980
+ const updateOwnDecision = await permissions$1.authorize(
981
+ [{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
982
+ { credentials }
983
+ );
984
+ if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
985
+ throw new errors.NotAllowedError("unauthorised");
986
+ }
987
+ if (requestUserId !== userEntityRef) {
988
+ throw new errors.NotAllowedError("you can only update your own api key requests");
989
+ }
990
+ }
991
+ const updated = await k8sClient$1.patchCustomResource(
992
+ "devportal.kuadrant.io",
993
+ "v1alpha1",
994
+ namespace,
995
+ "apikeys",
996
+ name,
997
+ parsed.data
998
+ );
999
+ res.json(updated);
1000
+ } catch (error) {
1001
+ console.error("error updating api key request:", error);
1002
+ if (error instanceof errors.NotAllowedError) {
1003
+ res.status(403).json({ error: error.message });
1004
+ } else if (error instanceof errors.InputError) {
1005
+ res.status(400).json({ error: error.message });
1006
+ } else {
1007
+ res.status(500).json({ error: "failed to update api key request" });
1008
+ }
1009
+ }
1010
+ });
1011
+ router.get("/apikeys/:namespace/:name", async (req, res) => {
1012
+ try {
1013
+ const credentials = await httpAuth.credentials(req);
1014
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
1015
+ const { namespace, name } = req.params;
1016
+ const readAllDecision = await permissions$1.authorize(
1017
+ [{ permission: permissions.kuadrantApiKeyReadAllPermission }],
1018
+ { credentials }
1019
+ );
1020
+ const canReadAll = readAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
1021
+ if (!canReadAll) {
1022
+ const readOwnDecision = await permissions$1.authorize(
1023
+ [{ permission: permissions.kuadrantApiKeyReadOwnPermission }],
1024
+ { credentials }
1025
+ );
1026
+ if (readOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
1027
+ throw new errors.NotAllowedError("unauthorised");
1028
+ }
1029
+ }
1030
+ const apiKey = await k8sClient$1.getCustomResource(
1031
+ "devportal.kuadrant.io",
1032
+ "v1alpha1",
1033
+ namespace,
1034
+ "apikeys",
1035
+ name
1036
+ );
1037
+ if (!apiKey) {
1038
+ res.status(404).json({ error: "API key not found" });
1039
+ return;
1040
+ }
1041
+ if (!canReadAll) {
1042
+ const ownerId = apiKey.spec?.requestedBy?.userId;
1043
+ if (ownerId !== userEntityRef) {
1044
+ throw new errors.NotAllowedError("not authorised to view this API key");
1045
+ }
1046
+ }
1047
+ res.status(200).json(apiKey);
1048
+ } catch (error) {
1049
+ console.error("failed to get api key:", error);
1050
+ if (error instanceof errors.NotAllowedError) {
1051
+ res.status(403).json({ error: error.message });
1052
+ } else {
1053
+ res.status(500).json({ error: "failed to get api key" });
1054
+ }
1055
+ }
1056
+ });
1057
+ router.get("/apikeys/:namespace/:name/secret", async (req, res) => {
1058
+ try {
1059
+ const credentials = await httpAuth.credentials(req);
1060
+ const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
1061
+ const { namespace, name } = req.params;
1062
+ const readAllDecision = await permissions$1.authorize(
1063
+ [{ permission: permissions.kuadrantApiKeyReadAllPermission }],
1064
+ { credentials }
1065
+ );
1066
+ const canReadAll = readAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
1067
+ if (!canReadAll) {
1068
+ const readOwnDecision = await permissions$1.authorize(
1069
+ [{ permission: permissions.kuadrantApiKeyReadOwnPermission }],
1070
+ { credentials }
1071
+ );
1072
+ if (readOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
1073
+ throw new errors.NotAllowedError("unauthorised");
1074
+ }
1075
+ }
1076
+ const apiKey = await k8sClient$1.getCustomResource(
1077
+ "devportal.kuadrant.io",
1078
+ "v1alpha1",
1079
+ namespace,
1080
+ "apikeys",
1081
+ name
1082
+ );
1083
+ if (!canReadAll) {
1084
+ const requestUserId = apiKey.spec?.requestedBy?.userId;
1085
+ if (requestUserId !== userEntityRef) {
1086
+ throw new errors.NotAllowedError("you can only read your own api key secrets");
1087
+ }
1088
+ }
1089
+ if (apiKey.status?.canReadSecret !== true) {
1090
+ res.status(403).json({
1091
+ error: "secret has already been read and cannot be retrieved again"
1092
+ });
1093
+ return;
1094
+ }
1095
+ if (!apiKey.status?.secretRef?.name || !apiKey.status?.secretRef?.key) {
1096
+ res.status(404).json({
1097
+ error: "secret reference not found in apikey status"
1098
+ });
1099
+ return;
1100
+ }
1101
+ const secretName = apiKey.status.secretRef.name;
1102
+ let secret;
1103
+ try {
1104
+ secret = await k8sClient$1.getSecret(namespace, secretName);
1105
+ } catch (error) {
1106
+ console.error("error fetching secret:", error);
1107
+ res.status(404).json({
1108
+ error: "secret not found"
1109
+ });
1110
+ return;
1111
+ }
1112
+ const secretData = secret.data || {};
1113
+ const apiKeyValue = secretData[secretKey];
1114
+ if (!apiKeyValue) {
1115
+ res.status(404).json({
1116
+ error: `secret key '${secretKey}' not found in secret`
1117
+ });
1118
+ return;
1119
+ }
1120
+ const decodedApiKey = Buffer.from(apiKeyValue, "base64").toString("utf-8");
1121
+ await k8sClient$1.patchCustomResourceStatus(
1122
+ "devportal.kuadrant.io",
1123
+ "v1alpha1",
1124
+ namespace,
1125
+ "apikeys",
1126
+ name,
1127
+ {
1128
+ ...apiKey.status,
1129
+ canReadSecret: false
1130
+ }
1131
+ );
1132
+ res.json({
1133
+ apiKey: decodedApiKey
1134
+ });
1135
+ } catch (error) {
1136
+ console.error("error reading api key secret:", error);
1137
+ if (error instanceof errors.NotAllowedError) {
1138
+ res.status(403).json({ error: error.message });
1139
+ } else {
1140
+ res.status(500).json({ error: "failed to read api key secret" });
1141
+ }
1142
+ }
1143
+ });
1144
+ router.use(pluginPermissionNode.createPermissionIntegrationRouter({
1145
+ permissions: permissions.kuadrantPermissions
1146
+ }));
1147
+ return router;
1148
+ }
1149
+
1150
+ exports.createRouter = createRouter;
1151
+ //# sourceMappingURL=router.cjs.js.map