mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 12:43:18 +00:00
- Add IAM v2 auth with role_id, is_owner, status checks, and API key without tenant_admin bypass - Enforce CRUD permissions and project/warehouse scope across API routes - Rewrite usuarios.vue with ribbon, tree, user/role management, and permission matrix - Add dynamic menu, route guards, usePermissions/useScope composables - Apply role templates on create; filter project docs by category; hide cost tab without permission - Add iam_test unit tests; update bootstrap and SaaS tenant admin to use role_id Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
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<number, { scope: UserScope; at: number }>();
|
|
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<UserScope> {
|
|
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<UserScope> {
|
|
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";
|
|
}
|