import { getTenantId } from '@librechat/data-schemas'; import { createHash, createPrivateKey, randomUUID, sign as cryptoSign } from 'crypto'; import type { KeyObject, JsonWebKey } from 'crypto'; import type { ServerRequest } from '~/types'; import { isEnabled } from '~/utils'; type CodeApiJwtAlg = 'EdDSA' | 'RS256'; type PrincipalSource = 'librechat_jwt' | 'openid_reuse'; interface CodeApiUserContext { id?: string; _id?: { toString(): string }; role?: string; tenantId?: string | { toString(): string }; provider?: string; orgId?: string; serviceId?: string; chcUserId?: string; idOnTheSource?: string; planId?: string; subscription?: { planId?: string; }; } interface CodeApiClaims { iss: string; aud: string; sub: string; iat: number; nbf: number; exp: number; jti: string; tenant_id: string; role: string; principal_source: PrincipalSource; org_id?: string; service_id?: string; chc_user_id?: string; plan_id?: string; auth_context_hash: string; } interface SigningConfig { alg: CodeApiJwtAlg; kid: string; issuer: string; audience: string; ttlSeconds: number; cacheSeconds: number; key: KeyObject; rawKey: string; } interface CachedToken { token: string; expiresAt: number; cachedUntil: number; } const DEFAULT_ISSUER = 'librechat'; const DEFAULT_AUDIENCE = 'codeapi'; const DEFAULT_KID = 'lc-codeapi-2026-05'; const DEFAULT_SINGLE_TENANT_ID = 'legacy'; const DEFAULT_TTL_SECONDS = 300; const DEFAULT_CACHE_SECONDS = 30; const MAX_TTL_SECONDS = 300; const MAX_CACHE_SECONDS = 30; const TOKEN_REUSE_SAFETY_WINDOW_SECONDS = 30; const TOKEN_CACHE_PRUNE_INTERVAL_SECONDS = 30; let signingConfigCache: SigningConfig | null = null; const tokenCache = new Map(); let tokenCacheLastPrunedAt = 0; function base64Url(value: Buffer | string): string { return Buffer.from(value).toString('base64url'); } function normalizePem(value: string): string { return value.replace(/\\n/g, '\n').trim(); } function getPrivateKeyRaw(): string { const inlineKey = process.env.CODEAPI_JWT_PRIVATE_KEY; if (inlineKey != null && inlineKey.trim() !== '') { return normalizePem(inlineKey); } const base64Key = process.env.CODEAPI_JWT_PRIVATE_KEY_BASE64; if (base64Key != null && base64Key.trim() !== '') { return normalizePem(Buffer.from(base64Key, 'base64').toString('utf8')); } const jwkJson = process.env.CODEAPI_JWT_PRIVATE_JWK_JSON; if (jwkJson != null && jwkJson.trim() !== '') { return jwkJson.trim(); } throw new Error('Code API JWT signing key is not configured'); } function parseAlg(value: string | undefined): CodeApiJwtAlg { if (value === 'RS256') { return 'RS256'; } if (value === undefined || value === '' || value === 'EdDSA') { return 'EdDSA'; } throw new Error(`Unsupported Code API JWT algorithm: ${value}`); } function parseCappedSeconds(value: string | undefined, fallback: number, max: number): number { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return fallback; } return Math.min(Math.floor(parsed), max); } function createSigningKey(rawKey: string): KeyObject { if (rawKey.startsWith('{')) { return createPrivateKey({ key: JSON.parse(rawKey) as JsonWebKey, format: 'jwk' }); } return createPrivateKey(rawKey); } function getSigningConfig(): SigningConfig { const rawKey = getPrivateKeyRaw(); const alg = parseAlg(process.env.CODEAPI_JWT_ALGORITHM); const kid = process.env.CODEAPI_JWT_KID ?? process.env.CODEAPI_JWT_KEY_ID ?? DEFAULT_KID; const issuer = process.env.CODEAPI_JWT_ISSUER ?? DEFAULT_ISSUER; const audience = process.env.CODEAPI_JWT_AUDIENCE ?? DEFAULT_AUDIENCE; const ttlSeconds = parseCappedSeconds( process.env.CODEAPI_JWT_TTL_SECONDS, DEFAULT_TTL_SECONDS, MAX_TTL_SECONDS, ); const cacheSeconds = parseCappedSeconds( process.env.CODEAPI_JWT_MINT_CACHE_SECONDS, DEFAULT_CACHE_SECONDS, MAX_CACHE_SECONDS, ); if ( signingConfigCache && signingConfigCache.rawKey === rawKey && signingConfigCache.alg === alg && signingConfigCache.kid === kid && signingConfigCache.issuer === issuer && signingConfigCache.audience === audience && signingConfigCache.ttlSeconds === ttlSeconds && signingConfigCache.cacheSeconds === cacheSeconds ) { return signingConfigCache; } signingConfigCache = { alg, kid, issuer, audience, ttlSeconds, cacheSeconds, rawKey, key: createSigningKey(rawKey), }; tokenCache.clear(); tokenCacheLastPrunedAt = 0; return signingConfigCache; } function stringifyClaimValue(value: unknown): string | undefined { if (typeof value === 'string' && value.trim() !== '') { return value; } if (value && typeof value === 'object' && 'toString' in value) { const stringValue = value.toString(); return stringValue.trim() === '' ? undefined : stringValue; } return undefined; } function resolveUser(req: ServerRequest): CodeApiUserContext { const user = req.user as CodeApiUserContext | undefined; if (!user) { throw new Error('Code API token minting requires an authenticated user'); } return user; } function resolveUserId(user: CodeApiUserContext): string { const userId = stringifyClaimValue(user.id) ?? stringifyClaimValue(user._id); if (!userId) { throw new Error('Code API token minting requires a canonical user id'); } return userId; } function resolveSingleTenantId(): string { const configured = process.env.CODEAPI_JWT_SINGLE_TENANT_ID; if (configured != null && configured.trim() !== '') { return configured.trim(); } return DEFAULT_SINGLE_TENANT_ID; } function resolveTenantId(user: CodeApiUserContext): string | undefined { const tenantId = stringifyClaimValue(user.tenantId) ?? getTenantId(); if (tenantId) { return tenantId; } if (isEnabled(process.env.TENANT_ISOLATION_STRICT)) { return undefined; } return resolveSingleTenantId(); } function isManagedCodeApiJwtMode(): boolean { const provider = process.env.CODEAPI_AUTH_PROVIDER; return provider === 'librechat-jwt' || provider === 'both'; } export function isCodeApiJwtAuthEnabled(): boolean { return isManagedCodeApiJwtMode() || isEnabled(process.env.CODEAPI_JWT_ENABLED); } function resolvePrincipalSource(req: ServerRequest): PrincipalSource { if (req.authStrategy === 'openidJwt') { return 'openid_reuse'; } return 'librechat_jwt'; } function canonicalContextHash(input: { userId: string; tenantId: string; role: string; principalSource: PrincipalSource; orgId?: string; serviceId?: string; chcUserId?: string; }): string { const canonical = { chc_user_id: input.chcUserId ?? '', org_id: input.orgId ?? '', principal_source: input.principalSource, role: input.role, service_id: input.serviceId ?? '', sub: input.userId, tenant_id: input.tenantId, }; return createHash('sha256').update(JSON.stringify(canonical)).digest('hex'); } function buildClaims(req: ServerRequest, config: SigningConfig, now: number): CodeApiClaims { const user = resolveUser(req); const userId = resolveUserId(user); const tenantId = resolveTenantId(user); if (!tenantId) { throw new Error('Code API JWT auth requires tenant context'); } const role = user.role ?? 'USER'; const principalSource = resolvePrincipalSource(req); const orgId = stringifyClaimValue(user.orgId); const serviceId = stringifyClaimValue(user.serviceId); const chcUserId = stringifyClaimValue(user.chcUserId) ?? stringifyClaimValue(user.idOnTheSource); const planId = stringifyClaimValue(user.planId) ?? stringifyClaimValue(user.subscription?.planId); const authContextHash = canonicalContextHash({ userId, tenantId, role, principalSource, orgId, serviceId, chcUserId, }); return { iss: config.issuer, aud: config.audience, sub: userId, iat: now, nbf: now, exp: now + config.ttlSeconds, jti: randomUUID(), tenant_id: tenantId, role, principal_source: principalSource, ...(orgId ? { org_id: orgId } : {}), ...(serviceId ? { service_id: serviceId } : {}), ...(chcUserId ? { chc_user_id: chcUserId } : {}), ...(planId ? { plan_id: planId } : {}), auth_context_hash: authContextHash, }; } function signJwt(config: SigningConfig, claims: CodeApiClaims): string { const header = { alg: config.alg, typ: 'JWT', kid: config.kid, }; const signingInput = `${base64Url(JSON.stringify(header))}.${base64Url(JSON.stringify(claims))}`; const signature = cryptoSign( config.alg === 'RS256' ? 'RSA-SHA256' : null, Buffer.from(signingInput), config.key, ); return `${signingInput}.${base64Url(signature)}`; } function cacheKey(config: SigningConfig, claims: CodeApiClaims): string { return [ config.alg, config.kid, claims.sub, claims.tenant_id, claims.role, claims.principal_source, claims.org_id ?? '', claims.service_id ?? '', claims.chc_user_id ?? '', claims.plan_id ?? '', claims.auth_context_hash, ].join(':'); } function pruneTokenCache(now: number): void { if (tokenCache.size === 0) { return; } if (now - tokenCacheLastPrunedAt < TOKEN_CACHE_PRUNE_INTERVAL_SECONDS) { return; } tokenCacheLastPrunedAt = now; for (const [key, cached] of tokenCache) { if (cached.cachedUntil <= now || cached.expiresAt <= now + TOKEN_REUSE_SAFETY_WINDOW_SECONDS) { tokenCache.delete(key); } } } export async function mintCodeApiToken(req: ServerRequest): Promise { if (!isCodeApiJwtAuthEnabled()) { return ''; } const config = getSigningConfig(); const now = Math.floor(Date.now() / 1000); pruneTokenCache(now); const claims = buildClaims(req, config, now); const key = cacheKey(config, claims); const cached = tokenCache.get(key); if ( cached && cached.cachedUntil > now && cached.expiresAt > now + TOKEN_REUSE_SAFETY_WINDOW_SECONDS ) { return cached.token; } const token = signJwt(config, claims); tokenCache.set(key, { token, expiresAt: claims.exp, cachedUntil: Math.min( now + config.cacheSeconds, claims.exp - TOKEN_REUSE_SAFETY_WINDOW_SECONDS, ), }); return token; } export async function getCodeApiAuthHeaders(req?: ServerRequest): Promise> { if (!req || !isCodeApiJwtAuthEnabled()) { return {}; } const token = await mintCodeApiToken(req); return token ? { Authorization: `Bearer ${token}` } : {}; }