Skip to main content

SPEC 001 — Authentication

FieldValue
StatusDRAFT
PriorityP0 — Launch-Critical
Backendequa-server/modules/auth/src/
Frontendequa-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

DetailValue
Fileequa-server/modules/auth/src/authentication.ts
Hash algorithmbcryptjs, 10 salt rounds
2FASupported (TOTP via twoFactorSecret)
Email verificationRequired before login succeeds
Session bindingsession.user = result.user.id (line 72)

2.2 Google OAuth

DetailValue
Fileequa-server/modules/auth/src/google-auth.ts
Token typesGoogle ID tokens + access tokens
Auto-registrationYes — creates user on first Google sign-in
Email verificationValidated from Google profile
Session bindingsession.user = user.id (line 88)
DetailValue
Fileequa-server/modules/auth/src/magic-link.ts
Token32-byte random hex
Expiry15 minutes
Anti-enumerationToken is generated even when user does not exist
Dev modeReturns token directly for testing
Exported functionssendMagicLink(), verifyMagicLink(), magicLinkLogin(), cleanupExpiredMagicLinks()

2.4 Sessions

DetailValue
Fileequa-server/modules/auth/src/sessions.ts
Libraryexpress-session + TypeORMSessionStore
SecretAPI_SESSION_SECRET environment variable
Max age2 520 000 ms (~42 minutes)
Cookie flagssecure when SSL is active, proxy: true, rolling: true

2.5 Authorization (RBAC)

DetailValue
Fileequa-server/modules/auth/src/authorization.ts
GuardrequirePermissions() (lines 21–26)
Site write checkcanWriteSite() (lines 43–47)
Site read checkcanReadSite() (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.
FormInputtypename
LoginIdentifiertextusernameOrEmail
LoginPasswordpasswordpassword
RegistrationEmailtextemail
RegistrationPasswordpasswordpassword
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.
DetailValue
Fileequa-web/src/modules/equabot-settings/services/storage.ts
Recognized query paramsequabotToken, gatewayToken (either accepted)
PersistencelocalStorage (gateway-token key)
Source of patternStandard 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

ColumnTypeConstraints
iduuidPK
emailcitextUNIQUE, NOT NULL
usernamevarchar
passwordHashvarchar
twoFactorSecretvarcharnullable
enabledboolean
twoFactorEnabledboolean
emailVerifiedboolean
acceptedTermsboolean

Sessions

ColumnTypeConstraints
idvarcharPK
expirestimestampINDEXED
useruuidINDEXED, FK → Users
jsonjsonb

TempPasswords

ColumnTypeConstraints
useruuidFK → Users
passwordHashvarchar

EmailVerifications

ColumnTypeConstraints
useruuidFK → Users
codevarchar

Onetimecodes

ColumnTypeConstraints
useruuidFK → Users
codevarchar
availableboolean

4. API Endpoints

MethodPathAuthDescription
POST/api/v1/auth/loginNoPassword login (returns session cookie)
POST/api/v1/auth/registerNoCreate account with email + password
POST/api/v1/auth/googleNoGoogle OAuth sign-in / auto-register
POST/api/v1/auth/magic-linkNoRequest magic link email
POST/api/v1/auth/magic-link/verifyNoVerify magic link token and create session
POST/api/v1/auth/logoutYesDestroy session
GET/api/v1/user/currentYesReturn authenticated user profile
POST/api/v1/auth/verify-emailNoConfirm email verification code
POST/api/v1/auth/2fa/enableYesEnable TOTP two-factor auth
POST/api/v1/auth/2fa/verifyYesVerify TOTP code

5. Frontend Components

ComponentFileDescription
Login pageequa-web/src/modules/auth/pages/login.tsxEmail + password form, Google button, magic-link option
Register pageequa-web/src/modules/auth/pages/register.tsxNew account creation form
Google auth buttonequa-web/src/modules/auth/components/google-auth-button.tsxInitiates Google OAuth flow
Auth modalequa-web/src/modules/landing/components/auth-modal.tsxLogin/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 linkssendMagicLink() 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 enforcementrequirePermissions() 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

RiskImpactMitigation
Session secret leak (API_SESSION_SECRET)Full session forgeryRotate secret; store in vault; restrict env access
bcrypt cost factor (10) may become insufficientBrute-force of leaked hashesMonitor OWASP guidance; plan migration to higher rounds or argon2
Magic link token interception (email)Account takeover15-min expiry limits window; consider rate-limiting requests per email
Sessions expire after 42 minutes of inactivityUsers logged out unexpectedly during long workflowsConsider increasing API_SESSION_MAX_AGE; implement session revocation on password change / 2FA reset
Google OAuth token validation bypassUnauthorized account creationValidate ID token signature server-side against Google JWKS
No account lockout on failed password attemptsBrute-force attacksAdd rate limiting / progressive lockout on login endpoint