@kuadrant/kuadrant-backstage-plugin-backend 0.0.1-test.1-d62c1cdb → 0.0.1-test.1-464d7760

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.
@@ -9,6 +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 alpha = require('./alpha.cjs.js');
12
13
  var permissions = require('./permissions.cjs.js');
13
14
 
14
15
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
@@ -21,45 +22,19 @@ function generateApiKey() {
21
22
  return crypto.randomBytes(32).toString("hex");
22
23
  }
23
24
  async function getUserIdentity(req, httpAuth, userInfo) {
24
- try {
25
- const credentials = await httpAuth.credentials(req, { allow: ["user", "none"] });
26
- if (!credentials || !credentials.principal || credentials.principal.type === "none") {
27
- console.log("no user credentials, treating as guest api owner");
28
- return {
29
- userId: "guest",
30
- isPlatformEngineer: false,
31
- isApiOwner: true,
32
- // allow guest as api owner in development
33
- isApiConsumer: true,
34
- groups: []
35
- };
36
- }
37
- const info = await userInfo.getUserInfo(credentials);
38
- const userId = info.userEntityRef.split("/")[1] || "guest";
39
- const groups = info.ownershipEntityRefs || [];
40
- const isPlatformEngineer = userId === "guest" || groups.some(
41
- (ref) => ref === "group:default/platform-engineers" || ref === "group:default/platform-admins"
42
- );
43
- const isApiOwner = userId === "guest" || groups.some(
44
- (ref) => ref === "group:default/api-owners" || ref === "group:default/app-developers"
45
- );
46
- const isApiConsumer = groups.some(
47
- (ref) => ref === "group:default/api-consumers"
48
- );
49
- console.log(`user identity resolved: userId=${userId}, isPlatformEngineer=${isPlatformEngineer}, isApiOwner=${isApiOwner}, isApiConsumer=${isApiConsumer}, groups=${groups.join(",")}`);
50
- return { userId, isPlatformEngineer, isApiOwner, isApiConsumer, groups };
51
- } catch (error) {
52
- const errorMsg = error instanceof Error ? error.message : String(error);
53
- console.warn(`failed to get user identity, defaulting to guest api owner: ${errorMsg}`);
54
- return {
55
- userId: "guest",
56
- isPlatformEngineer: false,
57
- isApiOwner: true,
58
- // allow guest as api owner in development
59
- isApiConsumer: true,
60
- groups: []
61
- };
25
+ const credentials = await httpAuth.credentials(req);
26
+ if (!credentials || !credentials.principal) {
27
+ throw new errors.NotAllowedError("authentication required");
62
28
  }
29
+ const info = await userInfo.getUserInfo(credentials);
30
+ const userId = info.userEntityRef.split("/")[1];
31
+ const groups = info.ownershipEntityRefs || [];
32
+ console.log(`user identity resolved: userId=${userId}, userEntityRef=${info.userEntityRef}, groups=${groups.join(",")}`);
33
+ return {
34
+ userId,
35
+ userEntityRef: info.userEntityRef,
36
+ groups
37
+ };
63
38
  }
64
39
  async function createRouter({
65
40
  httpAuth,
@@ -129,30 +104,42 @@ async function createRouter({
129
104
  }
130
105
  const { userId } = await getUserIdentity(req, httpAuth, userInfo);
131
106
  const apiProduct = req.body;
132
- const namespace = apiProduct.metadata?.namespace;
133
- const planPolicyRef = apiProduct.spec?.planPolicyRef;
134
- if (!namespace) {
135
- throw new errors.InputError("namespace is required in metadata");
136
- }
137
- if (!planPolicyRef?.name || !planPolicyRef?.namespace) {
138
- throw new errors.InputError("planPolicyRef with name and namespace is required");
139
- }
140
- const planPolicy = await k8sClient$1.getCustomResource(
141
- "extensions.kuadrant.io",
142
- "v1alpha1",
143
- planPolicyRef.namespace,
144
- "planpolicies",
145
- planPolicyRef.name
146
- );
147
- const plans = planPolicy.spec?.plans || [];
148
- if (plans.length === 0) {
149
- throw new errors.InputError("selected planpolicy has no plans defined");
107
+ const targetRef = apiProduct.spec?.targetRef;
108
+ if (!targetRef?.name || !targetRef?.kind || !targetRef?.namespace) {
109
+ throw new errors.InputError("targetRef with name, kind, and namespace is required");
150
110
  }
151
- apiProduct.spec.plans = plans;
111
+ const namespace = targetRef.namespace;
112
+ apiProduct.metadata.namespace = namespace;
152
113
  if (!apiProduct.spec.contact) {
153
114
  apiProduct.spec.contact = {};
154
115
  }
155
116
  apiProduct.spec.contact.team = `user:default/${userId}`;
117
+ const httpRouteNamespace = namespace;
118
+ const httpRouteName = targetRef.name;
119
+ try {
120
+ const planPoliciesResponse = await k8sClient$1.listCustomResources(
121
+ "extensions.kuadrant.io",
122
+ "v1alpha1",
123
+ "planpolicies",
124
+ httpRouteNamespace
125
+ );
126
+ const planPolicy = (planPoliciesResponse.items || []).find((pp) => {
127
+ const ref = pp.spec?.targetRef;
128
+ return ref?.kind === "HTTPRoute" && ref?.name === httpRouteName && (!ref?.namespace || ref?.namespace === httpRouteNamespace);
129
+ });
130
+ if (planPolicy && planPolicy.spec?.plans) {
131
+ apiProduct.spec.plans = planPolicy.spec.plans.map((plan) => ({
132
+ tier: plan.tier,
133
+ description: plan.description,
134
+ limits: plan.limits
135
+ }));
136
+ console.log(`copied ${apiProduct.spec.plans.length} plans from planpolicy ${planPolicy.metadata.name}`);
137
+ } else {
138
+ console.log(`no planpolicy found for httproute ${httpRouteNamespace}/${httpRouteName}`);
139
+ }
140
+ } catch (error) {
141
+ console.warn("failed to populate plans from planpolicy:", error);
142
+ }
156
143
  const created = await k8sClient$1.createCustomResource(
157
144
  "extensions.kuadrant.io",
158
145
  "v1alpha1",
@@ -160,6 +147,10 @@ async function createRouter({
160
147
  "apiproducts",
161
148
  apiProduct
162
149
  );
150
+ const provider = alpha.getAPIProductEntityProvider();
151
+ if (provider) {
152
+ await provider.refresh();
153
+ }
163
154
  res.status(201).json(created);
164
155
  } catch (error) {
165
156
  console.error("error creating apiproduct:", error);
@@ -191,6 +182,10 @@ async function createRouter({
191
182
  "apiproducts",
192
183
  name
193
184
  );
185
+ const provider = alpha.getAPIProductEntityProvider();
186
+ if (provider) {
187
+ await provider.refresh();
188
+ }
194
189
  res.status(204).send();
195
190
  } catch (error) {
196
191
  console.error("error deleting apiproduct:", error);
@@ -201,125 +196,133 @@ async function createRouter({
201
196
  }
202
197
  }
203
198
  });
204
- router.get("/planpolicies", async (req, res) => {
199
+ router.get("/httproutes", async (req, res) => {
205
200
  try {
206
201
  const credentials = await httpAuth.credentials(req);
207
202
  const decision = await permissions$1.authorize(
208
- [{ permission: permissions.kuadrantPlanPolicyListPermission }],
203
+ [{ permission: permissions.kuadrantApiProductListPermission }],
209
204
  { credentials }
210
205
  );
211
206
  if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
212
207
  throw new errors.NotAllowedError("unauthorised");
213
208
  }
214
- const data = await k8sClient$1.listCustomResources("extensions.kuadrant.io", "v1alpha1", "planpolicies");
215
- const filtered = {
216
- items: (data.items || []).map((policy) => ({
217
- metadata: {
218
- name: policy.metadata.name,
219
- namespace: policy.metadata.namespace
220
- }
221
- }))
222
- };
223
- res.json(filtered);
209
+ const data = await k8sClient$1.listCustomResources("gateway.networking.k8s.io", "v1", "httproutes");
210
+ res.json(data);
224
211
  } catch (error) {
225
- console.error("error fetching planpolicies:", error);
212
+ console.error("error fetching httproutes:", error);
226
213
  if (error instanceof errors.NotAllowedError) {
227
214
  res.status(403).json({ error: error.message });
228
215
  } else {
229
- res.status(500).json({ error: "failed to fetch planpolicies" });
216
+ res.status(500).json({ error: "failed to fetch httproutes" });
230
217
  }
231
218
  }
232
219
  });
233
- router.get("/planpolicies/:namespace/:name", async (req, res) => {
220
+ router.patch("/apiproducts/:namespace/:name", async (req, res) => {
221
+ const patchSchema = zod.z.object({
222
+ spec: zod.z.object({
223
+ displayName: zod.z.string().optional(),
224
+ description: zod.z.string().optional(),
225
+ version: zod.z.string().optional(),
226
+ publishStatus: zod.z.enum(["Draft", "Published"]).optional(),
227
+ approvalMode: zod.z.enum(["automatic", "manual"]).optional(),
228
+ tags: zod.z.array(zod.z.string()).optional(),
229
+ contact: zod.z.object({
230
+ email: zod.z.string().optional(),
231
+ team: zod.z.string().optional(),
232
+ slack: zod.z.string().optional()
233
+ }).partial().optional(),
234
+ documentation: zod.z.object({
235
+ docsURL: zod.z.string().optional(),
236
+ openAPISpec: zod.z.string().optional()
237
+ }).partial().optional()
238
+ }).partial()
239
+ });
240
+ const parsed = patchSchema.safeParse(req.body);
241
+ if (!parsed.success) {
242
+ return res.status(400).json({ error: "invalid patch: " + parsed.error.toString() });
243
+ }
234
244
  try {
235
245
  const credentials = await httpAuth.credentials(req);
246
+ if (!credentials || !credentials.principal) {
247
+ throw new errors.NotAllowedError("authentication required");
248
+ }
236
249
  const decision = await permissions$1.authorize(
237
- [{ permission: permissions.kuadrantPlanPolicyReadPermission }],
250
+ [{ permission: permissions.kuadrantApiProductUpdatePermission }],
238
251
  { credentials }
239
252
  );
240
253
  if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
241
254
  throw new errors.NotAllowedError("unauthorised");
242
255
  }
243
256
  const { namespace, name } = req.params;
244
- const data = await k8sClient$1.getCustomResource("extensions.kuadrant.io", "v1alpha1", namespace, "planpolicies", name);
245
- res.json(data);
257
+ const updated = await k8sClient$1.patchCustomResource(
258
+ "extensions.kuadrant.io",
259
+ "v1alpha1",
260
+ namespace,
261
+ "apiproducts",
262
+ name,
263
+ parsed.data
264
+ );
265
+ return res.json(updated);
246
266
  } catch (error) {
247
- console.error("error fetching planpolicy:", error);
267
+ console.error("error updating apiproduct:", error);
268
+ const errorMessage = error instanceof Error ? error.message : String(error);
248
269
  if (error instanceof errors.NotAllowedError) {
249
- res.status(403).json({ error: error.message });
270
+ return res.status(403).json({ error: error.message });
271
+ } else if (error instanceof errors.InputError) {
272
+ return res.status(400).json({ error: error.message });
250
273
  } else {
251
- res.status(500).json({ error: "failed to fetch planpolicy" });
274
+ return res.status(500).json({ error: errorMessage });
252
275
  }
253
276
  }
254
277
  });
255
- router.get("/apikeys", async (req, res) => {
278
+ router.get("/planpolicies", async (req, res) => {
256
279
  try {
257
280
  const credentials = await httpAuth.credentials(req);
258
- const userId = req.query.userId;
259
- const namespace = req.query.namespace;
260
- if (!namespace) {
261
- throw new errors.InputError("namespace query parameter is required");
262
- }
263
- const permission = userId ? permissions.kuadrantApiKeyReadOwnPermission : permissions.kuadrantApiKeyReadAllPermission;
264
281
  const decision = await permissions$1.authorize(
265
- [{ permission }],
282
+ [{ permission: permissions.kuadrantPlanPolicyListPermission }],
266
283
  { credentials }
267
284
  );
268
285
  if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
269
286
  throw new errors.NotAllowedError("unauthorised");
270
287
  }
271
- const data = await k8sClient$1.listSecrets(namespace);
272
- let filteredItems = data.items || [];
273
- if (userId) {
274
- filteredItems = filteredItems.filter(
275
- (secret) => secret.metadata?.annotations?.["secret.kuadrant.io/user-id"] === userId
276
- );
277
- }
278
- filteredItems = filteredItems.filter(
279
- (secret) => secret.metadata?.annotations?.["secret.kuadrant.io/user-id"]
280
- );
281
- res.json({ items: filteredItems });
288
+ const data = await k8sClient$1.listCustomResources("extensions.kuadrant.io", "v1alpha1", "planpolicies");
289
+ const filtered = {
290
+ items: (data.items || []).map((policy) => ({
291
+ metadata: {
292
+ name: policy.metadata.name,
293
+ namespace: policy.metadata.namespace
294
+ }
295
+ }))
296
+ };
297
+ res.json(filtered);
282
298
  } catch (error) {
283
- console.error("error fetching api keys:", error);
299
+ console.error("error fetching planpolicies:", error);
284
300
  if (error instanceof errors.NotAllowedError) {
285
301
  res.status(403).json({ error: error.message });
286
302
  } else {
287
- res.status(500).json({ error: "failed to fetch api keys" });
303
+ res.status(500).json({ error: "failed to fetch planpolicies" });
288
304
  }
289
305
  }
290
306
  });
291
- router.delete("/apikeys/:namespace/:name", async (req, res) => {
307
+ router.get("/planpolicies/:namespace/:name", async (req, res) => {
292
308
  try {
293
309
  const credentials = await httpAuth.credentials(req);
294
- const { userId } = await getUserIdentity(req, httpAuth, userInfo);
295
- const { namespace, name } = req.params;
296
- const secret = await k8sClient$1.getSecret(namespace, name);
297
- const secretUserId = secret.metadata?.annotations?.["secret.kuadrant.io/user-id"];
298
- const deleteAllDecision = await permissions$1.authorize(
299
- [{ permission: permissions.kuadrantApiKeyDeleteAllPermission }],
310
+ const decision = await permissions$1.authorize(
311
+ [{ permission: permissions.kuadrantPlanPolicyReadPermission }],
300
312
  { credentials }
301
313
  );
302
- const canDeleteAll = deleteAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
303
- if (!canDeleteAll) {
304
- const deleteOwnDecision = await permissions$1.authorize(
305
- [{ permission: permissions.kuadrantApiKeyDeleteOwnPermission }],
306
- { credentials }
307
- );
308
- if (deleteOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
309
- throw new errors.NotAllowedError("unauthorised");
310
- }
311
- if (secretUserId !== userId) {
312
- throw new errors.NotAllowedError("you can only delete your own api keys");
313
- }
314
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
315
+ throw new errors.NotAllowedError("unauthorised");
314
316
  }
315
- await k8sClient$1.deleteSecret(namespace, name);
316
- res.status(204).send();
317
+ const { namespace, name } = req.params;
318
+ const data = await k8sClient$1.getCustomResource("extensions.kuadrant.io", "v1alpha1", namespace, "planpolicies", name);
319
+ res.json(data);
317
320
  } catch (error) {
318
- console.error("error deleting api key:", error);
321
+ console.error("error fetching planpolicy:", error);
319
322
  if (error instanceof errors.NotAllowedError) {
320
323
  res.status(403).json({ error: error.message });
321
324
  } else {
322
- res.status(500).json({ error: "failed to delete api key" });
325
+ res.status(500).json({ error: "failed to fetch planpolicy" });
323
326
  }
324
327
  }
325
328
  });
@@ -328,9 +331,7 @@ async function createRouter({
328
331
  apiNamespace: zod.z.string(),
329
332
  planTier: zod.z.string(),
330
333
  useCase: zod.z.string().optional(),
331
- userId: zod.z.string(),
332
- userEmail: zod.z.string().optional(),
333
- namespace: zod.z.string()
334
+ userEmail: zod.z.string().optional()
334
335
  });
335
336
  router.post("/requests", async (req, res) => {
336
337
  const parsed = requestSchema.safeParse(req.body);
@@ -339,7 +340,8 @@ async function createRouter({
339
340
  }
340
341
  try {
341
342
  const credentials = await httpAuth.credentials(req);
342
- const { apiName, apiNamespace, planTier, useCase, userId, userEmail, namespace } = parsed.data;
343
+ const { apiName, apiNamespace, planTier, useCase, userEmail } = parsed.data;
344
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
343
345
  const resourceRef = `apiproduct:${apiNamespace}/${apiName}`;
344
346
  const decision = await permissions$1.authorize(
345
347
  [{
@@ -351,11 +353,6 @@ async function createRouter({
351
353
  if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
352
354
  throw new errors.NotAllowedError(`not authorised to request access to ${apiName}`);
353
355
  }
354
- const { userId: authenticatedUserId, isPlatformEngineer, isApiOwner } = await getUserIdentity(req, httpAuth, userInfo);
355
- const canCreateForOthers = isPlatformEngineer || isApiOwner;
356
- if (!canCreateForOthers && userId !== authenticatedUserId) {
357
- throw new errors.NotAllowedError("you can only create api key requests for yourself");
358
- }
359
356
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
360
357
  const randomSuffix = crypto.randomBytes(4).toString("hex");
361
358
  const requestName = `${userId}-${apiName}-${randomSuffix}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
@@ -368,7 +365,7 @@ async function createRouter({
368
365
  kind: "APIKeyRequest",
369
366
  metadata: {
370
367
  name: requestName,
371
- namespace
368
+ namespace: apiNamespace
372
369
  },
373
370
  spec: {
374
371
  apiName,
@@ -382,7 +379,7 @@ async function createRouter({
382
379
  const created = await k8sClient$1.createCustomResource(
383
380
  "extensions.kuadrant.io",
384
381
  "v1alpha1",
385
- namespace,
382
+ apiNamespace,
386
383
  "apikeyrequests",
387
384
  request
388
385
  );
@@ -452,7 +449,7 @@ async function createRouter({
452
449
  await k8sClient$1.patchCustomResourceStatus(
453
450
  "extensions.kuadrant.io",
454
451
  "v1alpha1",
455
- namespace,
452
+ apiNamespace,
456
453
  "apikeyrequests",
457
454
  requestName,
458
455
  status
@@ -516,11 +513,8 @@ async function createRouter({
516
513
  if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
517
514
  throw new errors.NotAllowedError("unauthorised");
518
515
  }
519
- const userId = req.query.userId;
516
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
520
517
  const namespace = req.query.namespace;
521
- if (!userId) {
522
- throw new errors.InputError("userId query parameter is required");
523
- }
524
518
  let data;
525
519
  if (namespace) {
526
520
  data = await k8sClient$1.listCustomResources("extensions.kuadrant.io", "v1alpha1", "apikeyrequests", namespace);
@@ -549,24 +543,14 @@ async function createRouter({
549
543
  throw new errors.InputError(parsed.error.toString());
550
544
  }
551
545
  try {
552
- const { userId, isApiOwner } = await getUserIdentity(req, httpAuth, userInfo);
553
- let canApprove = isApiOwner;
554
- if (!canApprove) {
555
- try {
556
- const credentials = await httpAuth.credentials(req, { allow: ["none"] });
557
- if (credentials) {
558
- const decision = await permissions$1.authorize(
559
- [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
560
- { credentials }
561
- );
562
- canApprove = decision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
563
- }
564
- } catch (error) {
565
- console.warn("permission check failed, using group-based authorization:", error);
566
- }
567
- }
568
- if (!canApprove) {
569
- throw new errors.NotAllowedError("you do not have permission to approve api key requests");
546
+ const credentials = await httpAuth.credentials(req);
547
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
548
+ const decision = await permissions$1.authorize(
549
+ [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
550
+ { credentials }
551
+ );
552
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
553
+ throw new errors.NotAllowedError("unauthorised");
570
554
  }
571
555
  const { namespace, name } = req.params;
572
556
  const { comment } = parsed.data;
@@ -684,24 +668,14 @@ async function createRouter({
684
668
  throw new errors.InputError(parsed.error.toString());
685
669
  }
686
670
  try {
687
- const { userId, isApiOwner } = await getUserIdentity(req, httpAuth, userInfo);
688
- let canReject = isApiOwner;
689
- if (!canReject) {
690
- try {
691
- const credentials = await httpAuth.credentials(req, { allow: ["none"] });
692
- if (credentials) {
693
- const decision = await permissions$1.authorize(
694
- [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
695
- { credentials }
696
- );
697
- canReject = decision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
698
- }
699
- } catch (error) {
700
- console.warn("permission check failed, using group-based authorization:", error);
701
- }
702
- }
703
- if (!canReject) {
704
- throw new errors.NotAllowedError("you do not have permission to reject api key requests");
671
+ const credentials = await httpAuth.credentials(req);
672
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
673
+ const decision = await permissions$1.authorize(
674
+ [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
675
+ { credentials }
676
+ );
677
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
678
+ throw new errors.NotAllowedError("unauthorised");
705
679
  }
706
680
  const { namespace, name } = req.params;
707
681
  const { comment } = parsed.data;
@@ -743,24 +717,14 @@ async function createRouter({
743
717
  throw new errors.InputError(parsed.error.toString());
744
718
  }
745
719
  try {
746
- const { userId, isApiOwner } = await getUserIdentity(req, httpAuth, userInfo);
747
- let canApprove = isApiOwner;
748
- if (!canApprove) {
749
- try {
750
- const credentials = await httpAuth.credentials(req, { allow: ["none"] });
751
- if (credentials) {
752
- const decision = await permissions$1.authorize(
753
- [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
754
- { credentials }
755
- );
756
- canApprove = decision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
757
- }
758
- } catch (error) {
759
- console.warn("permission check failed, using group-based authorization:", error);
760
- }
761
- }
762
- if (!canApprove) {
763
- throw new errors.NotAllowedError("you do not have permission to approve api key requests");
720
+ const credentials = await httpAuth.credentials(req);
721
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
722
+ const decision = await permissions$1.authorize(
723
+ [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
724
+ { credentials }
725
+ );
726
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
727
+ throw new errors.NotAllowedError("unauthorised");
764
728
  }
765
729
  const { requests, comment } = parsed.data;
766
730
  const reviewedBy = `user:default/${userId}`;
@@ -891,24 +855,14 @@ async function createRouter({
891
855
  throw new errors.InputError(parsed.error.toString());
892
856
  }
893
857
  try {
894
- const { userId, isApiOwner } = await getUserIdentity(req, httpAuth, userInfo);
895
- let canReject = isApiOwner;
896
- if (!canReject) {
897
- try {
898
- const credentials = await httpAuth.credentials(req, { allow: ["none"] });
899
- if (credentials) {
900
- const decision = await permissions$1.authorize(
901
- [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
902
- { credentials }
903
- );
904
- canReject = decision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
905
- }
906
- } catch (error) {
907
- console.warn("permission check failed, using group-based authorization:", error);
908
- }
909
- }
910
- if (!canReject) {
911
- throw new errors.NotAllowedError("you do not have permission to reject api key requests");
858
+ const credentials = await httpAuth.credentials(req);
859
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
860
+ const decision = await permissions$1.authorize(
861
+ [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
862
+ { credentials }
863
+ );
864
+ if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
865
+ throw new errors.NotAllowedError("unauthorised");
912
866
  }
913
867
  const { requests, comment } = parsed.data;
914
868
  const reviewedBy = `user:default/${userId}`;
@@ -952,7 +906,8 @@ async function createRouter({
952
906
  });
953
907
  router.delete("/requests/:namespace/:name", async (req, res) => {
954
908
  try {
955
- const { userId, isPlatformEngineer, isApiOwner } = await getUserIdentity(req, httpAuth, userInfo);
909
+ const credentials = await httpAuth.credentials(req);
910
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
956
911
  const { namespace, name } = req.params;
957
912
  const request = await k8sClient$1.getCustomResource(
958
913
  "extensions.kuadrant.io",
@@ -962,9 +917,22 @@ async function createRouter({
962
917
  name
963
918
  );
964
919
  const requestUserId = request.spec?.requestedBy?.userId;
965
- const canDeleteAll = isPlatformEngineer || isApiOwner;
966
- if (!canDeleteAll && requestUserId !== userId) {
967
- throw new errors.NotAllowedError("you can only delete your own api key requests");
920
+ const deleteAllDecision = await permissions$1.authorize(
921
+ [{ permission: permissions.kuadrantApiKeyRequestDeleteAllPermission }],
922
+ { credentials }
923
+ );
924
+ const canDeleteAll = deleteAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW;
925
+ if (!canDeleteAll) {
926
+ const deleteOwnDecision = await permissions$1.authorize(
927
+ [{ permission: permissions.kuadrantApiKeyRequestDeleteOwnPermission }],
928
+ { credentials }
929
+ );
930
+ if (deleteOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
931
+ throw new errors.NotAllowedError("unauthorised");
932
+ }
933
+ if (requestUserId !== userId) {
934
+ throw new errors.NotAllowedError("you can only delete your own api key requests");
935
+ }
968
936
  }
969
937
  if (request.status?.phase === "Approved") {
970
938
  try {
@@ -1001,30 +969,57 @@ async function createRouter({
1001
969
  }
1002
970
  });
1003
971
  router.patch("/requests/:namespace/:name", async (req, res) => {
972
+ const patchSchema = zod.z.object({
973
+ spec: zod.z.object({
974
+ useCase: zod.z.string().optional()
975
+ }).partial()
976
+ });
977
+ const parsed = patchSchema.safeParse(req.body);
978
+ if (!parsed.success) {
979
+ throw new errors.InputError("invalid patch: " + parsed.error.toString());
980
+ }
1004
981
  try {
1005
982
  const credentials = await httpAuth.credentials(req);
1006
- const decision = await permissions$1.authorize(
983
+ const { userId } = await getUserIdentity(req, httpAuth, userInfo);
984
+ const { namespace, name } = req.params;
985
+ const existing = await k8sClient$1.getCustomResource(
986
+ "extensions.kuadrant.io",
987
+ "v1alpha1",
988
+ namespace,
989
+ "apikeyrequests",
990
+ name
991
+ );
992
+ const updateAllDecision = await permissions$1.authorize(
1007
993
  [{ permission: permissions.kuadrantApiKeyRequestUpdatePermission }],
1008
994
  { credentials }
1009
995
  );
1010
- if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
1011
- throw new errors.NotAllowedError("unauthorised");
996
+ if (updateAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
997
+ const updateOwnDecision = await permissions$1.authorize(
998
+ [{ permission: permissions.kuadrantApiKeyRequestUpdateOwnPermission }],
999
+ { credentials }
1000
+ );
1001
+ if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
1002
+ throw new errors.NotAllowedError("unauthorised");
1003
+ }
1004
+ if (existing.spec?.requestedBy?.userId !== userId) {
1005
+ throw new errors.NotAllowedError("you can only update your own api key requests");
1006
+ }
1012
1007
  }
1013
- const { namespace, name } = req.params;
1014
- const patch = req.body;
1015
1008
  const updated = await k8sClient$1.patchCustomResource(
1016
1009
  "extensions.kuadrant.io",
1017
1010
  "v1alpha1",
1018
1011
  namespace,
1019
1012
  "apikeyrequests",
1020
1013
  name,
1021
- patch
1014
+ parsed.data
1022
1015
  );
1023
1016
  res.json(updated);
1024
1017
  } catch (error) {
1025
1018
  console.error("error updating api key request:", error);
1026
1019
  if (error instanceof errors.NotAllowedError) {
1027
1020
  res.status(403).json({ error: error.message });
1021
+ } else if (error instanceof errors.InputError) {
1022
+ res.status(400).json({ error: error.message });
1028
1023
  } else {
1029
1024
  res.status(500).json({ error: "failed to update api key request" });
1030
1025
  }