diff --git a/mvp/b2c/src/lib/auth/authz.ts b/mvp/b2c/src/lib/auth/authz.ts new file mode 100644 index 0000000..997b19b --- /dev/null +++ b/mvp/b2c/src/lib/auth/authz.ts @@ -0,0 +1,33 @@ +export type Role = 'reformista' | 'admin'; +export type UserStatus = 'activo' | 'deshabilitado'; + +export type AuthUser = { + id: string; + email: string; + nombre: string | null; + role: Role; + tenantId: string | null; + status: UserStatus; +}; + +export function isAdmin(user: AuthUser): boolean { + return user.role === 'admin'; +} + +export function assertAdmin(user: AuthUser | null): asserts user is AuthUser { + if (!user || user.role !== 'admin') { + throw new Error('Acceso restringido a administradores.'); + } +} + +export function resolveTenantId(user: AuthUser | null): string { + if (!user || !user.tenantId) { + throw new Error('El usuario no tiene un tenant asociado.'); + } + return user.tenantId; +} + +export function canAccessTenant(user: AuthUser, tenantId: string): boolean { + if (user.role === 'admin') return true; + return user.tenantId === tenantId; +} diff --git a/mvp/b2c/tests/auth/authz.test.ts b/mvp/b2c/tests/auth/authz.test.ts new file mode 100644 index 0000000..a47cbd3 --- /dev/null +++ b/mvp/b2c/tests/auth/authz.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest'; +import { + isAdmin, + assertAdmin, + resolveTenantId, + canAccessTenant, + type AuthUser, +} from '@/lib/auth/authz'; + +const reformista: AuthUser = { + id: 'u1', email: 'r@x.com', nombre: 'R', role: 'reformista', tenantId: 't1', status: 'activo', +}; +const admin: AuthUser = { + id: 'u2', email: 'a@x.com', nombre: 'A', role: 'admin', tenantId: null, status: 'activo', +}; + +describe('authz', () => { + it('isAdmin distingue roles', () => { + expect(isAdmin(admin)).toBe(true); + expect(isAdmin(reformista)).toBe(false); + }); + + it('assertAdmin lanza si no es admin o es null', () => { + expect(() => assertAdmin(admin)).not.toThrow(); + expect(() => assertAdmin(reformista)).toThrow(); + expect(() => assertAdmin(null)).toThrow(); + }); + + it('resolveTenantId devuelve el tenant del reformista y lanza para admin/null', () => { + expect(resolveTenantId(reformista)).toBe('t1'); + expect(() => resolveTenantId(admin)).toThrow(); + expect(() => resolveTenantId(null)).toThrow(); + }); + + it('canAccessTenant: reformista solo el suyo, admin cualquiera', () => { + expect(canAccessTenant(reformista, 't1')).toBe(true); + expect(canAccessTenant(reformista, 't2')).toBe(false); + expect(canAccessTenant(admin, 't2')).toBe(true); + }); +});