Commit Graph
10 Commits
Author SHA1 Message Date
Leon SerfatyandClaude Opus 5 8f90347659 feat(auth): finish Turnstile coverage across every auth entry point
Turnstile protected sign-in, sign-up and password-reset, but three gaps
remained:

- updatePassword had no verification and its page had no widget, so the
  final step of the reset flow was unprotected. The reset token is now
  carried through the failure redirect so a failed challenge doesn't
  strand the user on a form whose emailed link can't be replayed.

- /api/auth/[...all] exposed better-auth's handler directly, accepting
  unlimited credential guesses and email sends with no bot protection --
  a full bypass of the page-level checks. Credential-bearing POSTs now
  require a verified token. The gate lives in the route handler, so the
  server actions (which call auth.api.* in-process) are unaffected. GET
  is untouched for OAuth callbacks and verify-email links, and
  /sign-in/social stays open since it only redirects to the provider.

- Turnstile tokens expire after ~5 minutes and the widget never reset,
  so a form left open submitted a stale token and failed with "complete
  the verification challenge" despite the challenge visibly passing.

Verified with Cloudflare's test keys: all four auth forms block an
invalid token, pass a valid one through to real auth logic, and the API
gate returns 403 without a token and 401 with one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 16:26:16 -04:00
Leon SerfatyandClaude Opus 4.8 595a5e3e04 feat(auth): hide Google sign-in until OAuth is configured
Google social login has no credentials in production, so the "Continue with
Google" button (and its divider) errored on click. Gate the button + divider on
a new isGoogleConfigured() helper (requires GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET)
on both the login and signup pages, and guard the signInWithGoogle action as
defense in depth. The button reappears automatically once both env vars are set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 08:26:08 -04:00
Leon SerfatyandClaude Opus 4.8 e5e987eb4c fix(csp): whitelist Umami analytics origin so tracking works
The CSP script-src/connect-src didn't include the Umami host
(fickanalytics.phluit.net), so the browser blocked both loading script.js and
the event beacons (POST /api/send) — analytics recorded 0 visits despite the
site being live. Add a umamiOrigin() helper (derived from NEXT_PUBLIC_UMAMI_SRC,
defaulting to the shared phluit instance) and include it in script-src and
connect-src.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 07:37:35 -04:00
Leon SerfatyandClaude Fable 5 5a555c715e Build GDPR compliance system: data export, account deletion, consent
- Data export (Art. 15/20): GET /api/gdpr/export serves a full JSON export
  of the user's data (credentials/tokens excluded, exclusions declared)
- Right to erasure (Art. 17): self-service deletion with 30-day grace
  period (Settings -> Privacy & Data), cancellable; daily /api/cron/gdpr
  drain cancels Stripe billing, purges Spaces files, cascade-deletes the
  account, anonymizes consent rows, and writes audit evidence
- Migration 0011: account_deletion_requests (partial unique index = one
  pending per user) + FK-less consent_log (survives erasure)
- Consent: terms/privacy acceptance logged at signup (email + Google);
  cookie banner with analytics opt-out (umami.disabled), choices logged
  server-side for signed-in users via POST /api/gdpr/consent
- Admin deleteUser upgraded to the same full purge (was leaving Spaces
  files and Stripe subscriptions orphaned)
- /gdpr legal page now points at the self-service tools
- scripts/verify-gdpr.ts: end-to-end verification vs live dev DB (22/22)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 06:03:27 -04:00
Leon SerfatyandClaude Opus 4.8 0d11018019 fix(csp): allow inline scripts so the app hydrates on Turbopack builds
Next.js 16 builds with Turbopack, which does NOT stamp the middleware CSP
nonce onto its inline hydration scripts (self.__next_f.push). The nonce-based
`script-src 'self' 'nonce-…'` therefore blocked those inline scripts, React
never hydrated, and the marketing/app pages rendered as a blank/black shell
(header + framer-motion sections stuck at opacity:0).

Switch `script-src` to 'self' 'unsafe-inline' (Turbopack-compatible) and drop
the now-unused nonce plumbing. All other CSP directives stay strict
(object-src 'none', frame-ancestors 'none', locked connect-src/frame-src).
Verified in a local production container: served script-src is correct and
the page hydrates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 05:30:39 -04:00
Leon SerfatyandClaude Opus 4.8 5495b94924 Deploy on DigitalOcean App Platform (GitHub-source build) + consolidate audit-fixes
Deploy config:
- .do/app.yaml: build the Dockerfile directly from GitHub (deploy_on_push) instead
  of a pre-built DOCR image; NEXT_PUBLIC_* set RUN_AND_BUILD_TIME with the
  propertymanagement.network domain so they bake into the client bundle; add
  custom domains block (apex + www); wire Sentry DSN (server + browser).

Included pending work from the audit-fixes branch:
- AI provider abstraction (OpenAI/Anthropic, admin-selectable; Anthropic default)
- Per-landlord e-signature (DocuSign OAuth + Dropbox Sign) + migration 0010
- Outbound webhooks / Zapier integration
- PayPal removal (Stripe-only billing)
- Storage hardening (fail-loud when Spaces unconfigured), security fixes

Verified: full production Docker build (same build-args as DO) passes clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 04:45:24 -04:00
Leon SerfatyandClaude Opus 4.8 917a06ee85 docs: overhaul README and mark project proprietary
- Rewrite README: accurate current feature set (public API, webhooks/Zapier,
  PayPal, accounting sync, e-sign, team, branding, inspections, maps, ~30 tables,
  4 cron jobs), emoji section headers, clearer setup and security sections.
- Remove the inline env-variable example block; point to .env.example / DIGITALOCEAN.md.
- Fix stale "local disk" storage references to DigitalOcean Spaces.
- License: change MIT -> Proprietary; add LICENSE file and package.json "UNLICENSED".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 13:51:59 -04:00
Leon SerfatyandClaude Opus 4.8 c9968531e4 Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening
Batch commit of the pending working tree on security/audit-fixes-2026-07.
Major areas:
- Outbound webhooks / Zapier: schema + signed delivery with retries, public
  v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain.
- Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS
  (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when
  Spaces is unconfigured instead of silently using ephemeral disk.
- Integrations & features (concurrent work): accounting (QuickBooks/Xero),
  e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding,
  expanded legal pages.
- DB migrations 0006–0009.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 13:42:34 -04:00
Leon SerfatyandClaude Opus 4.8 969d5d4c8a Security hardening from 2026-07-01 audit
- Exclude supabase/ from Docker build context (leaked service_role key file)
- /api/files: exact per-user namespace match + reject path traversal;
  storage resolveKey rejects ".."/"." segments (fixes cross-user file read)
- Add ownsProperty/Unit/Tenant checks to tenants, maintenance (landlord path),
  and documents (JSON branch, now field-whitelisted) create handlers
- Escape user data in follow-up + payment-link emails (reuse escapeHtml)
- Neutralize CSV formula injection in toCsv + export routes
- Tighter sign-in rate limit (10/min); env-gated email verification + sender
- Per-request nonce CSP; drop script-src 'unsafe-inline' (styles unchanged)
- Add input length bounds; validate follow-ups POST body

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:56:34 -04:00
Leon SerfatyandClaude Opus 4.8 857b9a7811 Initial import: property management SaaS + security hardening + admin dashboard
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>
2026-06-23 20:36:07 -04:00