@acarmisc/backstage-plugin-litellm-backend 0.17.7 → 0.18.0
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/config.d.ts +28 -0
- package/dist/index.cjs.js +162 -75
- package/dist/index.cjs.js.map +3 -3
- package/dist/router.js +56 -18
- package/dist/routes/config.js +2 -0
- package/dist/routes/context.d.ts +4 -1
- package/dist/routes/teams.js +43 -4
- package/dist/teamAdmin.d.ts +24 -0
- package/dist/teamAdmin.js +3 -0
- package/package.json +1 -1
package/config.d.ts
CHANGED
|
@@ -324,6 +324,34 @@ export interface Config {
|
|
|
324
324
|
*/
|
|
325
325
|
allowTeamDelete?: boolean;
|
|
326
326
|
|
|
327
|
+
/**
|
|
328
|
+
* When true, all team write operations return 403 even for admins:
|
|
329
|
+
* POST /teams, PATCH /teams/:id, DELETE /teams/:id,
|
|
330
|
+
* POST/DELETE /teams/:id/members, and PUT objectPermissions routes.
|
|
331
|
+
* Reads (GET /teams, GET /teams/managed) keep working.
|
|
332
|
+
* Error message: 'Team management is read-only (litellm.teamAdmin.readOnly): teams are managed outside Backstage'.
|
|
333
|
+
* @default false
|
|
334
|
+
*/
|
|
335
|
+
readOnly?: boolean;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Backstage group refs that must include the caller for team creation.
|
|
339
|
+
* When non-empty, POST /teams additionally requires the caller to be
|
|
340
|
+
* a member of at least one of these groups, narrowing the permission.
|
|
341
|
+
* Error message: 'Access denied: team creation is limited to <comma-joined groups>'.
|
|
342
|
+
* @default []
|
|
343
|
+
*/
|
|
344
|
+
createGroups?: string[];
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* LiteLLM team roles whose holders may add/remove members of their own team.
|
|
348
|
+
* When non-empty, callers with one of these roles in members_with_roles
|
|
349
|
+
* can POST/DELETE /teams/:teamId/members without owning the team's group.
|
|
350
|
+
* Allows non-admins to manage their team's membership without full admin access.
|
|
351
|
+
* @default []
|
|
352
|
+
*/
|
|
353
|
+
memberManagerRoles?: string[];
|
|
354
|
+
|
|
327
355
|
/**
|
|
328
356
|
* Object-permission management (attaching knowledge bases / MCP servers
|
|
329
357
|
* to a team). This is the highest-risk surface — attaching a vector store
|
package/dist/index.cjs.js
CHANGED
|
@@ -568,9 +568,9 @@ var require_index_cjs = __commonJS({
|
|
|
568
568
|
}
|
|
569
569
|
});
|
|
570
570
|
|
|
571
|
-
// ../../node_modules/
|
|
571
|
+
// ../../node_modules/webidl-conversions/lib/index.js
|
|
572
572
|
var require_lib = __commonJS({
|
|
573
|
-
"../../node_modules/
|
|
573
|
+
"../../node_modules/webidl-conversions/lib/index.js"(exports2, module2) {
|
|
574
574
|
"use strict";
|
|
575
575
|
var conversions = {};
|
|
576
576
|
module2.exports = conversions;
|
|
@@ -721,9 +721,9 @@ var require_lib = __commonJS({
|
|
|
721
721
|
}
|
|
722
722
|
});
|
|
723
723
|
|
|
724
|
-
// ../../node_modules/
|
|
724
|
+
// ../../node_modules/whatwg-url/lib/utils.js
|
|
725
725
|
var require_utils = __commonJS({
|
|
726
|
-
"../../node_modules/
|
|
726
|
+
"../../node_modules/whatwg-url/lib/utils.js"(exports2, module2) {
|
|
727
727
|
"use strict";
|
|
728
728
|
module2.exports.mixin = function mixin(target, source) {
|
|
729
729
|
const keys = Object.getOwnPropertyNames(source);
|
|
@@ -910,9 +910,9 @@ var require_tr46 = __commonJS({
|
|
|
910
910
|
}
|
|
911
911
|
});
|
|
912
912
|
|
|
913
|
-
// ../../node_modules/
|
|
913
|
+
// ../../node_modules/whatwg-url/lib/url-state-machine.js
|
|
914
914
|
var require_url_state_machine = __commonJS({
|
|
915
|
-
"../../node_modules/
|
|
915
|
+
"../../node_modules/whatwg-url/lib/url-state-machine.js"(exports2, module2) {
|
|
916
916
|
"use strict";
|
|
917
917
|
var punycode = require("punycode");
|
|
918
918
|
var tr46 = require_tr46();
|
|
@@ -1982,9 +1982,9 @@ var require_url_state_machine = __commonJS({
|
|
|
1982
1982
|
}
|
|
1983
1983
|
});
|
|
1984
1984
|
|
|
1985
|
-
// ../../node_modules/
|
|
1985
|
+
// ../../node_modules/whatwg-url/lib/URL-impl.js
|
|
1986
1986
|
var require_URL_impl = __commonJS({
|
|
1987
|
-
"../../node_modules/
|
|
1987
|
+
"../../node_modules/whatwg-url/lib/URL-impl.js"(exports2) {
|
|
1988
1988
|
"use strict";
|
|
1989
1989
|
var usm = require_url_state_machine();
|
|
1990
1990
|
exports2.implementation = class URLImpl {
|
|
@@ -2139,9 +2139,9 @@ var require_URL_impl = __commonJS({
|
|
|
2139
2139
|
}
|
|
2140
2140
|
});
|
|
2141
2141
|
|
|
2142
|
-
// ../../node_modules/
|
|
2142
|
+
// ../../node_modules/whatwg-url/lib/URL.js
|
|
2143
2143
|
var require_URL = __commonJS({
|
|
2144
|
-
"../../node_modules/
|
|
2144
|
+
"../../node_modules/whatwg-url/lib/URL.js"(exports2, module2) {
|
|
2145
2145
|
"use strict";
|
|
2146
2146
|
var conversions = require_lib();
|
|
2147
2147
|
var utils = require_utils();
|
|
@@ -2321,9 +2321,9 @@ var require_URL = __commonJS({
|
|
|
2321
2321
|
}
|
|
2322
2322
|
});
|
|
2323
2323
|
|
|
2324
|
-
// ../../node_modules/
|
|
2324
|
+
// ../../node_modules/whatwg-url/lib/public-api.js
|
|
2325
2325
|
var require_public_api = __commonJS({
|
|
2326
|
-
"../../node_modules/
|
|
2326
|
+
"../../node_modules/whatwg-url/lib/public-api.js"(exports2) {
|
|
2327
2327
|
"use strict";
|
|
2328
2328
|
exports2.URL = require_URL().interface;
|
|
2329
2329
|
exports2.serializeURL = require_url_state_machine().serializeURL;
|
|
@@ -9050,7 +9050,10 @@ function readTeamAdminConfig(config) {
|
|
|
9050
9050
|
allowedMcpAccessGroups: config.getOptionalStringArray(
|
|
9051
9051
|
"litellm.teamAdmin.allowedMcpAccessGroups"
|
|
9052
9052
|
) ?? [],
|
|
9053
|
-
allowTeamDelete: config.getOptionalBoolean("litellm.teamAdmin.allowTeamDelete") ?? false
|
|
9053
|
+
allowTeamDelete: config.getOptionalBoolean("litellm.teamAdmin.allowTeamDelete") ?? false,
|
|
9054
|
+
readOnly: config.getOptionalBoolean("litellm.teamAdmin.readOnly") ?? false,
|
|
9055
|
+
createGroups: config.getOptionalStringArray("litellm.teamAdmin.createGroups") ?? [],
|
|
9056
|
+
memberManagerRoles: config.getOptionalStringArray("litellm.teamAdmin.memberManagerRoles") ?? []
|
|
9054
9057
|
};
|
|
9055
9058
|
}
|
|
9056
9059
|
function validateTeamWriteInput(input, cfg) {
|
|
@@ -9795,7 +9798,9 @@ function registerConfigRoutes(router, ctx) {
|
|
|
9795
9798
|
enabled: ctx.teamMgmtEnabled,
|
|
9796
9799
|
maxBudgetCeiling: ctx.teamAdminCfg.maxBudgetCeiling,
|
|
9797
9800
|
allowUnlimitedBudget: ctx.teamAdminCfg.allowUnlimitedBudget,
|
|
9798
|
-
objectPermissionsEnabled: ctx.objectPermsEnabled
|
|
9801
|
+
objectPermissionsEnabled: ctx.objectPermsEnabled,
|
|
9802
|
+
readOnly: ctx.teamAdminCfg.readOnly,
|
|
9803
|
+
memberManagerRoles: ctx.teamAdminCfg.memberManagerRoles
|
|
9799
9804
|
},
|
|
9800
9805
|
display: {
|
|
9801
9806
|
hideTeamBudgetForMembers: ctx.teamBudgetVisibility.hideTeamBudgetForMembers,
|
|
@@ -10748,6 +10753,12 @@ function registerTeamsRoutes(router, ctx) {
|
|
|
10748
10753
|
res.status(500).json({ error: "Team management is misconfigured (group is missing)" });
|
|
10749
10754
|
return;
|
|
10750
10755
|
}
|
|
10756
|
+
if (teamAdminCfg.readOnly) {
|
|
10757
|
+
res.status(403).json({
|
|
10758
|
+
error: "Team management is read-only (litellm.teamAdmin.readOnly): teams are managed outside Backstage"
|
|
10759
|
+
});
|
|
10760
|
+
return;
|
|
10761
|
+
}
|
|
10751
10762
|
const check = await assertTeamAdmin({
|
|
10752
10763
|
req,
|
|
10753
10764
|
auth,
|
|
@@ -10761,6 +10772,19 @@ function registerTeamsRoutes(router, ctx) {
|
|
|
10761
10772
|
res.status(check.status).json({ error: check.error });
|
|
10762
10773
|
return;
|
|
10763
10774
|
}
|
|
10775
|
+
if (teamAdminCfg.createGroups.length > 0) {
|
|
10776
|
+
const memberships = await Promise.all(
|
|
10777
|
+
teamAdminCfg.createGroups.map(
|
|
10778
|
+
(group) => isUserMemberOfGroup(check.userEntityRef, group, catalogClient, auth, logger)
|
|
10779
|
+
)
|
|
10780
|
+
);
|
|
10781
|
+
if (!memberships.some(Boolean)) {
|
|
10782
|
+
res.status(403).json({
|
|
10783
|
+
error: `Access denied: team creation is limited to ${teamAdminCfg.createGroups.join(", ")}`
|
|
10784
|
+
});
|
|
10785
|
+
return;
|
|
10786
|
+
}
|
|
10787
|
+
}
|
|
10764
10788
|
const v = validateTeamWriteInput(req.body ?? {}, teamAdminCfg);
|
|
10765
10789
|
if (!v.ok) {
|
|
10766
10790
|
res.status(400).json({ error: v.error });
|
|
@@ -10923,9 +10947,14 @@ function registerTeamsRoutes(router, ctx) {
|
|
|
10923
10947
|
router.post(
|
|
10924
10948
|
"/teams/:teamId/members",
|
|
10925
10949
|
async (req, res) => {
|
|
10926
|
-
const authz = await authorizeTeamSubresource(
|
|
10950
|
+
const authz = await authorizeTeamSubresource(
|
|
10951
|
+
req,
|
|
10952
|
+
res,
|
|
10953
|
+
import_backstage_plugin_litellm_common5.litellmTeamMembersManagePermission,
|
|
10954
|
+
{ allowTeamRole: true }
|
|
10955
|
+
);
|
|
10927
10956
|
if (!authz) return;
|
|
10928
|
-
const { teamId, owningGroup, actor } = authz;
|
|
10957
|
+
const { teamId, owningGroup, actor, via } = authz;
|
|
10929
10958
|
const body = req.body ?? {};
|
|
10930
10959
|
const userEntityRef = (body.userEntityRef ?? "").trim();
|
|
10931
10960
|
if (!userEntityRef) {
|
|
@@ -10945,6 +10974,12 @@ function registerTeamsRoutes(router, ctx) {
|
|
|
10945
10974
|
return;
|
|
10946
10975
|
}
|
|
10947
10976
|
maxBudgetInTeam = body.maxBudgetInTeam;
|
|
10977
|
+
if (via === "teamRole") {
|
|
10978
|
+
res.status(403).json({
|
|
10979
|
+
error: "Team member managers cannot set member budgets"
|
|
10980
|
+
});
|
|
10981
|
+
return;
|
|
10982
|
+
}
|
|
10948
10983
|
}
|
|
10949
10984
|
try {
|
|
10950
10985
|
const { token } = await auth.getPluginRequestToken({
|
|
@@ -11003,7 +11038,8 @@ function registerTeamsRoutes(router, ctx) {
|
|
|
11003
11038
|
actor,
|
|
11004
11039
|
teamId,
|
|
11005
11040
|
member: litellmUserId,
|
|
11006
|
-
owningGroup
|
|
11041
|
+
owningGroup,
|
|
11042
|
+
via
|
|
11007
11043
|
});
|
|
11008
11044
|
const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
|
|
11009
11045
|
res.json(
|
|
@@ -11017,15 +11053,35 @@ function registerTeamsRoutes(router, ctx) {
|
|
|
11017
11053
|
router.delete(
|
|
11018
11054
|
"/teams/:teamId/members",
|
|
11019
11055
|
async (req, res) => {
|
|
11020
|
-
const authz = await authorizeTeamSubresource(
|
|
11056
|
+
const authz = await authorizeTeamSubresource(
|
|
11057
|
+
req,
|
|
11058
|
+
res,
|
|
11059
|
+
import_backstage_plugin_litellm_common5.litellmTeamMembersManagePermission,
|
|
11060
|
+
{ allowTeamRole: true }
|
|
11061
|
+
);
|
|
11021
11062
|
if (!authz) return;
|
|
11022
|
-
const { teamId, owningGroup, actor } = authz;
|
|
11063
|
+
const { teamId, owningGroup, actor, team, via } = authz;
|
|
11023
11064
|
const userEntityRef = String(req.query.userEntityRef ?? "").trim();
|
|
11024
11065
|
if (!userEntityRef) {
|
|
11025
11066
|
res.status(400).json({ error: "userEntityRef query parameter is required" });
|
|
11026
11067
|
return;
|
|
11027
11068
|
}
|
|
11028
11069
|
const litellmUserId = toLiteLLMUserId(userEntityRef, userIdDomain);
|
|
11070
|
+
if (via === "teamRole") {
|
|
11071
|
+
if (litellmUserId === toLiteLLMUserId(actor, userIdDomain)) {
|
|
11072
|
+
res.status(400).json({ error: "You cannot remove yourself from the team" });
|
|
11073
|
+
return;
|
|
11074
|
+
}
|
|
11075
|
+
const role = team.members_with_roles?.find(
|
|
11076
|
+
(m) => m.user_id === litellmUserId
|
|
11077
|
+
)?.role;
|
|
11078
|
+
if (role && teamAdminCfg.memberManagerRoles.includes(role)) {
|
|
11079
|
+
res.status(403).json({
|
|
11080
|
+
error: `Only platform team admins can remove a team ${role}`
|
|
11081
|
+
});
|
|
11082
|
+
return;
|
|
11083
|
+
}
|
|
11084
|
+
}
|
|
11029
11085
|
try {
|
|
11030
11086
|
await client.teamMemberDelete({
|
|
11031
11087
|
team_id: teamId,
|
|
@@ -11035,7 +11091,8 @@ function registerTeamsRoutes(router, ctx) {
|
|
|
11035
11091
|
actor,
|
|
11036
11092
|
teamId,
|
|
11037
11093
|
member: litellmUserId,
|
|
11038
|
-
owningGroup
|
|
11094
|
+
owningGroup,
|
|
11095
|
+
via
|
|
11039
11096
|
});
|
|
11040
11097
|
const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
|
|
11041
11098
|
res.json(
|
|
@@ -11314,10 +11371,10 @@ function registerTeamUsageRoutes(router, ctx) {
|
|
|
11314
11371
|
});
|
|
11315
11372
|
}
|
|
11316
11373
|
|
|
11317
|
-
//
|
|
11374
|
+
// node_modules/jose/dist/node/esm/runtime/base64url.js
|
|
11318
11375
|
var import_node_buffer = require("node:buffer");
|
|
11319
11376
|
|
|
11320
|
-
//
|
|
11377
|
+
// node_modules/jose/dist/node/esm/lib/buffer_utils.js
|
|
11321
11378
|
var encoder = new TextEncoder();
|
|
11322
11379
|
var decoder = new TextDecoder();
|
|
11323
11380
|
var MAX_INT32 = 2 ** 32;
|
|
@@ -11332,7 +11389,7 @@ function concat(...buffers) {
|
|
|
11332
11389
|
return buf;
|
|
11333
11390
|
}
|
|
11334
11391
|
|
|
11335
|
-
//
|
|
11392
|
+
// node_modules/jose/dist/node/esm/runtime/base64url.js
|
|
11336
11393
|
function normalize(input) {
|
|
11337
11394
|
let encoded = input;
|
|
11338
11395
|
if (encoded instanceof Uint8Array) {
|
|
@@ -11342,7 +11399,7 @@ function normalize(input) {
|
|
|
11342
11399
|
}
|
|
11343
11400
|
var decode = (input) => new Uint8Array(import_node_buffer.Buffer.from(normalize(input), "base64url"));
|
|
11344
11401
|
|
|
11345
|
-
//
|
|
11402
|
+
// node_modules/jose/dist/node/esm/util/errors.js
|
|
11346
11403
|
var JOSEError = class extends Error {
|
|
11347
11404
|
static code = "ERR_JOSE_GENERIC";
|
|
11348
11405
|
code = "ERR_JOSE_GENERIC";
|
|
@@ -11428,18 +11485,18 @@ var JWSSignatureVerificationFailed = class extends JOSEError {
|
|
|
11428
11485
|
}
|
|
11429
11486
|
};
|
|
11430
11487
|
|
|
11431
|
-
//
|
|
11488
|
+
// node_modules/jose/dist/node/esm/runtime/is_key_object.js
|
|
11432
11489
|
var util = __toESM(require("node:util"), 1);
|
|
11433
11490
|
var is_key_object_default = (obj) => util.types.isKeyObject(obj);
|
|
11434
11491
|
|
|
11435
|
-
//
|
|
11492
|
+
// node_modules/jose/dist/node/esm/runtime/webcrypto.js
|
|
11436
11493
|
var crypto = __toESM(require("node:crypto"), 1);
|
|
11437
11494
|
var util2 = __toESM(require("node:util"), 1);
|
|
11438
11495
|
var webcrypto2 = crypto.webcrypto;
|
|
11439
11496
|
var webcrypto_default = webcrypto2;
|
|
11440
11497
|
var isCryptoKey = (key) => util2.types.isCryptoKey(key);
|
|
11441
11498
|
|
|
11442
|
-
//
|
|
11499
|
+
// node_modules/jose/dist/node/esm/lib/crypto_key.js
|
|
11443
11500
|
function unusable(name, prop = "algorithm.name") {
|
|
11444
11501
|
return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
11445
11502
|
}
|
|
@@ -11538,7 +11595,7 @@ function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
11538
11595
|
checkUsage(key, usages);
|
|
11539
11596
|
}
|
|
11540
11597
|
|
|
11541
|
-
//
|
|
11598
|
+
// node_modules/jose/dist/node/esm/lib/invalid_key_input.js
|
|
11542
11599
|
function message(msg, actual, ...types4) {
|
|
11543
11600
|
types4 = types4.filter(Boolean);
|
|
11544
11601
|
if (types4.length > 2) {
|
|
@@ -11567,14 +11624,14 @@ function withAlg(alg, actual, ...types4) {
|
|
|
11567
11624
|
return message(`Key for the ${alg} algorithm must be `, actual, ...types4);
|
|
11568
11625
|
}
|
|
11569
11626
|
|
|
11570
|
-
//
|
|
11627
|
+
// node_modules/jose/dist/node/esm/runtime/is_key_like.js
|
|
11571
11628
|
var is_key_like_default = (key) => is_key_object_default(key) || isCryptoKey(key);
|
|
11572
11629
|
var types3 = ["KeyObject"];
|
|
11573
11630
|
if (globalThis.CryptoKey || webcrypto_default?.CryptoKey) {
|
|
11574
11631
|
types3.push("CryptoKey");
|
|
11575
11632
|
}
|
|
11576
11633
|
|
|
11577
|
-
//
|
|
11634
|
+
// node_modules/jose/dist/node/esm/lib/is_disjoint.js
|
|
11578
11635
|
var isDisjoint = (...headers) => {
|
|
11579
11636
|
const sources = headers.filter(Boolean);
|
|
11580
11637
|
if (sources.length === 0 || sources.length === 1) {
|
|
@@ -11598,7 +11655,7 @@ var isDisjoint = (...headers) => {
|
|
|
11598
11655
|
};
|
|
11599
11656
|
var is_disjoint_default = isDisjoint;
|
|
11600
11657
|
|
|
11601
|
-
//
|
|
11658
|
+
// node_modules/jose/dist/node/esm/lib/is_object.js
|
|
11602
11659
|
function isObjectLike(value) {
|
|
11603
11660
|
return typeof value === "object" && value !== null;
|
|
11604
11661
|
}
|
|
@@ -11616,10 +11673,10 @@ function isObject(input) {
|
|
|
11616
11673
|
return Object.getPrototypeOf(input) === proto;
|
|
11617
11674
|
}
|
|
11618
11675
|
|
|
11619
|
-
//
|
|
11676
|
+
// node_modules/jose/dist/node/esm/runtime/get_named_curve.js
|
|
11620
11677
|
var import_node_crypto = require("node:crypto");
|
|
11621
11678
|
|
|
11622
|
-
//
|
|
11679
|
+
// node_modules/jose/dist/node/esm/lib/is_jwk.js
|
|
11623
11680
|
function isJWK(key) {
|
|
11624
11681
|
return isObject(key) && typeof key.kty === "string";
|
|
11625
11682
|
}
|
|
@@ -11633,7 +11690,7 @@ function isSecretJWK(key) {
|
|
|
11633
11690
|
return isJWK(key) && key.kty === "oct" && typeof key.k === "string";
|
|
11634
11691
|
}
|
|
11635
11692
|
|
|
11636
|
-
//
|
|
11693
|
+
// node_modules/jose/dist/node/esm/runtime/get_named_curve.js
|
|
11637
11694
|
var namedCurveToJOSE = (namedCurve) => {
|
|
11638
11695
|
switch (namedCurve) {
|
|
11639
11696
|
case "prime256v1":
|
|
@@ -11682,7 +11739,7 @@ var getNamedCurve2 = (kee, raw) => {
|
|
|
11682
11739
|
};
|
|
11683
11740
|
var get_named_curve_default = getNamedCurve2;
|
|
11684
11741
|
|
|
11685
|
-
//
|
|
11742
|
+
// node_modules/jose/dist/node/esm/runtime/check_key_length.js
|
|
11686
11743
|
var import_node_crypto2 = require("node:crypto");
|
|
11687
11744
|
var check_key_length_default = (key, alg) => {
|
|
11688
11745
|
let modulusLength;
|
|
@@ -11699,7 +11756,7 @@ var check_key_length_default = (key, alg) => {
|
|
|
11699
11756
|
}
|
|
11700
11757
|
};
|
|
11701
11758
|
|
|
11702
|
-
//
|
|
11759
|
+
// node_modules/jose/dist/node/esm/runtime/jwk_to_key.js
|
|
11703
11760
|
var import_node_crypto3 = require("node:crypto");
|
|
11704
11761
|
var parse = (key) => {
|
|
11705
11762
|
if (key.d) {
|
|
@@ -11709,7 +11766,7 @@ var parse = (key) => {
|
|
|
11709
11766
|
};
|
|
11710
11767
|
var jwk_to_key_default = parse;
|
|
11711
11768
|
|
|
11712
|
-
//
|
|
11769
|
+
// node_modules/jose/dist/node/esm/key/import.js
|
|
11713
11770
|
async function importJWK(jwk, alg) {
|
|
11714
11771
|
if (!isObject(jwk)) {
|
|
11715
11772
|
throw new TypeError("JWK must be an object");
|
|
@@ -11733,7 +11790,7 @@ async function importJWK(jwk, alg) {
|
|
|
11733
11790
|
}
|
|
11734
11791
|
}
|
|
11735
11792
|
|
|
11736
|
-
//
|
|
11793
|
+
// node_modules/jose/dist/node/esm/lib/check_key_type.js
|
|
11737
11794
|
var tag = (key) => key?.[Symbol.toStringTag];
|
|
11738
11795
|
var jwkMatchesOp = (alg, key, usage) => {
|
|
11739
11796
|
if (key.use !== void 0 && key.use !== "sig") {
|
|
@@ -11805,7 +11862,7 @@ function checkKeyType(allowJwk, alg, key, usage) {
|
|
|
11805
11862
|
var check_key_type_default = checkKeyType.bind(void 0, false);
|
|
11806
11863
|
var checkKeyTypeWithJwk = checkKeyType.bind(void 0, true);
|
|
11807
11864
|
|
|
11808
|
-
//
|
|
11865
|
+
// node_modules/jose/dist/node/esm/lib/validate_crit.js
|
|
11809
11866
|
function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) {
|
|
11810
11867
|
if (joseHeader.crit !== void 0 && protectedHeader?.crit === void 0) {
|
|
11811
11868
|
throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected');
|
|
@@ -11837,7 +11894,7 @@ function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader,
|
|
|
11837
11894
|
}
|
|
11838
11895
|
var validate_crit_default = validateCrit;
|
|
11839
11896
|
|
|
11840
|
-
//
|
|
11897
|
+
// node_modules/jose/dist/node/esm/lib/validate_algorithms.js
|
|
11841
11898
|
var validateAlgorithms = (option, algorithms) => {
|
|
11842
11899
|
if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== "string"))) {
|
|
11843
11900
|
throw new TypeError(`"${option}" option must be an array of strings`);
|
|
@@ -11849,11 +11906,11 @@ var validateAlgorithms = (option, algorithms) => {
|
|
|
11849
11906
|
};
|
|
11850
11907
|
var validate_algorithms_default = validateAlgorithms;
|
|
11851
11908
|
|
|
11852
|
-
//
|
|
11909
|
+
// node_modules/jose/dist/node/esm/runtime/verify.js
|
|
11853
11910
|
var crypto3 = __toESM(require("node:crypto"), 1);
|
|
11854
11911
|
var import_node_util2 = require("node:util");
|
|
11855
11912
|
|
|
11856
|
-
//
|
|
11913
|
+
// node_modules/jose/dist/node/esm/runtime/dsa_digest.js
|
|
11857
11914
|
function dsaDigest(alg) {
|
|
11858
11915
|
switch (alg) {
|
|
11859
11916
|
case "PS256":
|
|
@@ -11877,7 +11934,7 @@ function dsaDigest(alg) {
|
|
|
11877
11934
|
}
|
|
11878
11935
|
}
|
|
11879
11936
|
|
|
11880
|
-
//
|
|
11937
|
+
// node_modules/jose/dist/node/esm/runtime/node_key.js
|
|
11881
11938
|
var import_node_crypto4 = require("node:crypto");
|
|
11882
11939
|
var ecCurveAlgMap = /* @__PURE__ */ new Map([
|
|
11883
11940
|
["ES256", "P-256"],
|
|
@@ -11981,11 +12038,11 @@ function keyForCrypto(alg, key) {
|
|
|
11981
12038
|
return options ? { ...options, key } : key;
|
|
11982
12039
|
}
|
|
11983
12040
|
|
|
11984
|
-
//
|
|
12041
|
+
// node_modules/jose/dist/node/esm/runtime/sign.js
|
|
11985
12042
|
var crypto2 = __toESM(require("node:crypto"), 1);
|
|
11986
12043
|
var import_node_util = require("node:util");
|
|
11987
12044
|
|
|
11988
|
-
//
|
|
12045
|
+
// node_modules/jose/dist/node/esm/runtime/hmac_digest.js
|
|
11989
12046
|
function hmacDigest(alg) {
|
|
11990
12047
|
switch (alg) {
|
|
11991
12048
|
case "HS256":
|
|
@@ -11999,7 +12056,7 @@ function hmacDigest(alg) {
|
|
|
11999
12056
|
}
|
|
12000
12057
|
}
|
|
12001
12058
|
|
|
12002
|
-
//
|
|
12059
|
+
// node_modules/jose/dist/node/esm/runtime/get_sign_verify_key.js
|
|
12003
12060
|
var import_node_crypto5 = require("node:crypto");
|
|
12004
12061
|
function getSignVerifyKey(alg, key, usage) {
|
|
12005
12062
|
if (key instanceof Uint8Array) {
|
|
@@ -12024,7 +12081,7 @@ function getSignVerifyKey(alg, key, usage) {
|
|
|
12024
12081
|
throw new TypeError(invalid_key_input_default(key, ...types3, "Uint8Array", "JSON Web Key"));
|
|
12025
12082
|
}
|
|
12026
12083
|
|
|
12027
|
-
//
|
|
12084
|
+
// node_modules/jose/dist/node/esm/runtime/sign.js
|
|
12028
12085
|
var oneShotSign = (0, import_node_util.promisify)(crypto2.sign);
|
|
12029
12086
|
var sign2 = async (alg, key, data) => {
|
|
12030
12087
|
const k = getSignVerifyKey(alg, key, "sign");
|
|
@@ -12037,7 +12094,7 @@ var sign2 = async (alg, key, data) => {
|
|
|
12037
12094
|
};
|
|
12038
12095
|
var sign_default = sign2;
|
|
12039
12096
|
|
|
12040
|
-
//
|
|
12097
|
+
// node_modules/jose/dist/node/esm/runtime/verify.js
|
|
12041
12098
|
var oneShotVerify = (0, import_node_util2.promisify)(crypto3.verify);
|
|
12042
12099
|
var verify2 = async (alg, key, signature, data) => {
|
|
12043
12100
|
const k = getSignVerifyKey(alg, key, "verify");
|
|
@@ -12060,7 +12117,7 @@ var verify2 = async (alg, key, signature, data) => {
|
|
|
12060
12117
|
};
|
|
12061
12118
|
var verify_default = verify2;
|
|
12062
12119
|
|
|
12063
|
-
//
|
|
12120
|
+
// node_modules/jose/dist/node/esm/jws/flattened/verify.js
|
|
12064
12121
|
async function flattenedVerify(jws, key, options) {
|
|
12065
12122
|
if (!isObject(jws)) {
|
|
12066
12123
|
throw new JWSInvalid("Flattened JWS must be an object");
|
|
@@ -12166,7 +12223,7 @@ async function flattenedVerify(jws, key, options) {
|
|
|
12166
12223
|
return result;
|
|
12167
12224
|
}
|
|
12168
12225
|
|
|
12169
|
-
//
|
|
12226
|
+
// node_modules/jose/dist/node/esm/jws/compact/verify.js
|
|
12170
12227
|
async function compactVerify(jws, key, options) {
|
|
12171
12228
|
if (jws instanceof Uint8Array) {
|
|
12172
12229
|
jws = decoder.decode(jws);
|
|
@@ -12186,10 +12243,10 @@ async function compactVerify(jws, key, options) {
|
|
|
12186
12243
|
return result;
|
|
12187
12244
|
}
|
|
12188
12245
|
|
|
12189
|
-
//
|
|
12246
|
+
// node_modules/jose/dist/node/esm/lib/epoch.js
|
|
12190
12247
|
var epoch_default = (date) => Math.floor(date.getTime() / 1e3);
|
|
12191
12248
|
|
|
12192
|
-
//
|
|
12249
|
+
// node_modules/jose/dist/node/esm/lib/secs.js
|
|
12193
12250
|
var minute = 60;
|
|
12194
12251
|
var hour = minute * 60;
|
|
12195
12252
|
var day = hour * 24;
|
|
@@ -12246,7 +12303,7 @@ var secs_default = (str) => {
|
|
|
12246
12303
|
return numericDate;
|
|
12247
12304
|
};
|
|
12248
12305
|
|
|
12249
|
-
//
|
|
12306
|
+
// node_modules/jose/dist/node/esm/lib/jwt_claims_set.js
|
|
12250
12307
|
var normalizeTyp = (value) => value.toLowerCase().replace(/^application\//, "");
|
|
12251
12308
|
var checkAudiencePresence = (audPayload, audOption) => {
|
|
12252
12309
|
if (typeof audPayload === "string") {
|
|
@@ -12342,7 +12399,7 @@ var jwt_claims_set_default = (protectedHeader, encodedPayload, options = {}) =>
|
|
|
12342
12399
|
return payload;
|
|
12343
12400
|
};
|
|
12344
12401
|
|
|
12345
|
-
//
|
|
12402
|
+
// node_modules/jose/dist/node/esm/jwt/verify.js
|
|
12346
12403
|
async function jwtVerify(jwt, key, options) {
|
|
12347
12404
|
const verified = await compactVerify(jwt, key, options);
|
|
12348
12405
|
if (verified.protectedHeader.crit?.includes("b64") && verified.protectedHeader.b64 === false) {
|
|
@@ -12356,7 +12413,7 @@ async function jwtVerify(jwt, key, options) {
|
|
|
12356
12413
|
return result;
|
|
12357
12414
|
}
|
|
12358
12415
|
|
|
12359
|
-
//
|
|
12416
|
+
// node_modules/jose/dist/node/esm/jwks/local.js
|
|
12360
12417
|
function getKtyFromAlg(alg) {
|
|
12361
12418
|
switch (typeof alg === "string" && alg.slice(0, 2)) {
|
|
12362
12419
|
case "RS":
|
|
@@ -12477,7 +12534,7 @@ function createLocalJWKSet(jwks) {
|
|
|
12477
12534
|
return localJWKSet;
|
|
12478
12535
|
}
|
|
12479
12536
|
|
|
12480
|
-
//
|
|
12537
|
+
// node_modules/jose/dist/node/esm/runtime/fetch_jwks.js
|
|
12481
12538
|
var http = __toESM(require("node:http"), 1);
|
|
12482
12539
|
var https = __toESM(require("node:https"), 1);
|
|
12483
12540
|
var import_node_events = require("node:events");
|
|
@@ -12519,7 +12576,7 @@ var fetchJwks = async (url, timeout, options) => {
|
|
|
12519
12576
|
};
|
|
12520
12577
|
var fetch_jwks_default = fetchJwks;
|
|
12521
12578
|
|
|
12522
|
-
//
|
|
12579
|
+
// node_modules/jose/dist/node/esm/jwks/remote.js
|
|
12523
12580
|
function isCloudflareWorkers() {
|
|
12524
12581
|
return typeof WebSocketPair !== "undefined" || typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" || typeof EdgeRuntime !== "undefined" && EdgeRuntime === "vercel";
|
|
12525
12582
|
}
|
|
@@ -13175,12 +13232,18 @@ async function createRouter(options) {
|
|
|
13175
13232
|
sendTeamError: (err, res) => {
|
|
13176
13233
|
sendError(res, err, logger, "team operation");
|
|
13177
13234
|
},
|
|
13178
|
-
authorizeTeamSubresource: async (req, res, permission) => {
|
|
13235
|
+
authorizeTeamSubresource: async (req, res, permission, opts) => {
|
|
13179
13236
|
if (!ctx.requireTeamMgmt(res)) return null;
|
|
13180
13237
|
if (!teamAdminCfg.group) {
|
|
13181
13238
|
res.status(500).json({ error: "Team management is misconfigured (group is missing)" });
|
|
13182
13239
|
return null;
|
|
13183
13240
|
}
|
|
13241
|
+
if (teamAdminCfg.readOnly) {
|
|
13242
|
+
res.status(403).json({
|
|
13243
|
+
error: "Team management is read-only (litellm.teamAdmin.readOnly): teams are managed outside Backstage"
|
|
13244
|
+
});
|
|
13245
|
+
return null;
|
|
13246
|
+
}
|
|
13184
13247
|
const check = await assertTeamAdmin({
|
|
13185
13248
|
req,
|
|
13186
13249
|
auth,
|
|
@@ -13190,7 +13253,8 @@ async function createRouter(options) {
|
|
|
13190
13253
|
permission,
|
|
13191
13254
|
logger
|
|
13192
13255
|
});
|
|
13193
|
-
|
|
13256
|
+
const teamRoleEligible = !!opts?.allowTeamRole && teamAdminCfg.memberManagerRoles.length > 0;
|
|
13257
|
+
if (!check.ok && !(check.status === 403 && teamRoleEligible)) {
|
|
13194
13258
|
res.status(check.status).json({ error: check.error });
|
|
13195
13259
|
return null;
|
|
13196
13260
|
}
|
|
@@ -13211,29 +13275,52 @@ async function createRouter(options) {
|
|
|
13211
13275
|
return null;
|
|
13212
13276
|
}
|
|
13213
13277
|
const owningGroup = typeof existing.metadata?.owning_group === "string" ? existing.metadata.owning_group : void 0;
|
|
13214
|
-
|
|
13215
|
-
|
|
13278
|
+
let denial;
|
|
13279
|
+
if (!check.ok) {
|
|
13280
|
+
denial = { status: check.status, error: check.error };
|
|
13281
|
+
} else if (!owningGroup) {
|
|
13282
|
+
denial = {
|
|
13283
|
+
status: 403,
|
|
13216
13284
|
error: "This team is not managed by Backstage team admins and cannot be edited here"
|
|
13217
|
-
}
|
|
13218
|
-
|
|
13219
|
-
}
|
|
13220
|
-
const owns = await isUserMemberOfGroup(
|
|
13285
|
+
};
|
|
13286
|
+
} else if (await isUserMemberOfGroup(
|
|
13221
13287
|
check.userEntityRef,
|
|
13222
13288
|
owningGroup,
|
|
13223
13289
|
catalogClient,
|
|
13224
13290
|
auth,
|
|
13225
13291
|
logger
|
|
13226
|
-
)
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13292
|
+
)) {
|
|
13293
|
+
return {
|
|
13294
|
+
teamId,
|
|
13295
|
+
owningGroup,
|
|
13296
|
+
actor: check.userEntityRef,
|
|
13297
|
+
team: existing,
|
|
13298
|
+
via: "group"
|
|
13299
|
+
};
|
|
13300
|
+
} else {
|
|
13301
|
+
denial = {
|
|
13302
|
+
status: 403,
|
|
13303
|
+
error: `Access denied: team is owned by ${owningGroup}`
|
|
13304
|
+
};
|
|
13230
13305
|
}
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13306
|
+
if (teamRoleEligible) {
|
|
13307
|
+
const actor = await resolveUserId(req, auth);
|
|
13308
|
+
const litellmUserId = actor && toLiteLLMUserId(actor, userIdDomain);
|
|
13309
|
+
const role = existing.members_with_roles?.find(
|
|
13310
|
+
(m) => m.user_id === litellmUserId
|
|
13311
|
+
)?.role;
|
|
13312
|
+
if (actor && role && teamAdminCfg.memberManagerRoles.includes(role) && await ctx.assertPermission(req, permission)) {
|
|
13313
|
+
return {
|
|
13314
|
+
teamId,
|
|
13315
|
+
owningGroup: owningGroup ?? "",
|
|
13316
|
+
actor,
|
|
13317
|
+
team: existing,
|
|
13318
|
+
via: "teamRole"
|
|
13319
|
+
};
|
|
13320
|
+
}
|
|
13321
|
+
}
|
|
13322
|
+
res.status(denial.status).json({ error: denial.error });
|
|
13323
|
+
return null;
|
|
13237
13324
|
}
|
|
13238
13325
|
};
|
|
13239
13326
|
const router = (0, import_express.Router)();
|