Files
OmniRoute/open-sse/services/keyGroupAuth.ts
Webman 4e11887085 fix(barrel): migrate open-sse, src/shared, src/sse, src/models, src/domain off the @/lib/localDb barrel import (#11795 Phase 4) (#12053)
Resynced onto the release tip after #12051/#12052 landed. One real conflict in open-sse/services/combo.ts at both LKGP-clear call sites (handleComboChat + round-robin path): the release tip already has #12013's clearStaleLKGP() helper, which this PR's branch predates — kept the current helper call at both sites, discarding the pre-refactor inline pattern. typecheck:core and the open-sse test suite (vitest, 9/9 on volumeDetector) both green after resync. Thanks for the well-scoped Phase 4 migration.
2026-08-30 02:31:09 -03:00

85 lines
2.6 KiB
TypeScript

/**
* Key Group Authorization Service
*
* Enforces model-level access control based on API key group membership.
* Used in the request pipeline to check if an API key's groups allow
* access to the requested model.
*
* If the API key is not in any group, all models are allowed (no restriction).
* If the API key IS in a group, model access is determined by permissions:
* - Deny rules override allow rules
* - Patterns support wildcards: "gpt-*", "claude-*", "*" for all
* - Provider-specific rules: "openai/gpt-4"
*/
import { checkKeyModelAccess, getKeyGroupsForApiKey } from "@/lib/db/apiKeyGroups";
export interface KeyGroupAuthResult {
/** Whether the request is authorized */
authorized: boolean;
/** Human-readable reason if denied */
reason?: string;
/** Groups that apply to this key */
groups: Array<{ id: string; name: string }>;
}
/**
* Check if an API key has access to a specific model.
* This is the main entry point for the request pipeline.
*
* @param apiKeyId - The API key ID from the auth pipeline
* @param model - The model string (e.g., "gpt-4", "claude-opus-4-7")
* @param provider - Optional provider (e.g., "openai", "anthropic")
* @returns KeyGroupAuthResult
*/
export function authorizeKeyModelAccess(
apiKeyId: string | undefined,
model: string,
provider?: string
): KeyGroupAuthResult {
if (!apiKeyId) {
// No API key = no restriction (public endpoint)
return { authorized: true, groups: [] };
}
const groups = getKeyGroupsForApiKey(apiKeyId);
if (groups.length === 0) {
// Key not in any group = no restriction
return { authorized: true, groups: [] };
}
const accessCheck = checkKeyModelAccess(apiKeyId, model, provider);
if (accessCheck.allowed) {
return {
authorized: true,
groups: groups.map((g) => ({ id: g.id, name: g.name })),
};
}
const denyReason = accessCheck.deniedBy
? `Model "${model}" is denied by group permission (pattern: ${accessCheck.deniedBy.modelPattern})`
: `Model "${model}" is not in the allowed models for your API key group(s). ` +
`Configure group permissions or contact your administrator.`;
return {
authorized: false,
reason: denyReason,
groups: groups.map((g) => ({ id: g.id, name: g.name })),
};
}
/**
* Get a summary of group memberships for an API key (for dashboard display).
*/
export function getKeyGroupSummary(apiKeyId: string): {
groups: Array<{ id: string; name: string }>;
restricted: boolean;
} {
const groups = getKeyGroupsForApiKey(apiKeyId);
return {
groups: groups.map((g) => ({ id: g.id, name: g.name })),
restricted: groups.length > 0,
};
}