/** * 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; /** 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): 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'); } }