Ajuste rotas, Menus, Layout, Permissãoes UserRoleGuard

This commit is contained in:
Leonardo
2026-02-24 12:04:59 -03:00
parent b1c0cb47c0
commit d58dc21297
15 changed files with 1925 additions and 259 deletions

View File

@@ -11,6 +11,9 @@ import { buildUpgradeUrl } from '@/utils/upgradeContext'
import { sessionUser, sessionReady, sessionRefreshing, initSession } from '@/app/session'
// ✅ separa RBAC (papel) vs Plano (upgrade)
import { denyByRole, denyByPlan } from '@/router/accessRedirects'
// cache simples (evita bater no banco em toda navegação)
let sessionUidCache = null
@@ -234,14 +237,14 @@ export function applyGuards (router) {
if (!tenant.activeTenantId) {
const mem = Array.isArray(tenant.memberships) ? tenant.memberships : []
// 1) tenta casar role da rota (ex.: therapist) com membership
const wantedRoles = Array.isArray(to.meta?.roles) ? to.meta.roles : []
const preferred = wantedRoles.length
? mem.find(m => m && m.status === 'active' && m.tenant_id && wantedRoles.includes(m.role))
: null
// 1) tenta casar role da rota (ex.: therapist) com membership
const wantedRoles = Array.isArray(to.meta?.roles) ? to.meta.roles : []
const preferred = wantedRoles.length
? mem.find(m => m && m.status === 'active' && m.tenant_id && wantedRoles.includes(m.role))
: null
// 2) fallback: primeiro active
const firstActive = preferred || mem.find(m => m && m.status === 'active' && m.tenant_id)
// 2) fallback: primeiro active
const firstActive = preferred || mem.find(m => m && m.status === 'active' && m.tenant_id)
if (!firstActive) {
if (to.path === '/pages/access') { console.timeEnd(tlabel); return true }
@@ -299,42 +302,60 @@ export function applyGuards (router) {
}
}
// roles guard (plural)
// Se a rota pede roles específicas e o role ativo não bate,
// tenta ajustar o activeRole dentro do mesmo tenant (se houver membership compatível).
const allowedRoles = Array.isArray(to.meta?.roles) ? to.meta.roles : null
if (allowedRoles && allowedRoles.length && !allowedRoles.includes(tenant.activeRole)) {
const mem = Array.isArray(tenant.memberships) ? tenant.memberships : []
const compatible = mem.find(m =>
m && m.status === 'active' && m.tenant_id === tenantId && allowedRoles.includes(m.role)
)
if (compatible) {
// muda role ativo para o compatível
tenant.activeRole = compatible.role
}
}
// ------------------------------------------------
// ✅ RBAC (roles) — BLOQUEIA se não for compatível
//
// Importante:
// - Isso é "papel": se falhar, NÃO é caso de upgrade.
// - Só depois disso checamos feature/plano.
// ------------------------------------------------
const allowedRoles = Array.isArray(to.meta?.roles) ? to.meta.roles : null
if (allowedRoles && allowedRoles.length && !allowedRoles.includes(tenant.activeRole)) {
const mem = Array.isArray(tenant.memberships) ? tenant.memberships : []
const compatible = mem.find(m =>
m && m.status === 'active' && m.tenant_id === tenantId && allowedRoles.includes(m.role)
)
if (compatible) {
// muda role ativo para o compatível (mesmo tenant)
tenant.activeRole = compatible.role
} else {
// 🔥 aqui era o "furo": antes ajustava se achasse, mas se não achasse, deixava passar.
console.timeEnd(tlabel)
return denyByRole({ to, currentRole: tenant.activeRole })
}
}
// role guard (singular) - mantém compatibilidade
const requiredRole = to.meta?.role
if (requiredRole && tenant.activeRole !== requiredRole) {
const fallback = roleToPath(tenant.activeRole)
if (to.path === fallback) { console.timeEnd(tlabel); return { path: '/pages/access' } }
// RBAC singular também é "papel" → cai fora (não é upgrade)
console.timeEnd(tlabel)
return { path: fallback }
return denyByRole({ to, currentRole: tenant.activeRole })
}
// feature guard (entitlements/plano → upgrade)
// ------------------------------------------------
// ✅ feature guard (entitlements/plano → upgrade)
//
// Aqui sim é caso de upgrade:
// - o usuário "poderia" usar, mas o plano do tenant não liberou.
// ------------------------------------------------
const requiredFeature = to.meta?.feature
if (requiredFeature && ent?.can && !ent.can(requiredFeature)) {
if (to.name === 'upgrade') { console.timeEnd(tlabel); return true }
if (requiredFeature && ent?.can && !ent.can(requiredFeature)) {
// evita loop
if (to.name === 'upgrade') { console.timeEnd(tlabel); return true }
const url = buildUpgradeUrl({
missingKeys: [requiredFeature],
redirectTo: to.fullPath
})
console.timeEnd(tlabel)
return url
}
// Mantém compatibilidade com seu fluxo existente (buildUpgradeUrl)
const url = buildUpgradeUrl({
missingKeys: [requiredFeature],
redirectTo: to.fullPath
})
// Se quiser padronizar no futuro, você pode trocar por:
// return denyByPlan({ to, missingFeature: requiredFeature, redirectTo: to.fullPath })
console.timeEnd(tlabel)
return url
}
console.timeEnd(tlabel)
return true