@acarmisc/backstage-plugin-litellm-backend 0.17.2 → 0.17.4

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/plugin.d.ts CHANGED
@@ -1 +1,7 @@
1
+ /**
2
+ * Bridge routes exempt from Backstage's default auth policy (they verify the
3
+ * Keycloak JWT themselves). Every route registered in routes/bridge.ts must be
4
+ * listed here, or the framework answers 401 before the bridge verifier runs.
5
+ */
6
+ export declare const BRIDGE_UNAUTHENTICATED_PATHS: string[];
1
7
  export declare const litellmPlugin: import("@backstage/backend-plugin-api").BackendFeature;
package/dist/plugin.js CHANGED
@@ -1,10 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.litellmPlugin = void 0;
3
+ exports.litellmPlugin = exports.BRIDGE_UNAUTHENTICATED_PATHS = void 0;
4
4
  const backend_plugin_api_1 = require("@backstage/backend-plugin-api");
5
5
  const router_1 = require("./router");
6
6
  const bridge_1 = require("./bridge");
7
7
  const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
8
+ /**
9
+ * Bridge routes exempt from Backstage's default auth policy (they verify the
10
+ * Keycloak JWT themselves). Every route registered in routes/bridge.ts must be
11
+ * listed here, or the framework answers 401 before the bridge verifier runs.
12
+ */
13
+ exports.BRIDGE_UNAUTHENTICATED_PATHS = [
14
+ '/bridge/health',
15
+ '/bridge/keys',
16
+ '/bridge/models',
17
+ '/bridge/user/info',
18
+ ];
8
19
  exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
9
20
  pluginId: 'litellm',
10
21
  register(reg) {
@@ -33,11 +44,7 @@ exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
33
44
  // policy, otherwise the framework rejects the Keycloak Bearer with
34
45
  // "Illegal token" before the bridge verifier ever runs.
35
46
  if ((0, bridge_1.readBridgeConfig)(config).enabled) {
36
- for (const path of [
37
- '/bridge/health',
38
- '/bridge/keys',
39
- '/bridge/models',
40
- ]) {
47
+ for (const path of exports.BRIDGE_UNAUTHENTICATED_PATHS) {
41
48
  httpRouter.addAuthPolicy({ path, allow: 'unauthenticated' });
42
49
  }
43
50
  }
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerBridgeRoutes = registerBridgeRoutes;
4
4
  const provisioning_1 = require("../provisioning");
5
5
  const bridge_1 = require("../bridge");
6
+ const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
6
7
  const keyService_1 = require("../services/keyService");
7
8
  const errors_1 = require("../errors");
8
- const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
9
+ const backstage_plugin_litellm_common_2 = require("@acarmisc/backstage-plugin-litellm-common");
9
10
  function registerBridgeRoutes(router, ctx, bridgeOpts) {
10
11
  const { client, logger, userIdDomain, provisioningEnabled, provisioningDefaults, roleConfigs, allowUnlimitedBudget, teamRequired, keyValidationConfig, } = ctx;
11
12
  const { config } = bridgeOpts;
12
- const generateKeyInputSchema = (0, backstage_plugin_litellm_common_1.createGenerateKeyInputSchema)(ctx.keyValidationConfig);
13
+ const generateKeyInputSchema = (0, backstage_plugin_litellm_common_2.createGenerateKeyInputSchema)(ctx.keyValidationConfig);
13
14
  // ---------------------------------------------------------------------------
14
15
  // Bridge endpoints (CLI / Abby)
15
16
  //
@@ -125,11 +126,42 @@ function registerBridgeRoutes(router, ctx, bridgeOpts) {
125
126
  handleBridgeError(error, res);
126
127
  }
127
128
  });
129
+ // Caller's LiteLLM identity: user id + team memberships. Lets CLI clients
130
+ // offer a team picker and mint keys under a valid team.
131
+ router.get('/bridge/user/info', async (req, res) => {
132
+ try {
133
+ const claims = await requireClaims(req);
134
+ const user = await (0, bridge_1.getOrProvisionUserFromClaims)(client, claims, provisioningEnabled, provisioningDefaults, logger, bridgeIdentity);
135
+ res.json({ user_id: user.user_id, teams: user.teams ?? [] });
136
+ }
137
+ catch (error) {
138
+ handleBridgeError(error, res);
139
+ }
140
+ });
141
+ // Model catalogue. With ?team_id=X the list is narrowed to the models the
142
+ // team may use (its `models` plus access groups); the caller must belong
143
+ // to that team. Without it the full catalogue is returned, as before.
128
144
  router.get('/bridge/models', async (req, res) => {
129
145
  try {
130
- await requireClaims(req); // authenticate only
146
+ const claims = await requireClaims(req);
147
+ const teamId = typeof req.query.team_id === 'string' ? req.query.team_id : '';
131
148
  const models = await client.listModels();
132
- res.json(models);
149
+ if (!teamId) {
150
+ res.json(models);
151
+ return;
152
+ }
153
+ const user = await (0, bridge_1.getOrProvisionUserFromClaims)(client, claims, provisioningEnabled, provisioningDefaults, logger, bridgeIdentity);
154
+ if (!(user.teams ?? []).includes(teamId)) {
155
+ res.status(403).json({ error: 'Access denied: team is not one of your teams', team_id: teamId });
156
+ return;
157
+ }
158
+ const team = await client.getTeamInfo(teamId);
159
+ const allowed = team.models;
160
+ if (!allowed || allowed.length === 0 || allowed.includes(backstage_plugin_litellm_common_1.ALL_PROXY_MODELS)) {
161
+ res.json(models);
162
+ return;
163
+ }
164
+ res.json(models.filter(m => (0, backstage_plugin_litellm_common_1.isModelAllowed)(m, allowed)));
133
165
  }
134
166
  catch (error) {
135
167
  handleBridgeError(error, res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",