Skip to content

Authentication

kneshi edited this page May 7, 2026 · 1 revision

Authentication & User Roles

Authentication

  • Email + password, session-based via Redis
  • Password storage: bcrypt hash
  • Session: HttpOnly cookie, 7-day expiry

Signup Flow

Public signup is bootstrap-only: it works exactly once, to create the first administrator on a fresh install. After the first user exists, POST /api/auth/signup returns 410 Gone and the /signup page renders a "Signup is closed" card. The "Create account" link on /login is also hidden once the system is bootstrapped (the frontend learns this from GET /api/config's bootstrapAvailable field).

  1. Validate: first name + last name required, email required, password 12–128 characters with at least 2 of {lowercase, uppercase, digit, symbol}
  2. Check email uniqueness
  3. First user only: created as admin, approved = true, auto-logged in. All later attempts are rejected with 410.

Inviting users

After bootstrap, new users come in via admin invitation. The reset URL is always returned to the inviting admin so out-of-band sharing works even when SMTP is broken; when SMTP is enabled, the invitee also receives an email with the same link.

  1. An admin opens /users and clicks Invite user
  2. The admin fills in email + role; the invitee's first/last name are NOT collected here
  3. The backend creates the user with a disabled bcrypt-hashed random password, empty placeholder names, and issues a one-time reset URL (60-minute TTL). Audit entry user.invited is written before the token is issued.
  4. The response always includes the resetUrl (the admin sees it in the ResetUrlDialog with a Copy button) and an emailed: boolean flag indicating whether the user-invite email was actually dispatched. With SMTP_ENABLED=true (default), the invitee receives the link; with SMTP off, only the admin sees it and shares it manually (chat, signal, in person, sealed envelope).
  5. The invitee opens the URL and, on the same /reset-password page, fills in their first name, last name, and a new password. They can then log in with their assigned role.

Invited users skip the legacy "pending approval" state, they are approved = true from creation. The same ResetUrlDialog component powers both this flow and the admin password-reset flow, so the look-and-feel is identical.

The locale of the invite email follows the inviting admin's Accept-Language header, with MAIL_DEFAULT_LOCALE (en/fr) as the fallback.

Login Flow

  1. Look up user by email
  2. Verify password against bcrypt hash
  3. Create session in Redis (7-day expiry)
  4. Set HttpOnly cookie
  5. Approved users redirect to / (dashboard), unapproved to /pending

Forgotten password

The user-initiated reset flow depends on SMTP_ENABLED (default true). The frontend learns the current mode from smtpEnabled on GET /api/config.

SMTP enabled. /login shows a "Forgot password?" link. The /forgot-password page accepts an email; POST /api/auth/forgot-password issues a one-time reset token and emails the link (60-minute TTL). Successful password changes also trigger a confirmation email.

SMTP disabled. The public reset path closes:

  • The "Forgot password?" link is hidden on /login.
  • /forgot-password renders a "contact your administrator" notice (no form).
  • POST /api/auth/forgot-password returns 410 Gone with { error: 'smtp_disabled' }.
  • No confirmation emails are sent on password changes.

In SMTP-disabled mode, recovery is admin-driven; see Production - Recovering a forgotten password for the admin-issued (/users -> Reset password) and CLI (pnpm --filter backend password:reset) paths.

Admin-issued password reset (SMTP-aware)

Admins can reset another user's password from /users regardless of SMTP. Like invites, the response always includes resetUrl (shown to the admin) plus emailed: boolean. With SMTP enabled, the target user also receives an admin-password-reset email with the same link. With SMTP disabled, only the admin sees the URL and shares it out-of-band. Admins cannot use this endpoint on their own account.

Roles & Permissions

The Prisma Role enum has five values. ADMIN / DPO / EDITOR are the primary write tiers; PROCESS_OWNER and AUDITOR are narrower functional roles, with AUDITOR serving as the default read-only tier.

Capability Admin DPO Editor Process Owner Auditor
View dashboard, register, checklist, recitals, violations Yes Yes Yes Yes Yes
Create/edit treatments Yes Yes Yes Yes -
Create/edit violations Yes Yes Yes - -
Respond to checklist items Yes Yes Yes - -
Validate/invalidate treatments Yes Yes - - -
Delete treatments Yes Yes - - -
Export treatments as CSV Yes Yes - - Yes
View audit log Yes Yes - - Yes
Access DSR records (read/write) Yes Yes - - -
Manage users (approve, change role, deactivate) Yes - - - -
Edit organization settings Yes - - - -

PROCESS_OWNER : scoped to treatment create/update only. Cannot validate, cannot touch violations or other modules.

AUDITOR : read + export only. Never writes. Intended for third-party compliance reviews.

Self-management restriction: admins cannot change their own role, deactivate themselves, or admin-reset their own password. The corresponding endpoints return 403 Forbidden. This is enforced server-side regardless of UI state. To recover the sole admin's password without another admin available, see Production - Recovering a forgotten password.

Clone this wiki locally