> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Password login, Google OAuth, magic links, sessions, and role-based access control

# 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)                   |

### 2.6 Auth Form Fields (Frontend)

The login and registration forms use `react-final-form` with custom field components that render plain `<input type="text">`, **not** `<input type="email">`. This is intentional — the login field accepts either a username or an email address, so `type="email"` would reject valid usernames.

| Form         | Input      | `type`     | `name`            |
| ------------ | ---------- | ---------- | ----------------- |
| Login        | Identifier | `text`     | `usernameOrEmail` |
| Login        | Password   | `password` | `password`        |
| Registration | Email      | `text`     | `email`           |
| Registration | Password   | `password` | `password`        |

**Testing implication:** E2E selectors must target `input[name="usernameOrEmail"]` / `input[name="email"]`, never `input[type="email"]`. Tests using `type="email"` selectors silently time out (30s) because the locator never matches. See PR #515 for the full test locator fix.

**Source:** `equa-web/src/modules/auth/pages/login.tsx`, `equa-web/src/modules/auth/pages/register.tsx`, `e2e/regression-fixed-issues.spec.ts` (PR #515, 2026-04-02).

### 2.7 Gateway Token Sync (URL Query Params)

The Equa frontend participates in the equabot gateway's tokenized-URL pattern. When a user lands on the app with `?equabotToken=<token>` or `?gatewayToken=<token>` in the query string, `storage.ts` reads the token and auto-persists it to `localStorage` under the gateway-token key.

| Detail                  | Value                                                       |
| ----------------------- | ----------------------------------------------------------- |
| File                    | `equa-web/src/modules/equabot-settings/services/storage.ts` |
| Recognized query params | `equabotToken`, `gatewayToken` (either accepted)            |
| Persistence             | `localStorage` (gateway-token key)                          |
| Source of pattern       | Standard `equabot dashboard --no-open` URL handoff          |

**Why it exists:** Before PR #512, a client/server token desync could leave the dashboard unable to reach the gateway even though the user was authenticated. The URL-param handoff lets the gateway hand a fresh token directly to the browser via the landing URL, eliminating the desync class of bug (EQUAStart#502).

**Source:** `equa-web/src/modules/equabot-settings/services/storage.ts`, `storage.test.ts` (PR #512 Workstream A, 2026-04-01; lint follow-up in PR #513).

***

## 3. Data Model

### Users

| Column           | Type    | Constraints      |
| ---------------- | ------- | ---------------- |
| id               | uuid    | PK               |
| email            | 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

1. **Email verification required** — Users cannot access protected routes until `emailVerified = true`.
2. **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.
3. **Anti-enumeration on magic links** — `sendMagicLink()` always returns success, even for unknown emails, to prevent email harvesting.
4. **Secure cookies** — The `secure` flag is set automatically when SSL is detected, preventing cookie transmission over plain HTTP.
5. **Google auto-registration** — First-time Google OAuth users are created automatically; email is pre-verified from Google's claim.
6. **RBAC enforcement** — `requirePermissions()` middleware runs before every protected endpoint; `canWriteSite()` and `canReadSite()` gate organization-level access.
7. **Password hashing** — bcryptjs with 10 salt rounds; raw passwords are never stored or logged.
8. **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                                              |
