import { Config } from '@backstage/config'; import { LoggerService } from '@backstage/backend-plugin-api'; import { LiteLLMClient } from './client'; import { ProvisioningDefaults, UserInfo, VirtualKey } from './types'; /** Claims extracted from a verified Keycloak access token. */ export interface BridgeClaims { sub: string; email?: string; email_verified?: boolean; preferred_username?: string; name?: string; /** Authorized party — Keycloak sets this to the client_id of the requester. */ azp?: string; aud?: string | string[]; /** Keycloak payload `typ` claim: 'Bearer' for access tokens, 'ID' for ID tokens. */ typ?: string; } /** A token verifier pluggable for tests. */ export interface TokenVerifier { verify(token: string): Promise; } export interface BridgeConfig { enabled: boolean; /** Keycloak realm issuer, e.g. https://auth.example.com/realms/solution-innovation. */ issuer?: string; /** OIDC public client the CLI uses; checked against azp / aud. */ clientId: string; /** * Email domains whose verified addresses may use the bridge. Defaults to * `litellm.userIdDomain` when that is set; with neither, the bridge rejects * every caller (fail closed). */ allowedEmailDomains: string[]; } export declare function readBridgeConfig(config: Config): BridgeConfig; /** Thrown when the bridge is misconfigured (e.g. enabled without an issuer). */ export declare class BridgeConfigError extends Error { } /** Thrown when a presented token fails verification → maps to HTTP 401. */ export declare class BridgeAuthError extends Error { readonly status = 401; } /** Thrown when the caller's identity fails validation (e.g. email domain mismatch) → maps to HTTP 403. */ export declare class BridgeIdentityError extends Error { readonly status = 403; } export interface KeycloakJWTVerifierOptions { issuer: string; clientId: string; } /** * Verifies a Keycloak access token against the realm JWKS and ensures it was * issued for {@link clientId} (via azp, falling back to aud). Uses jose's * remote JWKS client (cached, with cooldown on errors). */ export declare class KeycloakJWTVerifier implements TokenVerifier { private readonly issuer; private readonly clientId; private readonly jwks; constructor(opts: KeycloakJWTVerifierOptions); verify(token: string): Promise; } /** Builds the default verifier from config, or throws BridgeConfigError. */ export declare function newDefaultVerifier(cfg: BridgeConfig): TokenVerifier; /** * How the bridge maps a verified token to a LiteLLM user. * - `userIdDomain`: the same `litellm.userIdDomain` the UI uses to build ids. * - `trustedEmailDomains`: email domains allowed to use the bridge; defaults to * `[userIdDomain]` when that is set. */ export interface BridgeIdentityOptions { userIdDomain?: string; trustedEmailDomains?: string[]; } /** * Resolves the LiteLLM user_id for a verified bridge token. * * Two separate questions, answered separately: * 1. Is this person allowed in? The token must carry a *verified* email whose * domain is in the trusted list (`litellm.bridge.allowedEmailDomains`, * defaulting to `litellm.userIdDomain`). With no trusted domain configured * everyone is rejected (fail closed). Failure → 403. * 2. Which LiteLLM user are they? The same one the UI addresses: the Backstage * user entity name (the Keycloak `preferred_username`) run through the same * `litellm.userIdDomain` rule the UI applies. Using the email local part * here would address a different user whenever a Keycloak username differs * from the email (and would provision a duplicate). The realm is the same * IdP Backstage signs users in with, so its usernames are the entity names. * * When `preferred_username` is email-shaped (contains '@') and its domain * is in the trusted list, we extract the local part and use that as the * username instead. This handles the Abby CLI case where `preferred_username` * is the full email. * * @throws BridgeIdentityError (403) if identity validation fails */ export declare function resolveBridgeUserId(claims: BridgeClaims, identity?: BridgeIdentityOptions): string; /** * Ensures a LiteLLM user exists for the verified identity. If the user is * missing and provisioning is enabled, creates it from the JWT claims (email + * name); if provisioning is disabled, throws a 404 telling the caller to log * in to Backstage first (the UI is the primary provisioning entry point). * * When the computed userId is not found, before provisioning we also search * for an existing user by email. This handles the case where a UI user (created * with a bare entity name) is accessed via the CLI (which uses email-shaped * preferred_username), and helps recover from cases where the bridge's userId * computation and the UI's differ. */ export declare function getOrProvisionUserFromClaims(client: LiteLLMClient, claims: BridgeClaims, provisioningEnabled: boolean, provisioningDefaults: ProvisioningDefaults, logger: LoggerService, identity?: BridgeIdentityOptions): Promise; /** Lists the caller's virtual keys (provisioning the user first if needed). */ export declare function bridgeListKeys(client: LiteLLMClient, claims: BridgeClaims, provisioningEnabled: boolean, provisioningDefaults: ProvisioningDefaults, logger: LoggerService, identity?: BridgeIdentityOptions): Promise;