- 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>
99 lines
3.7 KiB
Plaintext
99 lines
3.7 KiB
Plaintext
# Server
|
|
PUBLIC_PORT=3000
|
|
INTERNAL_PORT=3001
|
|
INTERNAL_HOST=127.0.0.1
|
|
|
|
# Database
|
|
DATABASE_URL=postgresql://user:password@localhost:5432/halobestie
|
|
|
|
# Valkey / Redis
|
|
VALKEY_URL=redis://localhost:6379
|
|
|
|
# Control center origin (for CORS + refresh-cookie). Comma-separated list allowed.
|
|
CC_ORIGIN=http://localhost:5173
|
|
|
|
# --- Auth (Phase 3.4) ---
|
|
|
|
# JWT access token signing key (HS256). Must be >= 32 chars.
|
|
AUTH_JWT_SECRET=replace-with-strong-random-32+char-secret
|
|
ACCESS_TOKEN_TTL_SECONDS=3600
|
|
REFRESH_TOKEN_TTL_DAYS=30
|
|
|
|
# --- 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=
|
|
|
|
# Apple Sign In
|
|
APPLE_SERVICES_ID=
|
|
APPLE_TEAM_ID=
|
|
APPLE_KEY_ID=
|
|
APPLE_PRIVATE_KEY=
|
|
|
|
# First super-admin (used by seed script)
|
|
ADMIN_EMAIL=admin@halobestie.com
|
|
ADMIN_PASSWORD=ChangeMe123!
|
|
|
|
# --- FCM (kept — only Messaging is used; Auth is self-managed) ---
|
|
# Path to Firebase service-account JSON (falls back to backend/firebase-service-account.json)
|
|
FIREBASE_SERVICE_ACCOUNT_PATH=
|
|
|
|
# --- Valkey availability mirror cadences ---
|
|
#
|
|
# All env-driven per backend/CLAUDE.md Config-Source Convention. Defaults match
|
|
# requirement/valkey-online-mirror-plan.md. Floor-clamped by their getters.
|
|
|
|
# How often the auto-offline sweep checks heartbeat freshness (seconds).
|
|
# The staleness threshold itself (`stale_after_seconds`) is CC-tunable via app_config.
|
|
MITRA_AUTO_OFFLINE_SWEEP_SECONDS=30
|
|
|
|
# How often heartbeat timestamps are batched from Valkey → Postgres (seconds).
|
|
# Per-ping heartbeat writes go to Valkey only; this preserves forensic
|
|
# `last_heartbeat_at` in Postgres with up to <interval> seconds of lag.
|
|
HEARTBEAT_MIRROR_INTERVAL_SECONDS=60
|
|
|
|
# How often Valkey state is re-derived from Postgres to heal drift (seconds).
|
|
# Belt-and-braces against failed best-effort Valkey writes, out-of-band Postgres
|
|
# mutations, or evictions. Set to 0 to disable (not recommended).
|
|
VALKEY_ONLINE_MIRROR_SWEEP_SECONDS=300
|
|
|
|
# --- Phase 5: Xendit (dev-safe defaults: integration disabled) ---
|
|
#
|
|
# Flip XENDIT_ENABLED=true in staging/prod once secret + webhook token are populated.
|
|
# When false, payment.service.js skips invoice creation and the dev/Maestro stub
|
|
# /internal/_test/force-confirm-payment plays the role of the webhook.
|
|
# See requirement/phase5-xendit-plan.md.
|
|
XENDIT_ENABLED=false
|
|
XENDIT_SECRET_KEY=
|
|
XENDIT_WEBHOOK_TOKEN=
|
|
XENDIT_SUCCESS_REDIRECT_URL=
|
|
XENDIT_FAILURE_REDIRECT_URL=
|
|
|
|
# --- Xendit webhook survival sink (optional file-based fallback) ---
|
|
#
|
|
# When the primary DB log (`webhook_logs` table) is unreachable, the route can
|
|
# spill each inbound webhook to a daily-rolling JSONL file instead. Disabled
|
|
# by default — production opts in by mounting a persistent volume / GCS sync
|
|
# and flipping XENDIT_WEBHOOK_FALLBACK_ENABLED=true. The handler will NOT
|
|
# fall back to stdout; if both DB and this sink are unavailable the event is
|
|
# dropped.
|
|
#
|
|
# Filename pattern: "<NAME>-YYYY-MM-DD.jsonl" (UTC day boundary).
|
|
XENDIT_WEBHOOK_FALLBACK_ENABLED=false
|
|
XENDIT_WEBHOOK_FALLBACK_DIR=./logs
|
|
XENDIT_WEBHOOK_FALLBACK_NAME=xendit-webhook-fallback
|