Files
mercadodevida/project/src/shared/auth.ts
2026-08-21 21:55:43 +02:00

45 lines
1.5 KiB
TypeScript

/**
* Shared auth contracts. Identity implements the authenticator; consumers
* (users, future modules) receive it by injection from the composition root.
* No module ever imports another module for auth.
*/
import type { FastifyRequest } from 'fastify';
import { AppError } from './errors.js';
export type Role = 'customer' | 'admin' | 'editor' | 'pos_cashier' | 'pos_manager';
export interface CurrentUser {
id: string;
email: string;
role: Role;
}
/**
* Resolves the session on the request into the current user.
* Throws AppError(401) when the request is not authenticated.
*/
export type Authenticate = (request: FastifyRequest) => Promise<CurrentUser>;
/** Throws AppError(403) unless the user holds the required role. */
export function requireRole(user: CurrentUser, role: Role): void {
if (user.role !== role) {
throw new AppError(403, 'FORBIDDEN', 'Access denied');
}
}
/** Throws AppError(403) unless the user holds at least one of the allowed roles.
* Use when an endpoint accepts multiple roles (e.g. POS-002 endpoints accept
* `pos_cashier`, `pos_manager`, and `admin`). */
export function requireAnyRole(user: CurrentUser, roles: ReadonlyArray<Role>): void {
if (!roles.includes(user.role)) {
throw new AppError(403, 'FORBIDDEN', 'Access denied');
}
}
/** Throws AppError(403) unless the user is the resource owner or an admin. */
export function requireOwnerOrAdmin(user: CurrentUser, ownerId: string): void {
if (user.role !== 'admin' && user.id !== ownerId) {
throw new AppError(403, 'FORBIDDEN', 'Access denied');
}
}