panels-origin/web-panel/middleware/auth.global.ts
Cursor Agent aed2f4531b
Implement client IAM v2: roles per tenant, CRUD matrix, scope, and panel UI
- 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>
2026-09-08 18:16:59 +00:00

67 lines
1.9 KiB
TypeScript

const ROUTE_PERMISSIONS: Record<string, string> = {
"/": "reports.view",
"/proyectos": "projects.view",
"/obras": "projects.view",
"/presupuesto": "budget.view",
"/programa-obra": "work_program.view",
"/control-presupuesto": "cost_control.view",
"/gastos": "expenses.view",
"/almacen": "warehouse.view",
"/kanban": "kanban.view",
"/padron": "workers.view",
"/gafetes": "documents.view",
"/nomina": "payroll.view",
"/configuracion": "settings.view",
"/usuarios": "users.view",
};
export default defineNuxtRouteMiddleware(async (to) => {
if (to.path === "/login") return;
const { user, fetchMe } = useAuth();
const { api } = useApi();
try {
if (!user.value) {
await Promise.race([
fetchMe(),
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 4000)),
]);
}
} catch {
user.value = null;
}
if (!user.value) return navigateTo("/login");
if (user.value.role_code === "platform_admin" && user.value.realm === "platform") {
user.value = null;
try {
await api("/v1/auth/logout", { method: "POST" });
} catch {
/* ignore */
}
if (import.meta.client) {
window.location.href = "http://localhost:3001/login";
return abortNavigation();
}
return navigateTo("/login");
}
if (user.value.must_change_password && to.path !== "/cambiar-password") {
return navigateTo("/cambiar-password");
}
if (!user.value.must_change_password && to.path === "/cambiar-password") {
return navigateTo("/");
}
const perms = usePermissions();
const userScope = useScope();
if (!perms.loaded.value) await perms.load();
if (!userScope.loaded.value) await userScope.load();
const required = Object.entries(ROUTE_PERMISSIONS).find(([path]) =>
path === "/" ? to.path === "/" : to.path === path || to.path.startsWith(`${path}/`),
)?.[1];
if (required && !perms.can(required)) {
return navigateTo("/");
}
});