Skip to content

Authentication

_david edited this page Aug 21, 2026 · 1 revision

Authentication

Tokens

  • Access token: signed with TOKEN_SECRET, expiry TOKEN_EXP_IN (e.g. 3h).
  • Refresh token: signed with TOKEN_REFRESH, expiry TOKEN_REFRESH_EXP_IN (defaults to 7d if unset).
  • Both carry { _id: candidateId } as the payload.
  • POST /auth/refresh rotates both tokens and blacklists the old refresh token — reusing a rotated-out refresh token is rejected ("Refresh token revoked" / 403).

Until 2026-08-21, TOKEN_EXP_IN was declared but never actually passed to the JWT signer, so both tokens silently defaulted to a 1-hour expiry each — meaning the refresh token expired at the same time as the access token, making the refresh flow pointless. Fixed; both now honor their configured/default expiries independently.

Token blacklist

utils/tokenBlacklist.ts — Redis-backed with an in-memory Map fallback if Redis is unavailable. TTL matches the token's remaining lifetime. A background cleanup job runs every 60s for the in-memory fallback path.

verifyToken middleware

Applied to every /api/v1/* route except /auth/*. On a valid token it:

  1. Attaches req.user = { _id }.
  2. Forces req.body.candidateId = req.user._id, overwriting whatever the client sent. This is deliberate — see Security for why.

verifyTokenByQuery is the same check but also accepts the token via ?token= (used by /download-pdf, since browsers can't set custom headers on a direct-link download).

i18n (as of v1.1.0, phase 1)

Send Accept-Language: en to get English messages; omit the header (or send vi) for Vietnamese (the default, matching pre-i18n behavior). Currently covers the full auth flow (register/login/logout/refresh) — see src/locales/{vi,en}.ts for the exact key set.

Not yet covered: Joi validation error messages, Mongoose required messages, and candidate/CV-section success/error messages — these still always return Vietnamese regardless of Accept-Language. Tracked under issue #78.

# English
curl -X POST .../api/v1/auth/login -H "Accept-Language: en" -d '{"email":"x","password":"wrong-but-valid-format"}'
# → {"message":"Incorrect password", ...}

# Vietnamese (default)
curl -X POST .../api/v1/auth/login -d '{"email":"x","password":"wrong-but-valid-format"}'
# → {"message":"Mật khẩu không chính xác", ...}

Password requirements

Enforced via Joi (config/joi.config.ts) and regex (config/regex.config.ts): minimum 12 characters, at least one uppercase, one lowercase, one number, one special character. Hashed with bcrypt at 12 rounds (utils/bcrypt.ts).

Clone this wiki locally