Files
baya-monorepo/mvp/blocker-phases/15-debug-mode-production.md
T
2026-08-02 20:37:14 +03:30

3.8 KiB

Phase 15 — Turn off developer/debug mode on the live site

Blocker: blockers.md §B.2. Do this deliberately, on its own, right before any real stranger is let near the site — it touches deploy config and secrets handling, not app logic, and has real prerequisites. Depends on: nothing code-wise, but bundle with credential rotation (blockers.md §B.1) when you do it — currently deferred per your own call, revisit before real users arrive.


Root cause. The exposure (GET api/v1/dev/last_otp/{phone}, unauthenticated by design, guarded only by environment.IsDevelopment()) is correctly implemented in code — the entire gap is that the deployed docker-compose.yml sets ASPNETCORE_ENVIRONMENT: Development, deliberately and already documented (a comment in the compose file and a full section in DEPLOY.md spell out why and what depends on it).

This is not a one-line flip — five real prerequisites, already documented in DEPLOY.md, need to land together:

  1. Create server/src/API/Baya.Web.Api/appsettings.Production.json with real (non-not-for-production sentinel) IdentitySettings:SecretKey/EncryptkeyStartupSecretsGuard rejects the sentinel outside Development, so a bare env-var flip with the current file would refuse to boot. Seams:FieldEncryption:Key/:HashKey must be copied byte-identical from the current Development value — never regenerated (root CLAUDE.md rule 6 — this key decrypts all existing PII).
  2. Set ASPNETCORE_ENVIRONMENT: Production in docker-compose.yml.
  3. Switch migrations from boot-time auto-apply (Development-only branch) to the documented one-shot (... migrate) deploy step — and confirm the demo/lifecycle seed data the shared DB currently relies on survives the transition, since those seeders stop running automatically post-flip.
  4. Swap Seams:Sms:Provider from telegram (a broadcast-to-a-fixed-chat-list relay, fine for trusted internal testers, not for strangers) to kavenegar with a real API key — flipping only the environment variable closes the /dev/last_otp leak but leaves OTPs going out over the Telegram relay instead of to the real phone.
  5. Bundle with credential rotation (blockers.md §B.1) when you do it — the same config file carries the DB password alongside the encryption keys.

Flag: whether to keep the Telegram relay live in parallel with kavenegar for internal/test accounts post-launch, or retire it entirely, is a product/ops call, not answered by the code.


Not phases — noted here so they aren't lost, but no code to write

  • §B.1 (rotate committed credentials) — per your call, skipped for now (private repo, single developer). Revisit before any real user touches the deployed site; bundle with step 5 above when you do.
  • §B.3 (legal review of Terms/Privacy) — not a code task; needs an actual legal review.
  • §B.4 (مودیان e-invoicing) — per your call, not built now. When you pick it up: the invoice data and tax math are already correct; the only missing piece is the actual government-facing registration call, and it needs a chosen provider/gateway before any adapter code makes sense. Add a note to forgotten-features.md saying exactly that, rather than building a speculative seam with nothing real to point it at.
  • §B.5 (e-namad certification) — a business/paperwork step, not a code change; also gates the real ZarinPal card-payment path (07-card-payment-redirect.md) from ever going fully live, independent of any code fix.
  • §B.6 (real bank-transfer payout rail) — per your call, stays mocked. Resolved by 11-nurse-payouts.md — the seam already exists server-side; only the client UI in front of it was missing.