@kuadrant/kuadrant-backstage-plugin-backend 0.0.2-dev-0b744dd → 0.0.2-dev-18c7ab0
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/dist/index.cjs.js +22 -6
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +46 -4
- package/dist/k8s-client.cjs.js +16 -0
- package/dist/k8s-client.cjs.js.map +1 -1
- package/dist/{alpha.cjs.js → module.cjs.js} +1 -1
- package/dist/module.cjs.js.map +1 -0
- package/dist/permissions.cjs.js +19 -1
- package/dist/permissions.cjs.js.map +1 -1
- package/dist/providers/APIProductEntityProvider.cjs.js.map +1 -1
- package/dist/{rbac.cjs.js → rbac-module.cjs.js} +1 -1
- package/dist/rbac-module.cjs.js.map +1 -0
- package/dist/router.cjs.js +505 -238
- package/dist/router.cjs.js.map +1 -1
- package/package.json +26 -38
- package/alpha/module.cjs.js +0 -31
- package/alpha/package.json +0 -11
- package/dist/alpha.cjs.js.map +0 -1
- package/dist/alpha.d.ts +0 -27
- package/dist/rbac.cjs.js.map +0 -1
- package/dist/rbac.d.ts +0 -11
- package/rbac/index.ts +0 -1
- package/rbac/package.json +0 -11
package/dist/router.cjs.js
CHANGED
|
@@ -9,7 +9,7 @@ var Router = require('express-promise-router');
|
|
|
9
9
|
var cors = require('cors');
|
|
10
10
|
var crypto = require('crypto');
|
|
11
11
|
var k8sClient = require('./k8s-client.cjs.js');
|
|
12
|
-
var
|
|
12
|
+
var module$1 = require('./module.cjs.js');
|
|
13
13
|
var permissions = require('./permissions.cjs.js');
|
|
14
14
|
|
|
15
15
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
@@ -23,6 +23,46 @@ function extractNameFromEntityRef(entityRef) {
|
|
|
23
23
|
const parts = entityRef.split("/");
|
|
24
24
|
return parts[parts.length - 1];
|
|
25
25
|
}
|
|
26
|
+
function getConsumerNamespace(userEntityRef) {
|
|
27
|
+
const username = extractNameFromEntityRef(userEntityRef);
|
|
28
|
+
if (!username || username.trim() === "") {
|
|
29
|
+
throw new Error("Invalid user identity - username is empty");
|
|
30
|
+
}
|
|
31
|
+
const sanitized = username.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
32
|
+
if (sanitized === "" || sanitized.startsWith("-") || sanitized.endsWith("-")) {
|
|
33
|
+
throw new Error(`Username "${username}" cannot be converted to valid namespace name`);
|
|
34
|
+
}
|
|
35
|
+
const hash = crypto.createHash("sha256").update(userEntityRef).digest("hex").substring(0, 8);
|
|
36
|
+
return `kuadrant-${sanitized}-${hash}`;
|
|
37
|
+
}
|
|
38
|
+
async function ensureConsumerNamespace(k8sClient, namespace) {
|
|
39
|
+
try {
|
|
40
|
+
await k8sClient.getNamespace(namespace);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const statusCode = error.statusCode || error.response?.statusCode || error.body?.code;
|
|
43
|
+
if (statusCode === 404) {
|
|
44
|
+
try {
|
|
45
|
+
await k8sClient.createNamespace({
|
|
46
|
+
apiVersion: "v1",
|
|
47
|
+
kind: "Namespace",
|
|
48
|
+
metadata: {
|
|
49
|
+
name: namespace,
|
|
50
|
+
labels: {
|
|
51
|
+
"devportal.kuadrant.io/consumer-namespace": "true"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} catch (createError) {
|
|
56
|
+
const createStatusCode = createError.statusCode || createError.response?.statusCode || createError.body?.code;
|
|
57
|
+
if (createStatusCode !== 409) {
|
|
58
|
+
throw createError;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
26
66
|
async function getUserIdentity(req, httpAuth, userInfo) {
|
|
27
67
|
const credentials = await httpAuth.credentials(req);
|
|
28
68
|
if (!credentials || !credentials.principal) {
|
|
@@ -36,6 +76,38 @@ async function getUserIdentity(req, httpAuth, userInfo) {
|
|
|
36
76
|
groups
|
|
37
77
|
};
|
|
38
78
|
}
|
|
79
|
+
async function verifyApiKeyUpdatePermission(credentials, userEntityRef, namespace, apiProductName, k8sClient, permissions$1) {
|
|
80
|
+
const updateAllDecision = await permissions$1.authorize(
|
|
81
|
+
[{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
|
|
82
|
+
{ credentials }
|
|
83
|
+
);
|
|
84
|
+
if (updateAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const updateOwnDecision = await permissions$1.authorize(
|
|
88
|
+
[{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
|
|
89
|
+
{ credentials }
|
|
90
|
+
);
|
|
91
|
+
if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
92
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
93
|
+
}
|
|
94
|
+
let apiProduct;
|
|
95
|
+
try {
|
|
96
|
+
apiProduct = await k8sClient.getCustomResource(
|
|
97
|
+
"devportal.kuadrant.io",
|
|
98
|
+
"v1alpha1",
|
|
99
|
+
namespace,
|
|
100
|
+
"apiproducts",
|
|
101
|
+
apiProductName
|
|
102
|
+
);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw new errors.InputError(`APIProduct '${apiProductName}' not found in namespace '${namespace}' - cannot verify ownership`);
|
|
105
|
+
}
|
|
106
|
+
const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
|
|
107
|
+
if (owner !== userEntityRef) {
|
|
108
|
+
throw new errors.NotAllowedError("you can only update requests for your own api products");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
39
111
|
async function createRouter({
|
|
40
112
|
httpAuth,
|
|
41
113
|
userInfo,
|
|
@@ -155,7 +227,7 @@ async function createRouter({
|
|
|
155
227
|
"apiproducts",
|
|
156
228
|
apiProduct
|
|
157
229
|
);
|
|
158
|
-
const provider =
|
|
230
|
+
const provider = module$1.getAPIProductEntityProvider();
|
|
159
231
|
if (provider) {
|
|
160
232
|
await provider.refresh();
|
|
161
233
|
}
|
|
@@ -201,8 +273,7 @@ async function createRouter({
|
|
|
201
273
|
allRequests = await k8sClient$1.listCustomResources(
|
|
202
274
|
"devportal.kuadrant.io",
|
|
203
275
|
"v1alpha1",
|
|
204
|
-
"apikeys"
|
|
205
|
-
namespace
|
|
276
|
+
"apikeys"
|
|
206
277
|
);
|
|
207
278
|
} catch (error) {
|
|
208
279
|
console.warn("failed to list apikeys during cascade delete:", error);
|
|
@@ -215,11 +286,12 @@ async function createRouter({
|
|
|
215
286
|
const deletionResults = await Promise.allSettled(
|
|
216
287
|
relatedRequests.map(async (request) => {
|
|
217
288
|
const requestName = request.metadata.name;
|
|
218
|
-
|
|
289
|
+
const requestNamespace = request.metadata.namespace;
|
|
290
|
+
console.log(`deleting apikey: ${requestNamespace}/${requestName}`);
|
|
219
291
|
await k8sClient$1.deleteCustomResource(
|
|
220
292
|
"devportal.kuadrant.io",
|
|
221
293
|
"v1alpha1",
|
|
222
|
-
|
|
294
|
+
requestNamespace,
|
|
223
295
|
"apikeys",
|
|
224
296
|
requestName
|
|
225
297
|
);
|
|
@@ -239,7 +311,7 @@ async function createRouter({
|
|
|
239
311
|
"apiproducts",
|
|
240
312
|
name
|
|
241
313
|
);
|
|
242
|
-
const provider =
|
|
314
|
+
const provider = module$1.getAPIProductEntityProvider();
|
|
243
315
|
if (provider) {
|
|
244
316
|
await provider.refresh();
|
|
245
317
|
}
|
|
@@ -257,7 +329,7 @@ async function createRouter({
|
|
|
257
329
|
try {
|
|
258
330
|
const credentials = await httpAuth.credentials(req);
|
|
259
331
|
const decision = await permissions$1.authorize(
|
|
260
|
-
[{ permission: permissions.
|
|
332
|
+
[{ permission: permissions.kuadrantHttpRouteListPermission }],
|
|
261
333
|
{ credentials }
|
|
262
334
|
);
|
|
263
335
|
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
@@ -274,8 +346,37 @@ async function createRouter({
|
|
|
274
346
|
}
|
|
275
347
|
}
|
|
276
348
|
});
|
|
349
|
+
router.get("/httproutes/:namespace/:name", async (req, res) => {
|
|
350
|
+
try {
|
|
351
|
+
const credentials = await httpAuth.credentials(req);
|
|
352
|
+
const decision = await permissions$1.authorize(
|
|
353
|
+
[{ permission: permissions.kuadrantHttpRouteListPermission }],
|
|
354
|
+
{ credentials }
|
|
355
|
+
);
|
|
356
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
357
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
358
|
+
}
|
|
359
|
+
const { namespace, name } = req.params;
|
|
360
|
+
const data = await k8sClient$1.getCustomResource("gateway.networking.k8s.io", "v1", namespace, "httproutes", name);
|
|
361
|
+
res.json(data);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error("error fetching httproute:", error);
|
|
364
|
+
if (error instanceof errors.NotAllowedError) {
|
|
365
|
+
res.status(403).json({ error: error.message });
|
|
366
|
+
} else {
|
|
367
|
+
res.status(500).json({ error: "failed to fetch httproute" });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
});
|
|
277
371
|
router.patch("/apiproducts/:namespace/:name", async (req, res) => {
|
|
278
372
|
const patchSchema = zod.z.object({
|
|
373
|
+
metadata: zod.z.object({
|
|
374
|
+
labels: zod.z.object({
|
|
375
|
+
// allow updating lifecycle phase via edit apiproduct dialog
|
|
376
|
+
// synced to backstage catalog entity's lifecycle field
|
|
377
|
+
lifecycle: zod.z.enum(["experimental", "production", "deprecated", "retired"]).optional()
|
|
378
|
+
}).partial().optional()
|
|
379
|
+
}).partial().optional(),
|
|
279
380
|
spec: zod.z.object({
|
|
280
381
|
displayName: zod.z.string().optional(),
|
|
281
382
|
description: zod.z.string().optional(),
|
|
@@ -290,7 +391,7 @@ async function createRouter({
|
|
|
290
391
|
}).partial().optional(),
|
|
291
392
|
documentation: zod.z.object({
|
|
292
393
|
docsURL: zod.z.string().optional(),
|
|
293
|
-
|
|
394
|
+
openAPISpecURL: zod.z.string().optional()
|
|
294
395
|
}).partial().optional()
|
|
295
396
|
}).partial()
|
|
296
397
|
});
|
|
@@ -326,6 +427,18 @@ async function createRouter({
|
|
|
326
427
|
if (req.body.metadata?.annotations) {
|
|
327
428
|
delete req.body.metadata.annotations["backstage.io/owner"];
|
|
328
429
|
}
|
|
430
|
+
if (parsed.data.spec?.publishStatus === "Published" && parsed.data.metadata?.labels?.lifecycle === "retired") {
|
|
431
|
+
throw new errors.InputError("cannot publish a retired API product");
|
|
432
|
+
}
|
|
433
|
+
if (parsed.data.metadata?.labels?.lifecycle === "retired") {
|
|
434
|
+
const existing = await k8sClient$1.getCustomResource("devportal.kuadrant.io", "v1alpha1", namespace, "apiproducts", name);
|
|
435
|
+
if (existing.spec?.publishStatus === "Published") {
|
|
436
|
+
if (!parsed.data.spec) {
|
|
437
|
+
parsed.data.spec = {};
|
|
438
|
+
}
|
|
439
|
+
parsed.data.spec.publishStatus = "Draft";
|
|
440
|
+
}
|
|
441
|
+
}
|
|
329
442
|
const updated = await k8sClient$1.patchCustomResource(
|
|
330
443
|
"devportal.kuadrant.io",
|
|
331
444
|
"v1alpha1",
|
|
@@ -334,7 +447,7 @@ async function createRouter({
|
|
|
334
447
|
name,
|
|
335
448
|
parsed.data
|
|
336
449
|
);
|
|
337
|
-
const provider =
|
|
450
|
+
const provider = module$1.getAPIProductEntityProvider();
|
|
338
451
|
if (provider) {
|
|
339
452
|
await provider.refresh();
|
|
340
453
|
}
|
|
@@ -368,18 +481,20 @@ async function createRouter({
|
|
|
368
481
|
name: policy.metadata.name,
|
|
369
482
|
namespace: policy.metadata.namespace
|
|
370
483
|
},
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
484
|
+
spec: policy.spec ? {
|
|
485
|
+
// expose targetRef to allow UI to match PlanPolicy -> HTTPRoute
|
|
486
|
+
targetRef: policy.spec.targetRef ? {
|
|
487
|
+
kind: policy.spec.targetRef.kind,
|
|
488
|
+
name: policy.spec.targetRef.name,
|
|
489
|
+
namespace: policy.spec.targetRef.namespace
|
|
490
|
+
} : void 0,
|
|
491
|
+
plans: (policy.spec.plans || []).map((plan) => ({
|
|
492
|
+
tier: plan.tier,
|
|
493
|
+
description: plan.description,
|
|
494
|
+
limits: plan.limits
|
|
495
|
+
}))
|
|
496
|
+
} : {},
|
|
497
|
+
status: policy.status
|
|
383
498
|
}))
|
|
384
499
|
};
|
|
385
500
|
res.json(filtered);
|
|
@@ -414,14 +529,93 @@ async function createRouter({
|
|
|
414
529
|
}
|
|
415
530
|
}
|
|
416
531
|
});
|
|
532
|
+
const secretSchema = zod.z.object({
|
|
533
|
+
name: zod.z.string().min(1),
|
|
534
|
+
apiKeyValue: zod.z.string().min(1)
|
|
535
|
+
});
|
|
536
|
+
router.post("/secrets", async (req, res) => {
|
|
537
|
+
try {
|
|
538
|
+
const parsed = secretSchema.safeParse(req.body);
|
|
539
|
+
if (!parsed.success) {
|
|
540
|
+
throw new errors.InputError(parsed.error.toString());
|
|
541
|
+
}
|
|
542
|
+
const credentials = await httpAuth.credentials(req);
|
|
543
|
+
const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
|
|
544
|
+
const consumerNamespace = getConsumerNamespace(userEntityRef);
|
|
545
|
+
const resourceRef = `secret:${consumerNamespace}/*`;
|
|
546
|
+
const decision = await permissions$1.authorize(
|
|
547
|
+
[{
|
|
548
|
+
permission: permissions.kuadrantApiKeyCreatePermission,
|
|
549
|
+
resourceRef
|
|
550
|
+
}],
|
|
551
|
+
{ credentials }
|
|
552
|
+
);
|
|
553
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
554
|
+
throw new errors.NotAllowedError("not authorized to create secrets");
|
|
555
|
+
}
|
|
556
|
+
await ensureConsumerNamespace(k8sClient$1, consumerNamespace);
|
|
557
|
+
const { name, apiKeyValue } = parsed.data;
|
|
558
|
+
const secret = {
|
|
559
|
+
apiVersion: "v1",
|
|
560
|
+
kind: "Secret",
|
|
561
|
+
metadata: {
|
|
562
|
+
name,
|
|
563
|
+
namespace: consumerNamespace
|
|
564
|
+
},
|
|
565
|
+
type: "Opaque",
|
|
566
|
+
data: {
|
|
567
|
+
api_key: Buffer.from(apiKeyValue).toString("base64")
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
const created = await k8sClient$1.createSecret(consumerNamespace, secret);
|
|
571
|
+
res.status(201).json(created);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
console.error("error creating secret:", error);
|
|
574
|
+
if (error instanceof errors.NotAllowedError) {
|
|
575
|
+
res.status(403).json({ error: error.message });
|
|
576
|
+
} else if (error instanceof errors.InputError) {
|
|
577
|
+
res.status(400).json({ error: error.message });
|
|
578
|
+
} else {
|
|
579
|
+
const errorMessage = error instanceof Error ? error.message : "failed to create secret";
|
|
580
|
+
res.status(500).json({ error: errorMessage });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
router.delete("/secrets/:name", async (req, res) => {
|
|
585
|
+
try {
|
|
586
|
+
const credentials = await httpAuth.credentials(req);
|
|
587
|
+
const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
|
|
588
|
+
const { name } = req.params;
|
|
589
|
+
const consumerNamespace = getConsumerNamespace(userEntityRef);
|
|
590
|
+
const decision = await permissions$1.authorize(
|
|
591
|
+
[{ permission: permissions.kuadrantApiKeyCreatePermission, resourceRef: `secret:${consumerNamespace}/*` }],
|
|
592
|
+
{ credentials }
|
|
593
|
+
);
|
|
594
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
595
|
+
throw new errors.NotAllowedError("not authorized to delete secrets");
|
|
596
|
+
}
|
|
597
|
+
await k8sClient$1.deleteSecret(consumerNamespace, name);
|
|
598
|
+
res.status(204).send();
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.error("error deleting secret:", error);
|
|
601
|
+
if (error instanceof errors.NotAllowedError) {
|
|
602
|
+
res.status(403).json({ error: error.message });
|
|
603
|
+
} else {
|
|
604
|
+
const errorMessage = error instanceof Error ? error.message : "failed to delete secret";
|
|
605
|
+
res.status(500).json({ error: errorMessage });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
});
|
|
417
609
|
const requestSchema = zod.z.object({
|
|
418
610
|
apiProductName: zod.z.string(),
|
|
419
611
|
// name of the APIProduct
|
|
420
612
|
namespace: zod.z.string(),
|
|
421
|
-
// namespace where
|
|
613
|
+
// owner's namespace (where the APIProduct lives)
|
|
422
614
|
planTier: zod.z.string(),
|
|
423
615
|
useCase: zod.z.string().optional(),
|
|
424
|
-
userEmail: zod.z.string().optional()
|
|
616
|
+
userEmail: zod.z.string().optional(),
|
|
617
|
+
secretName: zod.z.string()
|
|
618
|
+
// frontend creates secret via POST /secrets first
|
|
425
619
|
});
|
|
426
620
|
router.post("/requests", async (req, res) => {
|
|
427
621
|
const parsed = requestSchema.safeParse(req.body);
|
|
@@ -430,7 +624,7 @@ async function createRouter({
|
|
|
430
624
|
}
|
|
431
625
|
try {
|
|
432
626
|
const credentials = await httpAuth.credentials(req);
|
|
433
|
-
const { apiProductName, namespace, planTier, useCase, userEmail } = parsed.data;
|
|
627
|
+
const { apiProductName, namespace, planTier, useCase, userEmail, secretName: frontendSecretName } = parsed.data;
|
|
434
628
|
const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
|
|
435
629
|
const resourceRef = `apiproduct:${namespace}/${apiProductName}`;
|
|
436
630
|
const decision = await permissions$1.authorize(
|
|
@@ -443,23 +637,33 @@ async function createRouter({
|
|
|
443
637
|
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
444
638
|
throw new errors.NotAllowedError(`not authorised to request access to ${apiProductName}`);
|
|
445
639
|
}
|
|
640
|
+
const consumerNamespace = getConsumerNamespace(userEntityRef);
|
|
641
|
+
await ensureConsumerNamespace(k8sClient$1, consumerNamespace);
|
|
446
642
|
const randomSuffix = crypto.randomBytes(4).toString("hex");
|
|
447
|
-
const
|
|
448
|
-
const requestName = `${userName}-${apiProductName}-${randomSuffix}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
643
|
+
const requestName = `${apiProductName}-${randomSuffix}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
449
644
|
const requestedBy = { userId: userEntityRef };
|
|
450
645
|
if (userEmail) {
|
|
451
646
|
requestedBy.email = userEmail;
|
|
452
647
|
}
|
|
648
|
+
if (!frontendSecretName) {
|
|
649
|
+
throw new errors.InputError("secretName is required - create the secret via POST /secrets first");
|
|
650
|
+
}
|
|
651
|
+
const secretName = frontendSecretName;
|
|
453
652
|
const request = {
|
|
454
653
|
apiVersion: "devportal.kuadrant.io/v1alpha1",
|
|
455
654
|
kind: "APIKey",
|
|
456
655
|
metadata: {
|
|
457
656
|
name: requestName,
|
|
458
|
-
namespace
|
|
657
|
+
namespace: consumerNamespace
|
|
459
658
|
},
|
|
460
659
|
spec: {
|
|
461
660
|
apiProductRef: {
|
|
462
|
-
name: apiProductName
|
|
661
|
+
name: apiProductName,
|
|
662
|
+
namespace
|
|
663
|
+
// cross-namespace reference to owner's APIProduct
|
|
664
|
+
},
|
|
665
|
+
secretRef: {
|
|
666
|
+
name: secretName
|
|
463
667
|
},
|
|
464
668
|
planTier,
|
|
465
669
|
useCase: useCase || "",
|
|
@@ -469,7 +673,7 @@ async function createRouter({
|
|
|
469
673
|
const created = await k8sClient$1.createCustomResource(
|
|
470
674
|
"devportal.kuadrant.io",
|
|
471
675
|
"v1alpha1",
|
|
472
|
-
|
|
676
|
+
consumerNamespace,
|
|
473
677
|
"apikeys",
|
|
474
678
|
request
|
|
475
679
|
);
|
|
@@ -479,7 +683,8 @@ async function createRouter({
|
|
|
479
683
|
if (error instanceof errors.NotAllowedError) {
|
|
480
684
|
res.status(403).json({ error: error.message });
|
|
481
685
|
} else {
|
|
482
|
-
|
|
686
|
+
const errorMessage = error instanceof Error ? error.message : "failed to create api key request";
|
|
687
|
+
res.status(500).json({ error: errorMessage });
|
|
483
688
|
}
|
|
484
689
|
}
|
|
485
690
|
});
|
|
@@ -502,11 +707,13 @@ async function createRouter({
|
|
|
502
707
|
}
|
|
503
708
|
const status = req.query.status;
|
|
504
709
|
const namespace = req.query.namespace;
|
|
710
|
+
const apiProductName = req.query.apiProductName;
|
|
711
|
+
const apiProductNamespace = req.query.apiProductNamespace;
|
|
505
712
|
let data;
|
|
506
713
|
if (namespace) {
|
|
507
|
-
data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "
|
|
714
|
+
data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeyrequests", namespace);
|
|
508
715
|
} else {
|
|
509
|
-
data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "
|
|
716
|
+
data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeyrequests");
|
|
510
717
|
}
|
|
511
718
|
let filteredItems = data.items || [];
|
|
512
719
|
if (!canReadAll) {
|
|
@@ -520,9 +727,32 @@ async function createRouter({
|
|
|
520
727
|
(req2) => ownedApiProducts.includes(req2.spec?.apiProductRef?.name)
|
|
521
728
|
);
|
|
522
729
|
}
|
|
730
|
+
if (apiProductName) {
|
|
731
|
+
filteredItems = filteredItems.filter(
|
|
732
|
+
(req2) => req2.spec?.apiProductRef?.name === apiProductName
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (apiProductNamespace) {
|
|
736
|
+
filteredItems = filteredItems.filter(
|
|
737
|
+
(req2) => req2.spec?.apiProductRef?.namespace === apiProductNamespace
|
|
738
|
+
);
|
|
739
|
+
}
|
|
523
740
|
if (status) {
|
|
524
741
|
filteredItems = filteredItems.filter((req2) => {
|
|
525
|
-
|
|
742
|
+
let phase = "Pending";
|
|
743
|
+
if (req2.status?.conditions && req2.status.conditions.length > 0) {
|
|
744
|
+
const approved = req2.status.conditions.find(
|
|
745
|
+
(c) => c.type === "Approved" && c.status === "True"
|
|
746
|
+
);
|
|
747
|
+
if (approved) {
|
|
748
|
+
phase = "Approved";
|
|
749
|
+
} else {
|
|
750
|
+
const denied = req2.status.conditions.find(
|
|
751
|
+
(c) => c.type === "Denied" && c.status === "True"
|
|
752
|
+
);
|
|
753
|
+
if (denied) phase = "Denied";
|
|
754
|
+
}
|
|
755
|
+
}
|
|
526
756
|
return phase === status;
|
|
527
757
|
});
|
|
528
758
|
}
|
|
@@ -547,16 +777,32 @@ async function createRouter({
|
|
|
547
777
|
throw new errors.NotAllowedError("unauthorised");
|
|
548
778
|
}
|
|
549
779
|
const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
|
|
550
|
-
const
|
|
780
|
+
const apiProductName = req.query.apiProductName;
|
|
781
|
+
const apiProductNamespace = req.query.apiProductNamespace;
|
|
782
|
+
const consumerNs = getConsumerNamespace(userEntityRef);
|
|
551
783
|
let data;
|
|
552
|
-
|
|
553
|
-
data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeys",
|
|
554
|
-
}
|
|
555
|
-
|
|
784
|
+
try {
|
|
785
|
+
data = await k8sClient$1.listCustomResources("devportal.kuadrant.io", "v1alpha1", "apikeys", consumerNs);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
if (error.message?.includes("404") || error.statusCode === 404) {
|
|
788
|
+
res.json({ items: [] });
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
throw error;
|
|
556
792
|
}
|
|
557
|
-
|
|
793
|
+
let filteredItems = (data.items || []).filter(
|
|
558
794
|
(req2) => req2.spec?.requestedBy?.userId === userEntityRef
|
|
559
795
|
);
|
|
796
|
+
if (apiProductName) {
|
|
797
|
+
filteredItems = filteredItems.filter(
|
|
798
|
+
(req2) => req2.spec?.apiProductRef?.name === apiProductName
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (apiProductNamespace) {
|
|
802
|
+
filteredItems = filteredItems.filter(
|
|
803
|
+
(req2) => req2.spec?.apiProductRef?.namespace === apiProductNamespace
|
|
804
|
+
);
|
|
805
|
+
}
|
|
560
806
|
res.json({ items: filteredItems });
|
|
561
807
|
} catch (error) {
|
|
562
808
|
console.error("error fetching user api key requests:", error);
|
|
@@ -584,50 +830,43 @@ async function createRouter({
|
|
|
584
830
|
"devportal.kuadrant.io",
|
|
585
831
|
"v1alpha1",
|
|
586
832
|
namespace,
|
|
587
|
-
"
|
|
833
|
+
"apikeyrequests",
|
|
588
834
|
name
|
|
589
835
|
);
|
|
590
836
|
const spec = request.spec;
|
|
591
837
|
const apiProductName = spec.apiProductRef?.name;
|
|
592
838
|
if (!apiProductName) {
|
|
593
|
-
throw new errors.InputError("apiProductRef.name is required in
|
|
839
|
+
throw new errors.InputError("apiProductRef.name is required in APIKeyRequest spec");
|
|
594
840
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
841
|
+
await verifyApiKeyUpdatePermission(
|
|
842
|
+
credentials,
|
|
843
|
+
userEntityRef,
|
|
598
844
|
namespace,
|
|
599
|
-
|
|
600
|
-
|
|
845
|
+
apiProductName,
|
|
846
|
+
k8sClient$1,
|
|
847
|
+
permissions$1
|
|
601
848
|
);
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (owner !== userEntityRef) {
|
|
616
|
-
throw new errors.NotAllowedError("you can only approve requests for your own api products");
|
|
849
|
+
const approvalName = `${name}-approval-${crypto.randomBytes(4).toString("hex")}`;
|
|
850
|
+
const approval = {
|
|
851
|
+
apiVersion: "devportal.kuadrant.io/v1alpha1",
|
|
852
|
+
kind: "APIKeyApproval",
|
|
853
|
+
metadata: {
|
|
854
|
+
name: approvalName,
|
|
855
|
+
namespace
|
|
856
|
+
},
|
|
857
|
+
spec: {
|
|
858
|
+
apiKeyRequestRef: { name },
|
|
859
|
+
approved: true,
|
|
860
|
+
reviewedBy,
|
|
861
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
617
862
|
}
|
|
618
|
-
}
|
|
619
|
-
const status = {
|
|
620
|
-
phase: "Approved",
|
|
621
|
-
reviewedBy,
|
|
622
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
623
863
|
};
|
|
624
|
-
await k8sClient$1.
|
|
864
|
+
await k8sClient$1.createCustomResource(
|
|
625
865
|
"devportal.kuadrant.io",
|
|
626
866
|
"v1alpha1",
|
|
627
867
|
namespace,
|
|
628
|
-
"
|
|
629
|
-
|
|
630
|
-
status
|
|
868
|
+
"apikeyapprovals",
|
|
869
|
+
approval
|
|
631
870
|
);
|
|
632
871
|
res.json({ success: true });
|
|
633
872
|
} catch (error) {
|
|
@@ -653,52 +892,45 @@ async function createRouter({
|
|
|
653
892
|
"devportal.kuadrant.io",
|
|
654
893
|
"v1alpha1",
|
|
655
894
|
namespace,
|
|
656
|
-
"
|
|
895
|
+
"apikeyrequests",
|
|
657
896
|
name
|
|
658
897
|
);
|
|
659
898
|
const spec = request.spec;
|
|
660
899
|
const apiProductName = spec.apiProductRef?.name;
|
|
661
900
|
if (!apiProductName) {
|
|
662
|
-
throw new errors.InputError("apiProductRef.name is required in
|
|
901
|
+
throw new errors.InputError("apiProductRef.name is required in APIKeyRequest spec");
|
|
663
902
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
903
|
+
await verifyApiKeyUpdatePermission(
|
|
904
|
+
credentials,
|
|
905
|
+
userEntityRef,
|
|
667
906
|
namespace,
|
|
668
|
-
|
|
669
|
-
|
|
907
|
+
apiProductName,
|
|
908
|
+
k8sClient$1,
|
|
909
|
+
permissions$1
|
|
670
910
|
);
|
|
671
|
-
const
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (owner !== userEntityRef) {
|
|
685
|
-
throw new errors.NotAllowedError("you can only reject requests for your own api products");
|
|
911
|
+
const approvalName = `${name}-denial-${crypto.randomBytes(4).toString("hex")}`;
|
|
912
|
+
const approval = {
|
|
913
|
+
apiVersion: "devportal.kuadrant.io/v1alpha1",
|
|
914
|
+
kind: "APIKeyApproval",
|
|
915
|
+
metadata: {
|
|
916
|
+
name: approvalName,
|
|
917
|
+
namespace
|
|
918
|
+
},
|
|
919
|
+
spec: {
|
|
920
|
+
apiKeyRequestRef: { name },
|
|
921
|
+
approved: false,
|
|
922
|
+
reviewedBy,
|
|
923
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
686
924
|
}
|
|
687
|
-
}
|
|
688
|
-
const status = {
|
|
689
|
-
phase: "Rejected",
|
|
690
|
-
reviewedBy,
|
|
691
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
692
925
|
};
|
|
693
|
-
await k8sClient$1.
|
|
926
|
+
await k8sClient$1.createCustomResource(
|
|
694
927
|
"devportal.kuadrant.io",
|
|
695
928
|
"v1alpha1",
|
|
696
929
|
namespace,
|
|
697
|
-
"
|
|
698
|
-
|
|
699
|
-
status
|
|
930
|
+
"apikeyapprovals",
|
|
931
|
+
approval
|
|
700
932
|
);
|
|
701
|
-
res.
|
|
933
|
+
res.json({ success: true });
|
|
702
934
|
} catch (error) {
|
|
703
935
|
console.error("error rejecting api key request:", error);
|
|
704
936
|
if (error instanceof errors.NotAllowedError) {
|
|
@@ -723,73 +955,52 @@ async function createRouter({
|
|
|
723
955
|
try {
|
|
724
956
|
const credentials = await httpAuth.credentials(req);
|
|
725
957
|
const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
|
|
726
|
-
const
|
|
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;
|
|
958
|
+
const { requests, comment } = parsed.data;
|
|
741
959
|
const reviewedBy = userEntityRef;
|
|
742
960
|
const results = [];
|
|
743
961
|
for (const reqRef of requests) {
|
|
744
962
|
try {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|
-
}
|
|
963
|
+
const request = await k8sClient$1.getCustomResource(
|
|
964
|
+
"devportal.kuadrant.io",
|
|
965
|
+
"v1alpha1",
|
|
966
|
+
reqRef.namespace,
|
|
967
|
+
"apikeyrequests",
|
|
968
|
+
reqRef.name
|
|
969
|
+
);
|
|
970
|
+
const apiProductName = request.spec?.apiProductRef?.name;
|
|
971
|
+
if (!apiProductName) {
|
|
972
|
+
throw new errors.InputError("apiProductRef.name is required in APIKeyRequest spec");
|
|
780
973
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
974
|
+
await verifyApiKeyUpdatePermission(
|
|
975
|
+
credentials,
|
|
976
|
+
userEntityRef,
|
|
977
|
+
reqRef.namespace,
|
|
978
|
+
apiProductName,
|
|
979
|
+
k8sClient$1,
|
|
980
|
+
permissions$1
|
|
981
|
+
);
|
|
982
|
+
const approvalName = `${reqRef.name}-approval-${crypto.randomBytes(4).toString("hex")}`;
|
|
983
|
+
const approval = {
|
|
984
|
+
apiVersion: "devportal.kuadrant.io/v1alpha1",
|
|
985
|
+
kind: "APIKeyApproval",
|
|
986
|
+
metadata: {
|
|
987
|
+
name: approvalName,
|
|
988
|
+
namespace: reqRef.namespace
|
|
989
|
+
},
|
|
990
|
+
spec: {
|
|
991
|
+
apiKeyRequestRef: { name: reqRef.name },
|
|
992
|
+
approved: true,
|
|
993
|
+
reviewedBy,
|
|
994
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
995
|
+
...comment && { comment }
|
|
996
|
+
}
|
|
785
997
|
};
|
|
786
|
-
await k8sClient$1.
|
|
998
|
+
await k8sClient$1.createCustomResource(
|
|
787
999
|
"devportal.kuadrant.io",
|
|
788
1000
|
"v1alpha1",
|
|
789
1001
|
reqRef.namespace,
|
|
790
|
-
"
|
|
791
|
-
|
|
792
|
-
status
|
|
1002
|
+
"apikeyapprovals",
|
|
1003
|
+
approval
|
|
793
1004
|
);
|
|
794
1005
|
results.push({ namespace: reqRef.namespace, name: reqRef.name, success: true });
|
|
795
1006
|
} catch (error) {
|
|
@@ -820,21 +1031,7 @@ async function createRouter({
|
|
|
820
1031
|
try {
|
|
821
1032
|
const credentials = await httpAuth.credentials(req);
|
|
822
1033
|
const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
|
|
823
|
-
const
|
|
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;
|
|
1034
|
+
const { requests, comment } = parsed.data;
|
|
838
1035
|
const reviewedBy = userEntityRef;
|
|
839
1036
|
const results = [];
|
|
840
1037
|
for (const reqRef of requests) {
|
|
@@ -843,39 +1040,43 @@ async function createRouter({
|
|
|
843
1040
|
"devportal.kuadrant.io",
|
|
844
1041
|
"v1alpha1",
|
|
845
1042
|
reqRef.namespace,
|
|
846
|
-
"
|
|
1043
|
+
"apikeyrequests",
|
|
847
1044
|
reqRef.name
|
|
848
1045
|
);
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
"
|
|
852
|
-
|
|
1046
|
+
const apiProductName = request.spec?.apiProductRef?.name;
|
|
1047
|
+
if (!apiProductName) {
|
|
1048
|
+
throw new errors.InputError("apiProductRef.name is required in APIKeyRequest spec");
|
|
1049
|
+
}
|
|
1050
|
+
await verifyApiKeyUpdatePermission(
|
|
1051
|
+
credentials,
|
|
1052
|
+
userEntityRef,
|
|
853
1053
|
reqRef.namespace,
|
|
854
|
-
|
|
855
|
-
|
|
1054
|
+
apiProductName,
|
|
1055
|
+
k8sClient$1,
|
|
1056
|
+
permissions$1
|
|
856
1057
|
);
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1058
|
+
const approvalName = `${reqRef.name}-denial-${crypto.randomBytes(4).toString("hex")}`;
|
|
1059
|
+
const approval = {
|
|
1060
|
+
apiVersion: "devportal.kuadrant.io/v1alpha1",
|
|
1061
|
+
kind: "APIKeyApproval",
|
|
1062
|
+
metadata: {
|
|
1063
|
+
name: approvalName,
|
|
1064
|
+
namespace: reqRef.namespace
|
|
1065
|
+
},
|
|
1066
|
+
spec: {
|
|
1067
|
+
apiKeyRequestRef: { name: reqRef.name },
|
|
1068
|
+
approved: false,
|
|
1069
|
+
reviewedBy,
|
|
1070
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1071
|
+
...comment && { comment }
|
|
1072
|
+
}
|
|
871
1073
|
};
|
|
872
|
-
await k8sClient$1.
|
|
1074
|
+
await k8sClient$1.createCustomResource(
|
|
873
1075
|
"devportal.kuadrant.io",
|
|
874
1076
|
"v1alpha1",
|
|
875
1077
|
reqRef.namespace,
|
|
876
|
-
"
|
|
877
|
-
|
|
878
|
-
status
|
|
1078
|
+
"apikeyapprovals",
|
|
1079
|
+
approval
|
|
879
1080
|
);
|
|
880
1081
|
results.push({ namespace: reqRef.namespace, name: reqRef.name, success: true });
|
|
881
1082
|
} catch (error) {
|
|
@@ -903,30 +1104,23 @@ async function createRouter({
|
|
|
903
1104
|
const credentials = await httpAuth.credentials(req);
|
|
904
1105
|
const { userEntityRef } = await getUserIdentity(req, httpAuth, userInfo);
|
|
905
1106
|
const { namespace, name } = req.params;
|
|
906
|
-
const
|
|
1107
|
+
const apiKey = await k8sClient$1.getCustomResource(
|
|
907
1108
|
"devportal.kuadrant.io",
|
|
908
1109
|
"v1alpha1",
|
|
909
1110
|
namespace,
|
|
910
1111
|
"apikeys",
|
|
911
1112
|
name
|
|
912
1113
|
);
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
[{ permission: permissions.kuadrantApiKeyDeleteAllPermission }],
|
|
1114
|
+
const deleteOwnDecision = await permissions$1.authorize(
|
|
1115
|
+
[{ permission: permissions.kuadrantApiKeyDeleteOwnPermission }],
|
|
916
1116
|
{ credentials }
|
|
917
1117
|
);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
-
}
|
|
1118
|
+
if (deleteOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
1119
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
1120
|
+
}
|
|
1121
|
+
const requestUserId = apiKey.spec?.requestedBy?.userId;
|
|
1122
|
+
if (requestUserId !== userEntityRef) {
|
|
1123
|
+
throw new errors.NotAllowedError("you can only delete your own api key requests");
|
|
930
1124
|
}
|
|
931
1125
|
await k8sClient$1.deleteCustomResource(
|
|
932
1126
|
"devportal.kuadrant.io",
|
|
@@ -968,7 +1162,21 @@ async function createRouter({
|
|
|
968
1162
|
name
|
|
969
1163
|
);
|
|
970
1164
|
const requestUserId = existing.spec?.requestedBy?.userId;
|
|
971
|
-
|
|
1165
|
+
let currentPhase = "Pending";
|
|
1166
|
+
const conditions = existing.status?.conditions;
|
|
1167
|
+
if (conditions && conditions.length > 0) {
|
|
1168
|
+
const approved = conditions.find(
|
|
1169
|
+
(c) => c.type === "Approved" && c.status === "True"
|
|
1170
|
+
);
|
|
1171
|
+
if (approved) {
|
|
1172
|
+
currentPhase = "Approved";
|
|
1173
|
+
} else {
|
|
1174
|
+
const denied = conditions.find(
|
|
1175
|
+
(c) => c.type === "Denied" && c.status === "True"
|
|
1176
|
+
);
|
|
1177
|
+
if (denied) currentPhase = "Denied";
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
972
1180
|
if (currentPhase !== "Pending") {
|
|
973
1181
|
throw new errors.NotAllowedError("only pending requests can be edited");
|
|
974
1182
|
}
|
|
@@ -1086,19 +1294,13 @@ async function createRouter({
|
|
|
1086
1294
|
throw new errors.NotAllowedError("you can only read your own api key secrets");
|
|
1087
1295
|
}
|
|
1088
1296
|
}
|
|
1089
|
-
if (apiKey.
|
|
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) {
|
|
1297
|
+
if (!apiKey.spec?.secretRef?.name) {
|
|
1096
1298
|
res.status(404).json({
|
|
1097
|
-
error: "
|
|
1299
|
+
error: "secretRef not found in APIKey spec"
|
|
1098
1300
|
});
|
|
1099
1301
|
return;
|
|
1100
1302
|
}
|
|
1101
|
-
const secretName = apiKey.
|
|
1303
|
+
const secretName = apiKey.spec.secretRef.name;
|
|
1102
1304
|
let secret;
|
|
1103
1305
|
try {
|
|
1104
1306
|
secret = await k8sClient$1.getSecret(namespace, secretName);
|
|
@@ -1118,17 +1320,6 @@ async function createRouter({
|
|
|
1118
1320
|
return;
|
|
1119
1321
|
}
|
|
1120
1322
|
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
1323
|
res.json({
|
|
1133
1324
|
apiKey: decodedApiKey
|
|
1134
1325
|
});
|
|
@@ -1141,6 +1332,82 @@ async function createRouter({
|
|
|
1141
1332
|
}
|
|
1142
1333
|
}
|
|
1143
1334
|
});
|
|
1335
|
+
router.get("/authpolicies", async (req, res) => {
|
|
1336
|
+
try {
|
|
1337
|
+
const credentials = await httpAuth.credentials(req);
|
|
1338
|
+
const decision = await permissions$1.authorize(
|
|
1339
|
+
[{ permission: permissions.kuadrantAuthPolicyListPermission }],
|
|
1340
|
+
{ credentials }
|
|
1341
|
+
);
|
|
1342
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
1343
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
1344
|
+
}
|
|
1345
|
+
const data = await k8sClient$1.listCustomResources("kuadrant.io", "v1", "authpolicies");
|
|
1346
|
+
const filtered = {
|
|
1347
|
+
items: (data.items || []).map((policy) => ({
|
|
1348
|
+
metadata: {
|
|
1349
|
+
name: policy.metadata.name,
|
|
1350
|
+
namespace: policy.metadata.namespace
|
|
1351
|
+
},
|
|
1352
|
+
spec: policy.spec ? {
|
|
1353
|
+
// expose targetRef to allow UI to match AuthPolicy -> HTTPRoute
|
|
1354
|
+
targetRef: policy.spec.targetRef ? {
|
|
1355
|
+
kind: policy.spec.targetRef.kind,
|
|
1356
|
+
name: policy.spec.targetRef.name,
|
|
1357
|
+
namespace: policy.spec.targetRef.namespace
|
|
1358
|
+
} : void 0
|
|
1359
|
+
} : {},
|
|
1360
|
+
status: policy.status
|
|
1361
|
+
}))
|
|
1362
|
+
};
|
|
1363
|
+
res.json(filtered);
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
console.error("error fetching authpolicies:", error);
|
|
1366
|
+
if (error instanceof errors.NotAllowedError) {
|
|
1367
|
+
res.status(403).json({ error: error.message });
|
|
1368
|
+
} else {
|
|
1369
|
+
res.status(500).json({ error: "failed to fetch authpolicies" });
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
router.get("/ratelimitpolicies", async (req, res) => {
|
|
1374
|
+
try {
|
|
1375
|
+
const credentials = await httpAuth.credentials(req);
|
|
1376
|
+
const decision = await permissions$1.authorize(
|
|
1377
|
+
[{ permission: permissions.kuadrantRateLimitPolicyListPermission }],
|
|
1378
|
+
{ credentials }
|
|
1379
|
+
);
|
|
1380
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
1381
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
1382
|
+
}
|
|
1383
|
+
const data = await k8sClient$1.listCustomResources("kuadrant.io", "v1", "ratelimitpolicies");
|
|
1384
|
+
const filtered = {
|
|
1385
|
+
items: (data.items || []).map((policy) => ({
|
|
1386
|
+
metadata: {
|
|
1387
|
+
name: policy.metadata.name,
|
|
1388
|
+
namespace: policy.metadata.namespace
|
|
1389
|
+
},
|
|
1390
|
+
spec: policy.spec ? {
|
|
1391
|
+
// expose targetRef to allow UI to match AuthPolicy -> HTTPRoute
|
|
1392
|
+
targetRef: policy.spec.targetRef ? {
|
|
1393
|
+
kind: policy.spec.targetRef.kind,
|
|
1394
|
+
name: policy.spec.targetRef.name,
|
|
1395
|
+
namespace: policy.spec.targetRef.namespace
|
|
1396
|
+
} : void 0
|
|
1397
|
+
} : {},
|
|
1398
|
+
status: policy.status
|
|
1399
|
+
}))
|
|
1400
|
+
};
|
|
1401
|
+
res.json(filtered);
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
console.error("error fetching ratelimitpolicies:", error);
|
|
1404
|
+
if (error instanceof errors.NotAllowedError) {
|
|
1405
|
+
res.status(403).json({ error: error.message });
|
|
1406
|
+
} else {
|
|
1407
|
+
res.status(500).json({ error: "failed to fetch ratelimitpolicies" });
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1144
1411
|
router.use(pluginPermissionNode.createPermissionIntegrationRouter({
|
|
1145
1412
|
permissions: permissions.kuadrantPermissions
|
|
1146
1413
|
}));
|