ADR 0003: Users And Auth Domain Model

Status: Proposed Date: 2026-05-19


Purpose

Define the Autimit user and authentication model for the MVP B2C mobile app.

This ADR covers end-user identity, login, session tokens, profile data, and the first payment-method boundary required before a user can start a charging session.


Context

The MVP serves final users charging electric vehicles at public Autimit stations.

The production rollout user flow requires:

  • email, full name, and password registration
  • one active authenticated user account per email
  • email verification before financial actions
  • long-lived mobile sessions without long-lived access tokens
  • password reset
  • one active payment method before starting or reserving a charging session
  • optional CPF at signup, required only for fiscal invoice flows

The current API already has a minimal users table and email/password login, but it does not yet model all product rules.


Decision

Autimit will model B2C users as first-class product identities, separate from station ownership accounts.

For the MVP:

  • users represents final mobile-app users.
  • accounts represents station/location ownership and is not used for B2C user tenancy.
  • User authentication starts with email and password.
  • Email is normalized before persistence and login lookup.
  • Passwords are never stored directly.
  • JWT access tokens are short lived.
  • Long-lived mobile sessions use refresh tokens stored as hashes.
  • Refresh tokens rotate on use and can be revoked per device.
  • Email verification is required before payment, reservation, and charging actions.
  • Password reset is part of the production auth foundation.
  • Protected requests must verify both token validity and that the user still exists and is active.
  • Payment methods are stored separately from users.
  • A user may have at most one active payment method in the MVP.
  • Request rate limiting is enforced by Autimit infrastructure at the reverse-proxy or edge layer.

Domain Terms

TermDescription
UserFinal B2C customer using the Autimit mobile app
User ProfileUser-owned personal data such as name, phone, and optional CPF
Auth CredentialLogin credential used to authenticate a user
Access TokenJWT used by the mobile app for authenticated API requests
Refresh TokenLong-lived opaque session credential used to obtain new access tokens
User SessionPer-device authenticated session backed by a hashed refresh token
Email Verification TokenSingle-use token used to verify ownership of an email address
Password Reset TokenSingle-use token used to authorize password replacement
Payment MethodTokenized card reference stored after gateway tokenization
Active Payment MethodThe payment method used for reservations and charging sessions

User Model

Initial users table:

users

Required fields:

  • id
  • email
  • password_hash
  • name
  • email_verified_at
  • is_active
  • created_at
  • updated_at

Optional fields:

  • phone
  • cpf

Rules:

  • email is unique case-insensitively.
  • email is stored normalized with trim and lowercase.
  • password_hash stores only a password hash.
  • name stores the user's full display name.
  • email_verified_at is null until the email is verified.
  • phone is optional in the MVP.
  • cpf is optional at signup and required only for invoice flows.
  • is_active = false blocks login and protected API usage.
  • Unverified users may authenticate, but cannot perform financial or charging actions.

Password Rules

Minimum password policy:

  • at least 8 characters
  • accepts at least 64 characters
  • rejects known weak or commonly used passwords
  • allows letters, numbers, symbols, spaces, and Unicode characters

The API should not require periodic password rotation or composition rules such as mandatory uppercase, number, or special character unless future compliance requirements demand it.

Password hashing:

  • use a slow password hashing algorithm supported by the runtime
  • current implementation may use bcrypt with cost 12
  • password hashes are never returned by the API

Authentication Flow

sequenceDiagram
    autonumber
    actor User
    participant App as Mobile App
    participant API as Autimit API
    participant DB as PostgreSQL
    participant Email as Email Provider

    User->>App: Create account
    App->>API: POST /api/auth/register
    API->>API: Normalize email and validate password
    API->>DB: Create user with email_verified_at = null
    API->>DB: Store hashed email verification token
    API->>DB: Store hashed refresh token in user_sessions
    API->>Email: Send verification email
    API-->>App: user + access token + refresh token

    User->>App: Open verification link
    App->>API: POST /api/auth/verify-email
    API->>DB: Mark verification token used
    API->>DB: Set users.email_verified_at
    API-->>App: verified user

    App->>API: Authenticated API request
    API->>API: Verify short-lived access token
    API->>DB: Re-read active user
    API-->>App: Protected resource

    App->>API: POST /api/auth/refresh
    API->>DB: Validate hashed refresh token
    API->>DB: Revoke previous refresh token
    API->>DB: Store rotated refresh token
    API-->>App: new access token + new refresh token

    User->>App: Forgot password
    App->>API: POST /api/auth/password/forgot
    API->>DB: Store hashed password reset token
    API->>Email: Send password reset email
    API-->>App: generic ok response

    User->>App: Submit new password
    App->>API: POST /api/auth/password/reset
    API->>DB: Mark reset token used
    API->>DB: Update password hash
    API->>DB: Revoke all user sessions
    API-->>App: ok

Registration:

POST /api/auth/register

Request:

{
  "email": "user@example.com",
  "password": "Str0ng-pass",
  "name": "User Name"
}

Rules:

  • Normalize email.
  • Reject weak passwords.
  • Reject existing email.
  • Create active but unverified user.
  • Create an email verification token.
  • Send verification email out of band.
  • Return public user data, a short-lived access token, and a refresh token.

Login:

POST /api/auth/login

Rules:

  • Normalize email before lookup.
  • Reject missing, inactive, or password-mismatched users with the same unauthorized response shape.
  • Return public user data, a short-lived access token, and a refresh token.

Email verification:

POST /api/auth/verify-email
POST /api/auth/resend-verification

Rules:

  • Verification tokens are single use.
  • Verification tokens are stored only as hashes.
  • Verification tokens expire after 24 hours.
  • Resend does not reveal whether an email belongs to an account.
  • Resend cooldown and request limits are enforced by infrastructure.

Password reset:

POST /api/auth/password/forgot
POST /api/auth/password/reset

Rules:

  • Forgot-password responses must be identical whether the email exists or not.
  • Reset tokens are single use.
  • Reset tokens are stored only as hashes.
  • Reset tokens expire after 30 minutes.
  • Successful password reset revokes all existing user sessions.
  • The user is not automatically logged in after password reset.

Session refresh:

POST /api/auth/refresh
POST /api/auth/logout

Rules:

  • Refresh accepts only valid, unexpired, unrevoked refresh tokens.
  • Refresh rotates the refresh token.
  • Reuse of a revoked or already-rotated refresh token revokes the session family.
  • Logout revokes the current session.

Current user:

GET /api/users/me
PATCH /api/users/me

Rules:

  • Require a valid access token.
  • Re-read the user from the database.
  • Reject inactive users even if the token is still cryptographically valid.

Token Model

Access token JWT:

{
  "sub": "user_id",
  "email": "user@example.com",
  "typ": "access"
}

Rules:

  • Access token lifetime is 15 minutes.
  • sub is the canonical user identifier.
  • Token claims are not treated as the source of truth for user status.
  • API authorization must not rely only on token payload data.

Refresh token:

  • opaque random token
  • stored client-side by the mobile app in secure OS storage
  • stored server-side only as a hash
  • absolute lifetime is 30 days
  • idle lifetime is 7 days
  • rotated whenever used
  • revocable per device/session

Initial user_sessions table:

user_sessions

Required fields:

  • id
  • user_id
  • refresh_token_hash
  • device_name
  • ip_address
  • user_agent
  • last_used_at
  • expires_at
  • revoked_at
  • created_at
  • updated_at

Rules:

  • user_id references users.id.
  • refresh_token_hash is unique.
  • revoked_at null means the session can still be used if not expired.
  • Password reset revokes all sessions for the user.
  • User deactivation revokes all sessions for the user.

Email Verification Model

Initial table:

user_email_verifications

Required fields:

  • id
  • user_id
  • token_hash
  • email
  • expires_at
  • used_at
  • created_at

Rules:

  • user_id references users.id.
  • token_hash is unique.
  • Tokens are single use.
  • Tokens expire after 24 hours.
  • Verifying the email sets users.email_verified_at.
  • Changing a user's email clears email_verified_at and requires a new verification.

Password Reset Model

Initial table:

password_reset_tokens

Required fields:

  • id
  • user_id
  • token_hash
  • expires_at
  • used_at
  • created_at

Rules:

  • user_id references users.id.
  • token_hash is unique.
  • Tokens are single use.
  • Tokens expire after 30 minutes.
  • Successful reset updates password_hash.
  • Successful reset revokes all user_sessions for that user.

Request Rate Limiting

Rate limiting is owned by Autimit infrastructure, not by the application domain model.

The reverse proxy or edge layer must rate limit at least:

  • registration
  • login
  • email verification resend
  • password reset request
  • token refresh

The API must still use safe response shapes:

  • login failure does not reveal whether email or password was wrong
  • forgot-password does not reveal whether the email exists
  • resend-verification does not reveal whether the email exists
  • token values are never logged

Email Delivery

Autimit will send authentication emails through a transactional email provider behind an internal EmailProvider interface.

Initial provider decision:

  • Use Resend for the first production implementation.
  • Keep email templates and provider calls isolated from auth domain services.
  • Store only token hashes in the database.
  • Send only links with raw tokens to the user's email address.
  • Do not log raw verification or password reset tokens.

Reasons:

  • Resend has a small developer-facing API and official SDKs.
  • Resend supports REST API, SMTP relay, webhooks, domain authentication, and React Email templates.
  • Resend's free tier is enough for low-volume development and early rollout.
  • Postmark remains the preferred fallback if deliverability becomes more important than developer workflow.
  • AWS SES remains a later cost-optimization option if volume grows and Autimit is ready to own more deliverability operations.

Initial provider comparison:

ProviderFitTradeoff
ResendBest initial developer experience for transactional auth emailsNewer provider than Postmark and SendGrid
PostmarkStrong transactional-email focus and mature deliverability toolingHigher starting cost and less generous free tier
AWS SESLowest raw sending cost at scaleMore setup, deliverability, monitoring, and production-access overhead
SendGridMature, widely used, broad feature setHeavier product surface and less focused developer experience

Initial required environment variables:

EMAIL_PROVIDER=resend
RESEND_API_KEY=...
EMAIL_FROM=Autimit <no-reply@autimit.com>
APP_PUBLIC_URL=https://app.autimit.com

Implementation rules:

  • Auth services create token records and call an email service port.
  • The email service builds verification and reset URLs.
  • Provider failures should be observable and retriable.
  • Registration must not return raw email verification tokens in production.
  • Forgot-password must always return the same public response, even if sending fails internally.
  • Local development may log or return test tokens only when NODE_ENV != production.

Payment Method Boundary

Payment methods are not stored in users.

Initial table:

payment_methods

Required fields:

  • id
  • user_id
  • gateway
  • gateway_customer_id
  • gateway_payment_method_id
  • brand
  • last4
  • exp_month
  • exp_year
  • is_active
  • created_at
  • updated_at

Rules:

  • user_id references users.id.
  • The API stores only tokenized gateway identifiers and display metadata.
  • The API never receives or stores full card numbers or CVV.
  • A user may have only one active payment method in the MVP.
  • Adding a payment method requires verified email.
  • Starting or reserving a charging session requires an active payment method.

API Surface

Initial user/auth endpoints:

POST /api/auth/register
POST /api/auth/login
POST /api/auth/refresh
POST /api/auth/logout
POST /api/auth/verify-email
POST /api/auth/resend-verification
POST /api/auth/password/forgot
POST /api/auth/password/reset
GET /api/users/me
PATCH /api/users/me

Initial payment endpoints:

POST /api/payment-methods
GET /api/payment-methods/me
DELETE /api/payment-methods/:id

Payment endpoint details may be refined when the gateway provider is selected.


Out Of Scope

Not part of the first user/auth implementation:

  • social login
  • magic links
  • multi-factor authentication
  • admin user management
  • organization membership
  • fleet user roles

Multi-factor authentication may become required later for admin and fleet roles, but it is not part of the B2C MVP user rollout.


Consequences

Benefits:

  • Simple B2C user model aligned with the mobile MVP.
  • Clear split between final users and station ownership accounts.
  • Mobile users can remain signed in without making access tokens long lived.
  • Email ownership is verified before any financial or charging action.
  • Payment readiness can be checked without overloading the user table.
  • Inactive users can be blocked immediately even with old tokens.
  • Password reset has clear token and session revocation rules.

Tradeoffs:

  • More tables and token lifecycle logic in the first production-ready auth implementation.
  • Email delivery becomes a dependency of user activation.
  • No organization roles in the MVP user model.
  • Infrastructure must provide reliable request rate limiting for auth endpoints.

Implementation Plan

  1. Normalize email in register and login flows.
  2. Enforce the production password policy.
  3. Add email_verified_at to users.
  4. Add email verification token schema and endpoints.
  5. Add user session schema, refresh token rotation, and logout.
  6. Sign JWT access tokens with sub, email, typ, and 15-minute expiration.
  7. Update auth guard to re-read active users from the database.
  8. Add password reset schema and endpoints.
  9. Add EmailProvider interface and Resend implementation.
  10. Add tests for register, duplicate register, email verification, login, refresh, logout, password reset, inactive user, and GET /api/users/me.
  11. Add payment_methods schema and endpoints in a separate PR.