ADR-014 — User Entity, Roles and Avatar System
ADR-014 — User Entity, Roles and Avatar System
Sección titulada «ADR-014 — User Entity, Roles and Avatar System»Accepted
Context
Sección titulada «Context»Coveris needs one user model serving both authentication identity (SimpleJWT local, Cognito prod) and business permissions. It must be OIDC-compatible so Cognito migration requires no schema or API changes.
sub (UUID) is the permanent OIDC identifier — it never changes. email is USERNAME_FIELD but can change. given_name and family_name replicate OIDC claims for direct Cognito token ingestion.
Five hierarchical roles are needed. Per-object RBAC (django-guardian) was rejected — MVP does not require it.
The UI (topbar, control panel, audit timeline) needs an avatar. S3 upload is out of scope for MVP. Instead: initials + deterministic color from sub, zero backend changes, WCAG AA 4.5:1. All needed data already flows from GET /api/v1/auth/me/.
Decision
Sección titulada «Decision»User model. backend/apps/users/models.py extends AbstractBaseUser: sub (UUIDField, auto, non-editable, unique), email (EmailField, USERNAME_FIELD), given_name / family_name (CharField, max 255), role (CharField with choices), email_verified (BooleanField, default False), is_active, is_staff, date_joined. No other profile data in this table; extensions go in satellite tables.
Roles. Five roles on the role field, progressively restricted:
ADMIN— full access; manages users, org units, and all system configuration.MANAGER— manages org units, employees, and assignments within their scope.SUPERVISOR— read plus limited write restricted to their operational unit.VIEWER— read-only across the entire application.EMPLOYEE— reserved for future self-service; no functional use in MVP.
Default role: VIEWER. Escalation only via Django admin or an ADMIN-only endpoint.
User Avatar System. p-avatar renders [image] post-MVP; for MVP all users show [label] with shape="circle" (initials + color).
Initials (getInitials, frontend/src/app/shared/avatar-utils.ts): first letter of given_name + family_name; if only given_name, its first two chars; if neither, pi pi-user. Reactive to name changes.
Color (getAvatarColor, same file): first 8 hex digits of sub (no hyphens) mod 8 over palette — Blue #1e88e5, Green #43a047, Red #e53935, Purple #8e24aa, Orange #fb8c00, Cyan #00acc1, Indigo #3949ab, Pink #d81b60. All pass WCAG AA 4.5:1 with white text. Color is stable because sub is immutable ([[adr-008-authentication|ADR-008]]).
Consumers: app.topbar.ts, control-panel.component.ts, audit-log timeline (getInitialsFromFullName for non-system employee names).
User lifecycle. Creation via Django admin (MVP); self-registration deferred. Deactivation is soft-delete (is_active = False); no physical deletes in MVP. GDPR erasure (Art. 17) and portability (Art. 20) are authentication endpoints. Auth flow: [[adr-008-authentication|ADR-008]].
Image avatar (Option B, deferred). When S3 is available, add avatar_url (URLField, nullable) to User, PATCH /api/v1/auth/me/avatar/, and a migration. p-avatar switches from [label] to [image]; initials remain the permanent fallback.
Consequences
Sección titulada «Consequences»Model is OIDC-aligned and Cognito-ready: switching to Cognito requires only configuring the JWT validator, no schema changes. sub keeps all foreign references stable regardless of email changes.
All avatar logic is frontend-only (purely presentational). No new endpoints, migrations, or security surface for MVP.
Initials + color is the permanent fallback — there is never a “no avatar” state in the UI.
Option B requires a migration, a new endpoint in API.md, and a superseding ADR. The nullable avatar_url field ensures backward compatibility.
EMPLOYEE is a model-only reserve in MVP; activating it requires an explicit ADR.