Add decisiones de autorización puras
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
33
mvp/b2c/src/lib/auth/authz.ts
Normal file
33
mvp/b2c/src/lib/auth/authz.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
40
mvp/b2c/tests/auth/authz.test.ts
Normal file
40
mvp/b2c/tests/auth/authz.test.ts
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user