OTP overhaul: test-user bypass + hash-at-rest + Fazpass integration

- Test-OTP bypass allowlist for Apple reviewers / QA: phone-scoped static OTPs
  managed in CC (Settings → Test OTP Bypass), bcrypt-hashed on save, kill-switch
  toggle, per-entry expires_at. New `otp_requests` columns (is_bypass, code_hash)
  + DB CHECK enforcing bypass-row shape.
- Hash-at-rest for stub OTPs: replaced plaintext `<ref>:<code>` storage with
  bcrypt(code_hash); reference goes to fazpass_reference alone. Verify routes on
  sovereign is_bypass flag, defers code_hash-NULL rows to Fazpass.
- Fazpass integration (gated by FAZPASS_ENABLED env, default off): new
  fazpass.service.js calling /v1/otp/{request,verify}; distinct errors for wrong
  OTP (CODE_MISMATCH 401) vs provider outage (OTP_PROVIDER_FAILED 502).
- Removed redundant Free Trial CC section (was a back-compat shim for the same
  pricing_promotions row as "Diskon Sesi Pertama") + unused alias in
  pricing.service.js.

208 tests green (34 new for OTP + Fazpass). Fazpass API + dashboard PDFs added
at project root for reference (docs are auth-gated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 22:39:34 +08:00
parent 3a0cdf5c4e
commit 6fd98ca99c
15 changed files with 1958 additions and 158 deletions

View File

@@ -19,10 +19,21 @@ AUTH_JWT_SECRET=replace-with-strong-random-32+char-secret
ACCESS_TOKEN_TTL_SECONDS=3600
REFRESH_TOKEN_TTL_DAYS=30
# Fazpass (OTP provider — TBD real values once docs are available)
FAZPASS_API_KEY=
FAZPASS_BASE_URL=
FAZPASS_WEBHOOK_SECRET=
# --- Fazpass (OTP provider) ---
#
# When FAZPASS_ENABLED=true, requestOtp() calls Fazpass /v1/otp/request and
# verifyOtp() calls Fazpass /v1/otp/verify. When false, the in-process stub
# generates + verifies codes locally (dev/test default).
#
# Single merchant key authenticates the account; single gateway key selects
# the (channel + provider) tuple configured in dashboard → Integration → Add
# Gateway. The client-supplied `channel` in /otp/request becomes informational
# only when Fazpass is live — the gateway decides which channel actually fires.
FAZPASS_ENABLED=false
FAZPASS_BASE_URL=https://api.fazpass.com
FAZPASS_MERCHANT_KEY=
FAZPASS_GATEWAY_KEY=
FAZPASS_TIMEOUT_MS=10000
# Google OAuth — comma-separated list of valid audience client IDs (Android, iOS).
GOOGLE_OAUTH_CLIENT_IDS=