import type { AuthUser } from "./auth.ts"; import { withIamTenant } from "./iam_db.ts"; export type UserScope = { allProjects: boolean; projectIds: number[]; warehouseCentral: boolean; warehouseProjects: boolean; }; const scopeCache = new Map(); const SCOPE_TTL_MS = 60_000; export function invalidateUserScopeCache(userId?: number) { if (userId != null) scopeCache.delete(userId); else scopeCache.clear(); } export async function loadUserScope(user: AuthUser): Promise { if (user.is_owner || user.realm === "platform") { return { allProjects: true, projectIds: [], warehouseCentral: true, warehouseProjects: true, }; } const hit = scopeCache.get(user.id); if (hit && Date.now() - hit.at < SCOPE_TTL_MS) return hit.scope; const row = await withIamTenant(user.tenant_id, async (db) => { const u = await db.prepare( `SELECT scope_all_projects, warehouse_central, warehouse_projects FROM users WHERE id = ?`, ).get(user.id) as { scope_all_projects: boolean; warehouse_central: boolean; warehouse_projects: boolean; } | undefined; if (!u) return null; const projects = await db.prepare( `SELECT project_id FROM user_projects WHERE user_id = ? ORDER BY project_id`, ).all(user.id) as { project_id: number }[]; return { allProjects: Boolean(u.scope_all_projects), projectIds: projects.map((p) => Number(p.project_id)), warehouseCentral: Boolean(u.warehouse_central), warehouseProjects: Boolean(u.warehouse_projects), }; }); const scope = row ?? { allProjects: false, projectIds: [], warehouseCentral: false, warehouseProjects: false, }; scopeCache.set(user.id, { scope, at: Date.now() }); return scope; } export function allowedProjectIds(scope: UserScope): number[] | null { if (scope.allProjects) return null; return scope.projectIds; } export function assertProjectAccess(scope: UserScope, projectId: number | null | undefined): boolean { if (projectId == null || !Number.isFinite(projectId)) return false; if (scope.allProjects) return true; return scope.projectIds.includes(Number(projectId)); } export async function getUserScope(user: AuthUser): Promise { return await loadUserScope(user); } export function isOwnerAccountRow(row: { is_owner?: boolean; role_code?: string }): boolean { return Boolean(row.is_owner) || row.role_code === "tenant_admin"; }