- closes #102 — Persist custodial wallets in Postgres (removes in-memory
walletStore) - closes #103 — Move auth nonces to Postgres for multi-instance auth
- closes #112 — Enforce Twilio webhook signature validation in all environments
- closes #104 — Add production Dockerfile and deployment runbook
Problem: src/stellar/wallet.ts stored encrypted secrets in a module-level Map. Restarts wiped all wallets; horizontal scaling was impossible.
Fix:
- Added
CustodialWalletPrisma model (userIdunique,publicKeyunique,encryptedSecret/iv/authTagcolumns) - New migration:
prisma/migrations/20260529000001_add_custodial_wallets/ - Rewrote
createCustodialWallet,getWalletByUserId,getKeypairForUser, andlistWalletsto read/writedb.custodialWallet - 9 unit tests covering create, duplicate prevention, read, keypair decrypt round-trip, and simulated restart persistence
Key rotation / backup: rotate WALLET_ENCRYPTION_KEY by re-encrypting all custodial_wallets rows with the new key before swapping the env var. The database is the authoritative backup — losing the key makes wallets unrecoverable.
Problem: stellar-verification.ts stored challenge nonces in an in-memory Map. Rolling deploys and multiple app instances broke /api/auth/verify.
Fix:
- Added
AuthNoncePrisma model (stellarPubKeyunique,expiresAtindexed for cleanup) - New migration:
prisma/migrations/20260529000002_add_auth_nonces/ StellarVerificationclass is now stateless (no nonce map)challenge()upserts nonces viadb.authNonce; expired rows are pruned lazilyverify()reads/deletes nonces from DB — expiry check and replay prevention are preserved- Auth unit tests updated to mock
db.authNonceinstead of the in-memory store; added cross-instance test
Problem: src/routes/whatsapp.ts skipped validateRequest when NODE_ENV !== 'production', allowing spoofed requests on staging/dev.
Fix:
- Signature validation now runs whenever
TWILIO_AUTH_TOKENis set, regardless ofNODE_ENV - Returns
403immediately ifTWILIO_AUTH_TOKENis absent — no silent skip - Added
TWILIO_AUTH_TOKENto the required-vars list insrc/config/env.ts - Added fail-fast check in
src/index.tsinitServices()so the server refuses to start without the token - 5 unit tests: no-token 403, invalid-signature staging, invalid in development, valid happy path, env-agnostic enforcement
Added:
Dockerfile— multi-stage build:node:20-alpinebuilder (npm ci→prisma generate→tsc→ prod-only deps), then slim runtime image running as non-rootappuser; CMD runsprisma migrate deploy && node dist/index.js.dockerignore— excludesnode_modules,dist,.env*, logs, tests, docsdocs/PRODUCTION_DEPLOYMENT.md— new sections covering:- Build/push commands
- Minimum required env vars (
NODE_ENV=production,CORS_ORIGINS,WALLET_ENCRYPTION_KEY,ADMIN_API_TOKEN,TWILIO_AUTH_TOKEN, etc.) prisma migrate deployas pre-start step; Kubernetes initContainer pattern- Health/readiness probe table (
GET /health/liveliveness,GET /health/readyreadiness 200/503) with Kubernetes and ALB examples - Key rotation and backup expectations for
WALLET_ENCRYPTION_KEY,JWT_SEED, and auth nonces
-
npx jest tests/unit/stellar/wallet.test.ts— 9 tests pass -
npx jest src/controllers/__tests__/auth.test.ts— all auth tests pass -
npx jest tests/unit/whatsapp/webhook.test.ts— 5 tests pass -
docker build -t neurowealth-backend .completes without error -
GET /health/livereturns 200 after startup -
GET /health/readyreturns 503 before DB connects, 200 after all services ready - Starting without
TWILIO_AUTH_TOKENset fails fast with a clear error message - POST
/api/whatsapp/webhookwith a bad signature returns 403 in allNODE_ENVvalues
🤖 Generated with Claude Code