Authentication routes for email/password and OAuth (GitHub, Google).
Endpoints (INF-005: all under /api/v1/auth/)
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Email/password registration |
| POST | /api/v1/auth/login |
Email/password sign-in |
| POST | /api/v1/auth/logout |
Token revocation + cookie clear |
| POST | /api/v1/auth/refresh |
Refresh session (extend cookie TTL) |
| GET | /api/v1/auth/me |
Return current user from cookie |
| GET | /api/v1/auth/export |
Export user-owned account data (SEC-003) |
| DELETE | /api/v1/auth/account |
Delete account + owned data (SEC-003) |
| GET | /api/v1/auth/verify |
Verify email via token (SEC-001) |
| POST | /api/v1/auth/resend-verification |
Resend verification email (SEC-001) |
| POST | /api/v1/auth/forgot-password |
Request a password reset token |
| POST | /api/v1/auth/reset-password |
Reset password using a valid token |
| GET | /api/v1/auth/github/callback |
GitHub OAuth token exchange |
| GET | /api/v1/auth/google/callback |
Google OAuth token exchange |
Security measures
- Passwords hashed with scrypt (64-byte key, 16-byte random salt)
- JWT stored in HttpOnly; Secure; SameSite=Strict cookie — never in localStorage
- A companion
token_expcookie (Non-HttpOnly) exposes only the exp timestamp so the frontend can proactively warn before expiry without ever touching the JWT - JWT signed with HS256, 8-hour expiry
- Rate limiting: separate per-endpoint buckets (login: 10, forgot/reset: 5 per IP per 15 min)
- Revoked tokens kept in an in-memory Map (production: use Redis — see ENH-002)
- Password reset tokens persisted in DB table
password_reset_tokens(migration 003) - Input validation and sanitisation on every endpoint
- OAuth state parameter validated on the frontend to prevent CSRF
- CSRF double-submit cookie protection on all mutating endpoints (via appSetup.js)
- No sensitive data (passwords, raw OAuth tokens, JWT strings) returned to client
- Source:
Members
(static, constant) EXP_COOKIE
Expiry hint cookie — Non-HttpOnly so the frontend can read the exp timestamp.
- Source:
(static, constant) JWT_TTL_SEC
JWT TTL in seconds (8 hours). Must match signJwt default.
- Source:
(static, constant) requireAuth
Backward-compatible alias. All files that do
import { requireAuth } from "./routes/auth.js"
continue to work — requireUser is the same JWT cookie → bearer → query
middleware that requireAuth used to be.
- Source:
Methods
(static) _internalCheckRateLimit(bucket, ip) → {Object}
Internal cross-module helper — exposes the per-bucket rate limiter so the
WebAuthn router can apply the shared webauthnVerify bucket without
duplicating the limiter state.
Parameters:
| Name | Type | Description |
|---|---|---|
bucket |
string | |
ip |
string |
- Source:
Returns:
- Type
- Object
(static) _internalConsumePendingMfaLogin(token, optsopt) → {Object|null}
Internal cross-module helper — exported so routes/webauthn.js can
consume / peek pending-MFA tokens issued by /login without duplicating
the in-memory store. Underscore prefix marks it as not part of the
public API contract.
Parameters:
| Name | Type | Attributes | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
token |
string | ||||||||||
opts |
Object |
<optional> |
Properties
|
- Source:
Returns:
- Type
- Object | null
(static) _internalGenerateTotpCode(secret, offsetStepsopt) → {string}
Internal cross-module helper — exposes the TOTP code generator so the test
suite (backend/tests/helpers/test-base.js) can produce valid codes
without re-implementing base32-decode + HMAC. Keeping this in production
code means any algorithm drift (SHA-256, 8-digit, etc.) is reflected in
tests on the same commit. Underscore prefix marks it as not part of the
public API contract.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
secret |
string | Base32 TOTP secret. |
||
offsetSteps |
number |
<optional> |
0 | Step offset from |
- Source:
Returns:
6-digit code.
- Type
- string
(static) _internalRecordPendingMfaFailure(token) → {Object|null}
Internal cross-module helper — routes/webauthn.js shares the
pendingToken brute-force surface (a stolen token can be replayed against
arbitrary assertion attempts) and must apply the same per-token strike
budget. Underscore prefix marks it as not part of the public API contract.
Parameters:
| Name | Type | Description |
|---|---|---|
token |
string |
- Source:
Returns:
- Type
- Object | null
(static) _internalRevokeCurrentSession(req, res)
SEC-004: Industry-standard "security-posture change → terminate session" primitive. Revokes the current request's JTI and clears the auth cookies so the caller is forced to re-authenticate immediately. Matches the DELETE /account pattern and the behaviour of Auth0, Clerk, Okta, GitHub on credential / MFA changes.
Exported as an underscore-prefixed cross-module helper so
routes/webauthn.js can apply the same semantics on passkey removal
without duplicating the JTI-revoke + cookie-clear plumbing.
Parameters:
| Name | Type | Description |
|---|---|---|
req |
Object | Must carry |
res |
Object |
- Source:
(static) _internalVerifyAccountPassword(user, password) → {Promise.<boolean>}
Internal cross-module helper — exported so routes/webauthn.js can reuse
the password-confirmation policy (OAuth-only users skip the password
check; everyone else must supply a matching password).
Parameters:
| Name | Type | Description |
|---|---|---|
user |
Object | |
password |
string |
- Source:
Returns:
- Type
- Promise.<boolean>
(static) setAuthCookie(res, token, expSec)
Set the HttpOnly auth cookie + a readable expiry hint cookie on a response. Called after every successful authentication (login, OAuth, refresh, workspace switch).
Parameters:
| Name | Type | Description |
|---|---|---|
res |
Object | Express response object. |
token |
string | The signed JWT string. |
expSec |
number | Unix timestamp of token expiry (seconds). |
- Source:
(inner) checkRateLimit(bucket, ip) → {Object}
Check rate limit for a specific bucket.
Parameters:
| Name | Type | Description |
|---|---|---|
bucket |
string | — key in rateBuckets (e.g. "login", "forgotPassword") |
ip |
string |
- Source:
Returns:
- Type
- Object
(inner) clearAuthCookies(res)
Clear both auth cookies, effectively logging the user out client-side.
Parameters:
| Name | Type | Description |
|---|---|---|
res |
Object | Express response object. |
- Source:
(inner) ensureUserWorkspace(user)
Ensure the user belongs to at least one workspace, creating a personal workspace if needed. Called from every auth path (login, OAuth) so no user can end up without a workspace.
Parameters:
| Name | Type | Description |
|---|---|---|
user |
Object | — User row from the database. |
- Source:
(inner) isOAuthOnlyUser(user) → {boolean}
Check whether a user registered via OAuth only (no password set).
Parameters:
| Name | Type | Description |
|---|---|---|
user |
Object |
- Source:
Returns:
- Type
- boolean
(inner) validatePasswordStrength(password) → {string|null}
Validate password strength.
Parameters:
| Name | Type | Description |
|---|---|---|
password |
string |
- Source:
Returns:
Error message, or null if valid.
- Type
- string | null
(async, inner) verifyAccountPassword(user, password) → {Promise.<boolean>}
Verify the authenticated user's password for sensitive account actions. For OAuth-only users (no passwordHash), password verification is skipped — the OAuth session itself serves as proof of identity.
Parameters:
| Name | Type | Description |
|---|---|---|
user |
Object | |
password |
string |
- Source:
Returns:
- Type
- Promise.<boolean>