fix(auth): close bootstrap gate hole between sign-up and user mirror

Sign-ups through the Better Auth proxy write to auth_users, but the
bootstrap gate counted users — which only mirrors on the first
authenticated call. In that window a second public registration was
accepted on both the proxied sign-up and the native register endpoint.

countLocalUsers now unions users with auth_users (guarded by
to_regclass so backend-only installs still work), and handleRegister
uses it instead of its own inline count.

e2e-verified on a live stack: users=0/auth_users=1 already reports
mode=login and 403s both public paths; internal-token bypass works,
wrong token refused, authed POST /users creates users (201), and the
users mirror still populates on first authenticated call.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-17 16:22:26 +02:00
1 parent 572ff8dce3
commit e127c62b0f
2 files changed
+32 -5

No files matched your search

+12 -3
View File
@@ -237,9 +237,18 @@ pieces exist; this phase closes the gaps that block real usage.
ever conflicts, Docker's bind error lands in `deployments.error` and shows
in the failed-deploy log tail — not silent. Revisit if a service-level
host-port config is ever added.
- [ ] Auth hardening: e2e verify bootstrap gate (first user registers, second
is refused, manual creation works), OAuth happy paths for GitHub +
Google, session expiry redirect.
- [x] Auth hardening: e2e-verified against a live stack (scratch Postgres +
Redis + real Better Auth sidecar). Fixed a real gate hole: sign-ups write
`auth_users` but the gate counted `users`, which only mirrors on the
first authenticated call — a second registration slipped through the
pre-mirror window. `countLocalUsers` now unions both tables (verified:
`users=0, auth_users=1` already returns `mode: login` and 403s on both
public register paths; internal-token manual creation works, wrong token
refused). Session expiry redirect confirmed in `App.tsx`
(401 → null → `/auth/sign-in?redirect=…`). OAuth happy paths remain
unverifiable without real GitHub/Google credentials — providers are
env-gated and `disableImplicitSignUp` means OAuth cannot bypass the
gate by creating accounts.
**Gate**: a fresh instance can register → create project → connect GitHub repo
→ push → see build → see deploy → view logs → roll back, with zero manual API