feat: implement authentication and RBAC
- packages/auth: Argon2id password hashing, JWT access tokens (jose), opaque refresh tokens with rotation, generic error messages (no user-enumeration via timing or message differences) - roles/permissions/role_permissions/user_roles/sessions/audit_logs schema (agente.md secoes 142-150); RBAC scope PLATFORM vs TENANT - withUserContext(): narrow RLS exception so a user can discover their own tenant_memberships before a tenant is chosen (login flow) - userHasPermission()/isPlatformUser(): explicit service-layer RBAC checks (roles/permissions tables are not RLS-protected — documented why in docs/AUTHENTICATION.md) - seed: permission catalog, 4 system roles, initial Platform Super Admin (password written once to FIRST_LOGIN.txt, 600, outside Git) - automated end-to-end test: login, RBAC check, refresh rotation, logout
This commit is contained in:
40
packages/auth/src/permissions.ts
Normal file
40
packages/auth/src/permissions.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { getPrismaClient } from "@b2bcall/database";
|
||||
|
||||
/**
|
||||
* Verifica se `userId` tem a permissão `permissionKey`, considerando roles
|
||||
* PLATFORM (tenantId nulo, valem em qualquer contexto) e roles TENANT
|
||||
* atribuídas especificamente em `tenantId` (agente.md secao 144: role scope
|
||||
* PLATFORM ou TENANT — nunca escopo global acidental).
|
||||
*
|
||||
* Não usa RLS (ver comentário na migration auth_and_rbac): a checagem é
|
||||
* feita explicitamente aqui, na camada de serviço confiável.
|
||||
*/
|
||||
export async function userHasPermission(
|
||||
userId: string,
|
||||
permissionKey: string,
|
||||
tenantId?: string,
|
||||
): Promise<boolean> {
|
||||
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 } } } } },
|
||||
});
|
||||
|
||||
return userRoles.some((userRole) =>
|
||||
userRole.role.rolePermissions.some((rp) => rp.permission.key === permissionKey),
|
||||
);
|
||||
}
|
||||
|
||||
/** Atalho: usuário tem QUALQUER role com scope PLATFORM (ex.: platform_super_admin). */
|
||||
export async function isPlatformUser(userId: string): Promise<boolean> {
|
||||
const prisma = getPrismaClient();
|
||||
const count = await prisma.userRole.count({
|
||||
where: { userId, tenantId: null, role: { scope: "PLATFORM" } },
|
||||
});
|
||||
return count > 0;
|
||||
}
|
||||
Reference in New Issue
Block a user