Replace the hand-rolled JWT auth with Better Auth 1.6.25 httpOnly cookie
sessions, validated against the database on every request so revocation,
bans and role changes take effect immediately.
Backend:
- betterAuth.ts wires the Drizzle adapter, magic links, Google sign-in and
the admin plugin; auth-schema.ts maps Better Auth's models onto the
existing `users` table so user IDs and their foreign keys survive intact.
- routes/auth.ts is gone; Better Auth serves the standard endpoints and
authExt.ts carries the flows it doesn't cover.
- auth.ts shrinks to session resolution and helpers; sessions/revocation in
dashboard.ts now read and delete `auth_sessions` rows directly.
- Schema adds the Better Auth core + admin columns (email_verified, image,
banned, ban_reason, ban_expires), with migrations and tests.
- rateLimit.ts resolves client IPs spoof-resistantly: proxy headers are only
honoured from loopback/RFC1918 peers plus TRUSTED_PROXIES.
- passwordPolicy.ts centralises password validation.
- Bump drizzle-orm, drizzle-kit and better-sqlite3 to versions compatible
with Better Auth.
Frontend:
- auth-client.ts plus a reworked AuthContext and api/client.ts move to
cookie-based sessions; no more bearer tokens in requests or middleware.
photo-api:
- Validate Better Auth session cookies against the shared auth_sessions
table instead of verifying JWTs; JWT_SECRET is no longer needed for user
auth, and PHOTO_VIEW_SECRET now signs gallery view tokens.
BETTER_AUTH_SECRET and BETTER_AUTH_URL are required in production; the
deprecated JWT_SECRET stays only as the photo-api view-token fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Locks no longer fail open: an unreachable backend throws
LockUnavailableError and withLock skips the run (or opts into running
unlocked, as template seeding does). The lnbits payment poller keeps
polling through an outage, made safe by only sending confirmation
emails when a row actually transitioned.
- Rate limiter INCR+PEXPIRE now runs as one Lua script, self-healing
counters stranded without a TTL.
- Per-email login lockout moves to a Redis-backed store shared across
replicas (in-memory fallback preserved), case-insensitive on email.
- Graceful shutdown on SIGTERM/SIGINT: stop periodic jobs, close the
server, force-close lingering SSE sockets, close Redis.
- Active PING probe backs the health flag; /health reports last ping.
SSE payment streams also poll the DB as a pub/sub-gap fallback.
- Scale compose gains requirepass, maxmemory 256mb with noeviction, and
an authenticated healthcheck; .env.example documents passwords, TLS
(rediss://), and DB-index selection.
- First tests in the repo: vitest + ioredis-mock covering the lock,
rate limiter, and login lockout stores (27 tests).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use moment-timezone in parseEventDatetime so naive America/Asuncion wall-clock times convert correctly regardless of the host Node tz database. Also normalize user registration date filters with toDbDate for Postgres.
Co-authored-by: Cursor <cursoragent@cursor.com>
Split oversized frontend API client, email service, and admin/booking pages into focused modules while preserving import surfaces, and add Redis-backed queues, stale booking cleanup, stronger auth, and scale deployment configs.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add ticket validation and check-in API endpoints
- Add PDF ticket generation with QR codes (pdfkit)
- Add admin QR scanner page with camera support
- Add admin site settings page
- Update email templates with PDF ticket download link
- Add checked_in_by_admin_id field for audit tracking
- Update booking success page with ticket download
- Various UI improvements to events and booking pages
- Dashboard community links now use .env values like community page
- Removed hardcoded social media URLs from dashboard
- Added deploy folder with nginx and systemd service configs
- Moved linktree page to public route
- Various backend and auth context updates