SPEC 001 — Authentication
| Field | Value |
|---|---|
| Status | DRAFT |
| Priority | P0 — Launch-Critical |
| Backend | equa-server/modules/auth/src/ |
| Frontend | equa-web/src/modules/auth/, equa-web/src/modules/landing/ |
1. Feature Purpose
Authentication gates every action in the Equa platform. It provides three sign-in methods (password, Google OAuth, magic link), enforces email verification before access, supports optional two-factor authentication, and maintains rolling server-side sessions. Authorization is layered on top via RBAC with per-site read/write permission checks.2. Current State (Verified)
2.1 Password Login
| Detail | Value |
|---|---|
| File | equa-server/modules/auth/src/authentication.ts |
| Hash algorithm | bcryptjs, 10 salt rounds |
| 2FA | Supported (TOTP via twoFactorSecret) |
| Email verification | Required before login succeeds |
| Session binding | session.user = result.user.id (line 72) |
2.2 Google OAuth
| Detail | Value |
|---|---|
| File | equa-server/modules/auth/src/google-auth.ts |
| Token types | Google ID tokens + access tokens |
| Auto-registration | Yes — creates user on first Google sign-in |
| Email verification | Validated from Google profile |
| Session binding | session.user = user.id (line 88) |
2.3 Magic Link
| Detail | Value |
|---|---|
| File | equa-server/modules/auth/src/magic-link.ts |
| Token | 32-byte random hex |
| Expiry | 15 minutes |
| Anti-enumeration | Token is generated even when user does not exist |
| Dev mode | Returns token directly for testing |
| Exported functions | sendMagicLink(), verifyMagicLink(), magicLinkLogin(), cleanupExpiredMagicLinks() |
2.4 Sessions
| Detail | Value |
|---|---|
| File | equa-server/modules/auth/src/sessions.ts |
| Library | express-session + TypeORMSessionStore |
| Secret | API_SESSION_SECRET environment variable |
| Max age | 2 520 000 ms (~42 minutes) |
| Cookie flags | secure when SSL is active, proxy: true, rolling: true |
2.5 Authorization (RBAC)
| Detail | Value |
|---|---|
| File | equa-server/modules/auth/src/authorization.ts |
| Guard | requirePermissions() (lines 21–26) |
| Site write check | canWriteSite() (lines 43–47) |
| Site read check | canReadSite() (lines 49–50) |
3. Data Model
Users
| Column | Type | Constraints |
|---|---|---|
| id | uuid | PK |
| citext | UNIQUE, NOT NULL | |
| username | varchar | |
| passwordHash | varchar | |
| twoFactorSecret | varchar | nullable |
| enabled | boolean | |
| twoFactorEnabled | boolean | |
| emailVerified | boolean | |
| acceptedTerms | boolean |
Sessions
| Column | Type | Constraints |
|---|---|---|
| id | varchar | PK |
| expires | timestamp | INDEXED |
| user | uuid | INDEXED, FK → Users |
| json | jsonb |
TempPasswords
| Column | Type | Constraints |
|---|---|---|
| user | uuid | FK → Users |
| passwordHash | varchar |
EmailVerifications
| Column | Type | Constraints |
|---|---|---|
| user | uuid | FK → Users |
| code | varchar |
Onetimecodes
| Column | Type | Constraints |
|---|---|---|
| user | uuid | FK → Users |
| code | varchar | |
| available | boolean |
4. API Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/auth/login | No | Password login (returns session cookie) |
| POST | /api/v1/auth/register | No | Create account with email + password |
| POST | /api/v1/auth/google | No | Google OAuth sign-in / auto-register |
| POST | /api/v1/auth/magic-link | No | Request magic link email |
| POST | /api/v1/auth/magic-link/verify | No | Verify magic link token and create session |
| POST | /api/v1/auth/logout | Yes | Destroy session |
| GET | /api/v1/user/current | Yes | Return authenticated user profile |
| POST | /api/v1/auth/verify-email | No | Confirm email verification code |
| POST | /api/v1/auth/2fa/enable | Yes | Enable TOTP two-factor auth |
| POST | /api/v1/auth/2fa/verify | Yes | Verify TOTP code |
5. Frontend Components
| Component | File | Description |
|---|---|---|
| Login page | equa-web/src/modules/auth/pages/login.tsx | Email + password form, Google button, magic-link option |
| Register page | equa-web/src/modules/auth/pages/register.tsx | New account creation form |
| Google auth button | equa-web/src/modules/auth/components/google-auth-button.tsx | Initiates Google OAuth flow |
| Auth modal | equa-web/src/modules/landing/components/auth-modal.tsx | Login/register modal on landing page |
Frontend Session Handling
- Cookies sent with
credentials: 'include'on every fetch. - 401 responses trigger automatic logout and redirect to login.
- Current user loaded from
GET /api/v1/user/current.
6. Business Rules
- Email verification required — Users cannot access protected routes until
emailVerified = true. - Rolling sessions — Every authenticated request refreshes the session expiry (rolling: true), so active users stay logged in as long as they make a request within every 42-minute window.
- Anti-enumeration on magic links —
sendMagicLink()always returns success, even for unknown emails, to prevent email harvesting. - Secure cookies — The
secureflag is set automatically when SSL is detected, preventing cookie transmission over plain HTTP. - Google auto-registration — First-time Google OAuth users are created automatically; email is pre-verified from Google’s claim.
- RBAC enforcement —
requirePermissions()middleware runs before every protected endpoint;canWriteSite()andcanReadSite()gate organization-level access. - Password hashing — bcryptjs with 10 salt rounds; raw passwords are never stored or logged.
- Magic link expiry — Tokens expire after 15 minutes;
cleanupExpiredMagicLinks()removes stale rows.
7. Acceptance Criteria
- User can register with email + password and receive a verification email
- User cannot access protected routes until email is verified
- User can log in with verified email + correct password
- User can enable and verify TOTP two-factor authentication
- User can sign in with Google OAuth and is auto-registered on first use
- User can request a magic link and log in via the emailed token
- Magic link tokens expire after 15 minutes
- Session persists for up to 42 minutes with rolling refresh
- 401 response on frontend triggers automatic logout
- RBAC guards block unauthorized access to organization resources
- Anti-enumeration: magic link request returns success for non-existent emails
8. Risks
| Risk | Impact | Mitigation |
|---|---|---|
Session secret leak (API_SESSION_SECRET) | Full session forgery | Rotate secret; store in vault; restrict env access |
| bcrypt cost factor (10) may become insufficient | Brute-force of leaked hashes | Monitor OWASP guidance; plan migration to higher rounds or argon2 |
| Magic link token interception (email) | Account takeover | 15-min expiry limits window; consider rate-limiting requests per email |
| Sessions expire after 42 minutes of inactivity | Users logged out unexpectedly during long workflows | Consider increasing API_SESSION_MAX_AGE; implement session revocation on password change / 2FA reset |
| Google OAuth token validation bypass | Unauthorized account creation | Validate ID token signature server-side against Google JWKS |
| No account lockout on failed password attempts | Brute-force attacks | Add rate limiting / progressive lockout on login endpoint |