@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.
- package/dist/alpha.cjs.js +6 -0
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/alpha.d.ts +27 -0
- package/dist/index.cjs.js +3 -0
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +50 -0
- package/dist/k8s-client.cjs.js +9 -1
- package/dist/k8s-client.cjs.js.map +1 -1
- package/dist/permissions.cjs.js +18 -0
- package/dist/permissions.cjs.js.map +1 -1
- package/dist/providers/APIProductEntityProvider.cjs.js.map +1 -1
- package/dist/rbac.d.ts +11 -0
- package/dist/router.cjs.js +215 -220
- package/dist/router.cjs.js.map +1 -1
- package/package.json +41 -5
package/dist/router.cjs.js
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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("/
|
|
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.
|
|
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("
|
|
215
|
-
|
|
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
|
|
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
|
|
216
|
+
res.status(500).json({ error: "failed to fetch httproutes" });
|
|
230
217
|
}
|
|
231
218
|
}
|
|
232
219
|
});
|
|
233
|
-
router.
|
|
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.
|
|
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
|
|
245
|
-
|
|
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
|
|
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:
|
|
274
|
+
return res.status(500).json({ error: errorMessage });
|
|
252
275
|
}
|
|
253
276
|
}
|
|
254
277
|
});
|
|
255
|
-
router.get("/
|
|
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.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
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
|
|
303
|
+
res.status(500).json({ error: "failed to fetch planpolicies" });
|
|
288
304
|
}
|
|
289
305
|
}
|
|
290
306
|
});
|
|
291
|
-
router.
|
|
307
|
+
router.get("/planpolicies/:namespace/:name", async (req, res) => {
|
|
292
308
|
try {
|
|
293
309
|
const credentials = await httpAuth.credentials(req);
|
|
294
|
-
const
|
|
295
|
-
|
|
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
|
-
|
|
303
|
-
|
|
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
|
-
|
|
316
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
|
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
|
|
966
|
-
|
|
967
|
-
|
|
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
|
|
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 (
|
|
1011
|
-
|
|
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
|
-
|
|
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
|
}
|