mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
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:
1 parent
572ff8dce3
commit
e127c62b0f
2 files changed
+32
-5
No files matched your search
+12
-3
@@ -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
|
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
|
in the failed-deploy log tail — not silent. Revisit if a service-level
|
||||||
host-port config is ever added.
|
host-port config is ever added.
|
||||||
- [ ] Auth hardening: e2e verify bootstrap gate (first user registers, second
|
- [x] Auth hardening: e2e-verified against a live stack (scratch Postgres +
|
||||||
is refused, manual creation works), OAuth happy paths for GitHub +
|
Redis + real Better Auth sidecar). Fixed a real gate hole: sign-ups write
|
||||||
Google, session expiry redirect.
|
`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
|
**Gate**: a fresh instance can register → create project → connect GitHub repo
|
||||||
→ push → see build → see deploy → view logs → roll back, with zero manual API
|
→ push → see build → see deploy → view logs → roll back, with zero manual API
|
||||||
|
|||||||
@@ -264,8 +264,7 @@ func handleRegister(c *gin.Context) {
|
|||||||
db := c.MustGet("db").(*database.DB)
|
db := c.MustGet("db").(*database.DB)
|
||||||
jwtSecret := c.MustGet("jwt_secret").(string)
|
jwtSecret := c.MustGet("jwt_secret").(string)
|
||||||
|
|
||||||
var count int
|
count, err := countLocalUsers(db)
|
||||||
err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
||||||
return
|
return
|
||||||
@@ -432,6 +431,25 @@ func countLocalUsers(db *database.DB) (int, error) {
|
|||||||
if err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err != nil {
|
if err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
// Better Auth writes to auth_users; a row there only mirrors into users on
|
||||||
|
// the first authenticated call. Count it too or a second public sign-up
|
||||||
|
// slips through the bootstrap window. UNION by email avoids double-counting
|
||||||
|
// mirrored users.
|
||||||
|
var authTable *string
|
||||||
|
if err := db.QueryRow("SELECT to_regclass('auth_users')").Scan(&authTable); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if authTable != nil && *authTable != "" {
|
||||||
|
if err := db.QueryRow(`
|
||||||
|
SELECT COUNT(*) FROM (
|
||||||
|
SELECT email FROM users
|
||||||
|
UNION
|
||||||
|
SELECT email FROM auth_users
|
||||||
|
) u
|
||||||
|
`).Scan(&count); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user