| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import { getDbInstance } from "@/lib/db/core";
|
| import { randomUUID } from "crypto";
|
|
|
|
|
|
|
| export interface KeyGroup {
|
| id: string;
|
| name: string;
|
| description: string;
|
| isActive: boolean;
|
| createdAt: string;
|
| updatedAt: string;
|
| }
|
|
|
| export interface GroupModelPermission {
|
| id: string;
|
| groupId: string;
|
| modelPattern: string;
|
| provider: string | null;
|
| accessType: "allow" | "deny";
|
| createdAt: string;
|
| }
|
|
|
| export interface KeyGroupMember {
|
| keyId: string;
|
| groupId: string;
|
| createdAt: string;
|
| }
|
|
|
| export interface KeyGroupWithPermissions extends KeyGroup {
|
| permissions: GroupModelPermission[];
|
| memberCount: number;
|
| }
|
|
|
|
|
|
|
| export function getAllKeyGroups(): KeyGroup[] {
|
| const db = getDbInstance() as any;
|
| const rows = db.prepare("SELECT * FROM key_groups ORDER BY name ASC").all() as any[];
|
| return rows.map(rowToGroup);
|
| }
|
|
|
| export function getKeyGroup(id: string): KeyGroup | undefined {
|
| const db = getDbInstance() as any;
|
| const row = db.prepare("SELECT * FROM key_groups WHERE id = ?").get(id) as any;
|
| return row ? rowToGroup(row) : undefined;
|
| }
|
|
|
| export function getKeyGroupWithPermissions(id: string): KeyGroupWithPermissions | undefined {
|
| const group = getKeyGroup(id);
|
| if (!group) return undefined;
|
|
|
| const permissions = getGroupPermissions(id);
|
| const memberCount = getGroupMemberCount(id);
|
|
|
| return { ...group, permissions, memberCount };
|
| }
|
|
|
| export function createKeyGroup(name: string, description = ""): KeyGroup {
|
| const db = getDbInstance() as any;
|
| const id = randomUUID();
|
| const now = new Date().toISOString();
|
|
|
| db.prepare(
|
| "INSERT INTO key_groups (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
|
| ).run(id, name, description, now, now);
|
|
|
| return getKeyGroup(id)!;
|
| }
|
|
|
| export function updateKeyGroup(
|
| id: string,
|
| updates: { name?: string; description?: string; isActive?: boolean }
|
| ): KeyGroup | undefined {
|
| const existing = getKeyGroup(id);
|
| if (!existing) return undefined;
|
|
|
| const db = getDbInstance() as any;
|
| const sets: string[] = [];
|
| const params: Record<string, unknown> = { id };
|
|
|
| if (updates.name !== undefined) {
|
| sets.push("name = @name");
|
| params.name = updates.name;
|
| }
|
| if (updates.description !== undefined) {
|
| sets.push("description = @description");
|
| params.description = updates.description;
|
| }
|
| if (updates.isActive !== undefined) {
|
| sets.push("is_active = @isActive");
|
| params.isActive = updates.isActive ? 1 : 0;
|
| }
|
|
|
| if (sets.length === 0) return existing;
|
| sets.push("updated_at = datetime('now')");
|
|
|
| db.prepare(`UPDATE key_groups SET ${sets.join(", ")} WHERE id = @id`).run(params);
|
| return getKeyGroup(id);
|
| }
|
|
|
| export function deleteKeyGroup(id: string): boolean {
|
| const db = getDbInstance() as any;
|
|
|
| const result = db.prepare("DELETE FROM key_groups WHERE id = ?").run(id);
|
| return result.changes > 0;
|
| }
|
|
|
|
|
|
|
| export function getGroupPermissions(groupId: string): GroupModelPermission[] {
|
| const db = getDbInstance() as any;
|
| const rows = db
|
| .prepare(
|
| "SELECT * FROM group_model_permissions WHERE group_id = ? ORDER BY access_type ASC, model_pattern ASC"
|
| )
|
| .all(groupId) as any[];
|
| return rows.map(rowToPermission);
|
| }
|
|
|
| export function addGroupPermission(
|
| groupId: string,
|
| modelPattern: string,
|
| accessType: "allow" | "deny",
|
| provider?: string
|
| ): GroupModelPermission {
|
| const db = getDbInstance() as any;
|
| const id = randomUUID();
|
| const now = new Date().toISOString();
|
|
|
| db.prepare(
|
| "INSERT INTO group_model_permissions (id, group_id, model_pattern, provider, access_type, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
| ).run(id, groupId, modelPattern, provider || null, accessType, now);
|
|
|
| return getGroupPermissions(groupId).find((p) => p.id === id)!;
|
| }
|
|
|
| export function removeGroupPermission(permissionId: string): boolean {
|
| const db = getDbInstance() as any;
|
| const result = db.prepare("DELETE FROM group_model_permissions WHERE id = ?").run(permissionId);
|
| return result.changes > 0;
|
| }
|
|
|
| export function clearGroupPermissions(groupId: string): void {
|
| const db = getDbInstance() as any;
|
| db.prepare("DELETE FROM group_model_permissions WHERE group_id = ?").run(groupId);
|
| }
|
|
|
|
|
|
|
| export function getGroupMembers(groupId: string): KeyGroupMember[] {
|
| const db = getDbInstance() as any;
|
| const rows = db
|
| .prepare("SELECT * FROM key_group_members WHERE group_id = ? ORDER BY created_at ASC")
|
| .all(groupId) as any[];
|
| return rows.map(rowToMember);
|
| }
|
|
|
| export function getKeyGroupsForApiKey(keyId: string): KeyGroup[] {
|
| const db = getDbInstance() as any;
|
| const rows = db
|
| .prepare(
|
| `
|
| SELECT g.* FROM key_groups g
|
| INNER JOIN key_group_members m ON g.id = m.group_id
|
| WHERE m.key_id = ? AND g.is_active = 1
|
| ORDER BY g.name ASC
|
| `
|
| )
|
| .all(keyId) as any[];
|
| return rows.map(rowToGroup);
|
| }
|
|
|
| export function addKeyToGroup(keyId: string, groupId: string): boolean {
|
| const db = getDbInstance() as any;
|
| try {
|
| db.prepare("INSERT OR IGNORE INTO key_group_members (key_id, group_id) VALUES (?, ?)").run(
|
| keyId,
|
| groupId
|
| );
|
| return true;
|
| } catch {
|
| return false;
|
| }
|
| }
|
|
|
| export function removeKeyFromGroup(keyId: string, groupId: string): boolean {
|
| const db = getDbInstance() as any;
|
| const result = db
|
| .prepare("DELETE FROM key_group_members WHERE key_id = ? AND group_id = ?")
|
| .run(keyId, groupId);
|
| return result.changes > 0;
|
| }
|
|
|
| function getGroupMemberCount(groupId: string): number {
|
| const db = getDbInstance() as any;
|
| const row = db
|
| .prepare("SELECT COUNT(*) as count FROM key_group_members WHERE group_id = ?")
|
| .get(groupId) as any;
|
| return row?.count || 0;
|
| }
|
|
|
|
|
|
|
| export interface ModelAccessCheck {
|
| allowed: boolean;
|
| matchedRules: GroupModelPermission[];
|
| deniedBy: GroupModelPermission | null;
|
| }
|
|
|
| |
| |
| |
|
|
| export function checkKeyModelAccess(
|
| keyId: string,
|
| model: string,
|
| provider?: string
|
| ): ModelAccessCheck {
|
| const groups = getKeyGroupsForApiKey(keyId);
|
| if (groups.length === 0) {
|
|
|
| return { allowed: true, matchedRules: [], deniedBy: null };
|
| }
|
|
|
| const db = getDbInstance() as any;
|
| const groupIds = groups.map((g) => g.id);
|
| const placeholders = groupIds.map(() => "?").join(",");
|
|
|
| const rules = db
|
| .prepare(
|
| `
|
| SELECT * FROM group_model_permissions
|
| WHERE group_id IN (${placeholders})
|
| ORDER BY access_type ASC
|
| `
|
| )
|
| .all(...groupIds) as any[];
|
|
|
| const permissions = rules.map(rowToPermission);
|
|
|
|
|
| const denyRules = permissions.filter(
|
| (p) =>
|
| p.accessType === "deny" &&
|
| matchesModelPattern(p.modelPattern, model) &&
|
| (!p.provider || p.provider === provider)
|
| );
|
|
|
| if (denyRules.length > 0) {
|
| return { allowed: false, matchedRules: permissions, deniedBy: denyRules[0] };
|
| }
|
|
|
|
|
| const allowRules = permissions.filter(
|
| (p) =>
|
| p.accessType === "allow" &&
|
| matchesModelPattern(p.modelPattern, model) &&
|
| (!p.provider || p.provider === provider)
|
| );
|
|
|
| if (allowRules.length > 0) {
|
| return { allowed: true, matchedRules: permissions, deniedBy: null };
|
| }
|
|
|
|
|
| return { allowed: false, matchedRules: permissions, deniedBy: null };
|
| }
|
|
|
| function matchesModelPattern(pattern: string, model: string): boolean {
|
| if (pattern === "*") return true;
|
| if (pattern.includes("*")) {
|
| const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
| return regex.test(model);
|
| }
|
| return pattern === model;
|
| }
|
|
|
|
|
|
|
| function rowToGroup(row: any): KeyGroup {
|
| return {
|
| id: row.id,
|
| name: row.name,
|
| description: row.description || "",
|
| isActive: row.is_active === 1,
|
| createdAt: row.created_at,
|
| updatedAt: row.updated_at,
|
| };
|
| }
|
|
|
| function rowToPermission(row: any): GroupModelPermission {
|
| return {
|
| id: row.id,
|
| groupId: row.group_id,
|
| modelPattern: row.model_pattern,
|
| provider: row.provider || null,
|
| accessType: row.access_type,
|
| createdAt: row.created_at,
|
| };
|
| }
|
|
|
| function rowToMember(row: any): KeyGroupMember {
|
| return {
|
| keyId: row.key_id,
|
| groupId: row.group_id,
|
| createdAt: row.created_at,
|
| };
|
| }
|
|
|