feat: "itens sem permissão não aparecem" — filtro de menu por permission (secao 169)
getUserPermissionKeys(userId, tenantId?) (novo, packages/auth) devolve todas as permission keys do usuário no contexto atual, mesma query de userHasPermission só que retornando o conjunto inteiro. GET /auth/me agora inclui permissionKeys — leitura adicional só pra UX, nunca decisão de autorização (isso continua sendo o PermissionGuard em cada endpoint). NavLeaf/NavSection ganham campo opcional permission; todos os ~35 itens de menu (tenant e platform) anotados com a permission key mínima que o endpoint GET correspondente já exige. filterNavByPermissions() (novo, nav-types.ts) esconde o item, ou a seção inteira se nenhum filho sobrar. TenantSidebar/PlatformSidebar/*Topbar são client components que importam TENANT_NAV/PLATFORM_NAV direto (não recebem como prop do server) porque os ícones (LucideIcon, funções) não são serializáveis através da fronteira RSC — restrição já documentada desde a PHASE 23. O filtro roda dentro desses wrappers, client-side, recebendo só permissionKeys: string[] (serializável) como prop. Testado ponta a ponta com um supervisor de teste de verdade (convidado, senha trocada, logado via UI): menu perde Telefonia > Dialplan e a seção Administração inteira (nenhum permission bate). Confirmado que a autorização real continua valendo sem o item no menu: acesso direto a /app/administracao/usuarios continua batendo 403 no backend. Regressão: tenant_admin e platform_super_admin continuam vendo todo item do próprio menu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
@@ -30,6 +30,33 @@ export async function userHasPermission(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Todas as permission keys que `userId` tem no contexto de `tenantId`
|
||||
* (ou só as de escopo PLATFORM, se `tenantId` for omitido) — usado pelo
|
||||
* frontend pra filtrar o menu (agente.md secao 169: "itens sem permissão
|
||||
* não aparecem"), nunca pra decidir autorização de escrita (isso continua
|
||||
* sendo `userHasPermission`/`PermissionGuard` no backend, a única fonte
|
||||
* confiável — o menu é só UX, esconder um item não substitui o check).
|
||||
*/
|
||||
export async function getUserPermissionKeys(userId: string, tenantId?: string): Promise<string[]> {
|
||||
const prisma = getPrismaClient();
|
||||
|
||||
const where = tenantId
|
||||
? { userId, OR: [{ tenantId }, { tenantId: null }] }
|
||||
: { userId, tenantId: null };
|
||||
|
||||
const userRoles = await prisma.userRole.findMany({
|
||||
where,
|
||||
include: { role: { include: { rolePermissions: { include: { permission: true } } } } },
|
||||
});
|
||||
|
||||
const keys = new Set<string>();
|
||||
for (const userRole of userRoles) {
|
||||
for (const rp of userRole.role.rolePermissions) keys.add(rp.permission.key);
|
||||
}
|
||||
return Array.from(keys);
|
||||
}
|
||||
|
||||
/** Atalho: usuário tem QUALQUER role com scope PLATFORM (ex.: platform_super_admin). */
|
||||
export async function isPlatformUser(userId: string): Promise<boolean> {
|
||||
const prisma = getPrismaClient();
|
||||
|
||||
Reference in New Issue
Block a user