Property Management Network — Next.js 16 (App Router), Better Auth, Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend. Includes: - Security hardening: access-control/IDOR fixes, TLS-by-default DB layer, constant-time cron auth, strict security headers, atomic AI quota gating, HTML/email output encoding, demo-backdoor disabled in production. - Superadmin dashboard at /admin (overview/MRR, server-paginated users with ban/impersonate/plan/delete, billing, platform activity + admin audit log, AI usage, system health) via the Better Auth admin plugin. - Seed/migration utility scripts under scripts/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.9 KiB
3.9 KiB
Security & Pre-Deployment Checklist
⚠️ TREAT ALL SECRETS IN
.env.localAS COMPROMISED. This project was distributed in a transfer package, which means every secret that was present in.env.local— theDATABASE_URL/ Postgres password,BETTER_AUTH_SECRET, and any Stripe / OpenAI / Resend API keys — has left a trusted boundary and must be treated as leaked. Rotate all of them before any production deployment or client handoff. Do not assume "it was only a zip" — assume the file is public.
1. Rotate every secret before production / handoff
Work through this list and rotate each item. Do not reuse any value that
ever appeared in the distributed .env.local.
- Postgres password — change the database role's password (or provision a
brand-new role) and update
DATABASE_URLeverywhere it is configured. Then revoke the old credential. BETTER_AUTH_SECRET— generate a fresh 32-byte secret:bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Rotating this invalidates existing sessions — expected and desired.- Stripe — roll the secret key (and restricted keys), and rotate the webhook signing secret in the Stripe Dashboard.
- OpenAI — revoke the leaked API key and issue a new one.
- Resend — revoke the leaked API key and issue a new one.
CRON_SECRET— set a strong random value (the cron routes now fail closed if it is unset):bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Configure the same value in Vercel so Cron sendsAuthorization: Bearer <CRON_SECRET>.- Google OAuth — if the client secret was present in the transfer, rotate it in the Google Cloud Console.
2. Secret hygiene
- Never commit
.env.local(or any real.env*with live values). Confirm it is listed in.gitignore. - Store production secrets in the deployment platform's encrypted env-var store (e.g. Vercel Project Settings → Environment Variables), not in files.
- Use distinct secrets per environment (dev / preview / production).
3. Database / TLS
- Set
DATABASE_SSL=requirein production so the connection uses verified TLS (encrypted + certificate-verified).DATABASE_SSL=disableis only for local / unix-socket development. If the provider uses a private/custom CA, supply it viaDATABASE_CA. - Use a managed Postgres on a private network (or the provider's private endpoint) rather than a database exposed on a public IP.
Resolved in code
The following hardening has already been applied in this codebase:
- TLS enforcement —
lib/db/index.tsnow defaults to verified TLS (rejectUnauthorized: true) and never silently runs plaintext. Behavior is controlled by the explicitDATABASE_SSLenv var (disable/no-verify/require), with optionalDATABASE_CA. - Constant-time cron auth —
lib/cron-auth.tsperforms atimingSafeEqualbearer-token comparison that fails closed whenCRON_SECRETis unset. All cron routes (daily,late-fees,lease-expiry,rent-reminders) now use it and share the standardAuthorization: Bearerscheme. - Security headers —
next.config.tssets a strict baseline on all routes:X-Content-Type-Options,X-Frame-Options: DENY,Referrer-Policy: no-referrer(protects the tenant-portal URL token), HSTS with preload,X-DNS-Prefetch-Control: off, and a Content-Security-Policy. - Auth rate limiting —
lib/auth.tsenables better-auth's built-in rate limiting (20 requests / 60s per IP) to slow brute-force and credential stuffing. - Test-plan backdoor removed — the development-only backdoor that bypassed plan/subscription checks is disabled in production.