fix(auth): 公开登录门按来源限速并设全站发信上限,冷却表定期清理

This commit is contained in:
Lemon-miaow committed 2026-09-24 15:51:42 +08:00
1 parent 15f729ffea
commit c4e4953f3d
26 files changed
+1141 -64

No files matched your search

+10 -6
View File
@@ -24,12 +24,9 @@ import (
// this separator).
// - No principal. The throttle cannot key off a user id (there is none yet); it
// keys off the typed recipient address, the same anti-bomb dimension the onboard
// start uses. Per-source (client-IP) aggregate limiting is deliberately NOT done
// here: cooldownLimiter is a one-per-window primitive, so keying it on client IP
// would false-positive on shared egress (CGNAT / office NAT), and behind
// Cloudflare RemoteAddr is the proxy anyway. The only real harm — bombing one
// mailbox — is already bounded per recipient; volumetric per-source limiting
// belongs at the edge.
// start uses. Volume from one client is bounded separately by the per-address
// token bucket every public auth door sits behind (throttleAuthDoor), and total
// mail by the install-wide mail budget (ratelimit.go).
// - Refuse staff. Like handleBindRedeem this public door provably never mints a
// session for an admin identity: op.console stays behind Zero Trust (and its own
// in-game approval gate). The refusal happens only AFTER a valid code is
@@ -84,6 +81,13 @@ func (a *API) handleLoginEmailStart(w http.ResponseWriter, r *http.Request) {
return
}
// The install-wide mail budget is checked before the address is resolved,
// so while it is spent every address gets the same 429.
if err := a.checkMailBudget(); err != nil {
writeError(w, r, err)
return
}
// Atomically reserve the per-recipient cooldown BEFORE any work, so a burst of
// truly concurrent starts yields exactly one winner and each admitted send is one
// real, non-idempotent email. The key is namespaced apart from the onboard door's