Cap concurrent password checks and count attempts before checking

Every argon2 run takes 64 MiB and nothing limited how many ran at once,
so parallel sign-in attempts could run the server out of memory (8 at
once used about 600 MB). At most two now run at once; at most 16
sign-ins wait for one, more get HTTP 429. 30 parallel sign-ins peaked
at 275 MB.

A sign-in attempt now counts toward the lockout before its password is
checked, so parallel attempts cannot get past it; a right password
takes its own attempt back. IPv6 addresses are locked out by /64.
This commit is contained in:
Daniel Redetzke
2026-10-05 00:23:05 +03:00
parent a59a095691
commit b54ff1b002
4 changed files with 136 additions and 34 deletions
+1 -1
View File
@@ -232,7 +232,7 @@ func (a *App) login(w http.ResponseWriter, r *http.Request) {
if err != nil {
slog.Warn("login failed", "user", in.Username, "remote", ip, "reason", err.Error())
code := http.StatusUnauthorized
if errors.Is(err, errLocked) {
if errors.Is(err, errLocked) || errors.Is(err, errBusy) {
code = http.StatusTooManyRequests
}
writeJSON(w, code, map[string]string{"error": err.Error()})