feat(F-006): users profile, addresses and RBAC
- users module: profile + address CRUD behind use cases (users_profiles, users_addresses) - roles customer/admin on identity_users; role resolved from DB per request - shared auth contract (Authenticate, requireRole, requireOwnerOrAdmin) injected from composition root; users never imports identity - authorization runs before existence checks; address SQL scoped by user_id - @fastify/cookie registered once at app root (cross-module) - migrations 003_identity_roles + 004_users (reversible) - no new npm dependencies; tests: unit 52, integration 22 Gates: reviewer/security/qa APPROVED; verify.sh green
This commit is contained in:
35
project/src/shared/auth.ts
Normal file
35
project/src/shared/auth.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
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 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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user