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:
usersrepresents final mobile-app users.accountsrepresents 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
| Term | Description |
|---|---|
User | Final B2C customer using the Autimit mobile app |
User Profile | User-owned personal data such as name, phone, and optional CPF |
Auth Credential | Login credential used to authenticate a user |
Access Token | JWT used by the mobile app for authenticated API requests |
Refresh Token | Long-lived opaque session credential used to obtain new access tokens |
User Session | Per-device authenticated session backed by a hashed refresh token |
Email Verification Token | Single-use token used to verify ownership of an email address |
Password Reset Token | Single-use token used to authorize password replacement |
Payment Method | Tokenized card reference stored after gateway tokenization |
Active Payment Method | The payment method used for reservations and charging sessions |
User Model
Initial users table:
users
Required fields:
idemailpassword_hashnameemail_verified_atis_activecreated_atupdated_at
Optional fields:
phonecpf
Rules:
emailis unique case-insensitively.emailis stored normalized with trim and lowercase.password_hashstores only a password hash.namestores the user's full display name.email_verified_atis null until the email is verified.phoneis optional in the MVP.cpfis optional at signup and required only for invoice flows.is_active = falseblocks 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
bcryptwith cost12 - 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.
subis 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:
iduser_idrefresh_token_hashdevice_nameip_addressuser_agentlast_used_atexpires_atrevoked_atcreated_atupdated_at
Rules:
user_idreferencesusers.id.refresh_token_hashis unique.revoked_atnull 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:
iduser_idtoken_hashemailexpires_atused_atcreated_at
Rules:
user_idreferencesusers.id.token_hashis 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_atand requires a new verification.
Password Reset Model
Initial table:
password_reset_tokens
Required fields:
iduser_idtoken_hashexpires_atused_atcreated_at
Rules:
user_idreferencesusers.id.token_hashis unique.- Tokens are single use.
- Tokens expire after 30 minutes.
- Successful reset updates
password_hash. - Successful reset revokes all
user_sessionsfor 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:
| Provider | Fit | Tradeoff |
|---|---|---|
| Resend | Best initial developer experience for transactional auth emails | Newer provider than Postmark and SendGrid |
| Postmark | Strong transactional-email focus and mature deliverability tooling | Higher starting cost and less generous free tier |
| AWS SES | Lowest raw sending cost at scale | More setup, deliverability, monitoring, and production-access overhead |
| SendGrid | Mature, widely used, broad feature set | Heavier 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:
iduser_idgatewaygateway_customer_idgateway_payment_method_idbrandlast4exp_monthexp_yearis_activecreated_atupdated_at
Rules:
user_idreferencesusers.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
- Normalize email in register and login flows.
- Enforce the production password policy.
- Add
email_verified_attousers. - Add email verification token schema and endpoints.
- Add user session schema, refresh token rotation, and logout.
- Sign JWT access tokens with
sub,email,typ, and 15-minute expiration. - Update auth guard to re-read active users from the database.
- Add password reset schema and endpoints.
- Add
EmailProviderinterface and Resend implementation. - Add tests for register, duplicate register, email verification, login, refresh, logout, password reset, inactive user, and
GET /api/users/me. - Add
payment_methodsschema and endpoints in a separate PR.