Files
mercadodevida/work/artifacts/F-006/architect.md
rikrdo 546971280f 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
2026-08-15 09:28:15 +02:00

1.6 KiB

Architect — F-006 Users: profile, addresses, RBAC

done -> work/artifacts/F-006/architect.md

Deliverables

  • specs/F-006-users-rbac/SPEC.md, DESIGN.md, TASKS.md, TESTS.md

Key decisions

  1. Cross-module auth por inyección, no por imports: R1 prohíbe que users importe identity. identity exporta createSessionAuthenticator por su index; el composition root lo crea y lo inyecta en registerUsersRoutes. El contrato (CurrentUser, Authenticate, requireRole) vive en src/shared/auth.ts.
  2. Role en identity_users (migración 003): fuente única de verdad de autorización, viaja con el usuario autenticado, imposible de escalar vía API (register siempre crea 'customer'; solo DB/migración cambia roles).
  3. Authz antes que existencia: no-owner recibe 403 sin importar si el recurso existe (AC1 literal; sin enumeración).
  4. Tablas users_profiles / users_addresses con FK a identity_users solo como integridad de schema; queries runtime tocan únicamente tablas users_* (regla de prefijo intacta).
  5. Admin endpoint dentro de users: GET /users lista profiles (tabla propia). Nada obliga a users a leer identity_users en runtime.
  6. Cero dependencias nuevas.

Security posture

  • Session resolution server-side (hash de token, expiración y revocación en SQL).
  • Owner-or-admin en cada ruta; admin-only con requireRole.
  • Role jamás proviene del cliente: sale del JOIN sessions+users en DB.

Risks

  • Profiles lazy (no auto-create en register): GET /users lista solo profiles existentes. Aceptado para el slice; documentado.