fix(07): WR-02 move rate limit check before validation in LoginPostHandler
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b61f36f17e
commit
ab12bf0962
1 changed files with 18 additions and 15 deletions
|
|
@ -165,8 +165,8 @@ func LoginPageHandler() http.HandlerFunc {
|
|||
}
|
||||
|
||||
// LoginPostHandler handles POST /login:
|
||||
// 1. Validate email + password format (specific errors per D-20/D-25)
|
||||
// 2. Rate-limit check before any expensive work (D-16, D-18; gates DoS T-2-14)
|
||||
// 1. Rate-limit check before any other work — even invalid inputs consume tokens (D-16, D-18; gates DoS T-2-14)
|
||||
// 2. Validate email + password format (specific errors per D-20/D-25)
|
||||
// 3. Look up user by email
|
||||
// 4. Verify argon2id password hash
|
||||
// 5. Rotate session (D-10 fixation defense)
|
||||
|
|
@ -189,14 +189,27 @@ func LoginPostHandler(deps AuthDeps) http.HandlerFunc {
|
|||
email := strings.TrimSpace(r.PostFormValue("email"))
|
||||
password := r.PostFormValue("password")
|
||||
|
||||
// 2. Rate-limit check FIRST — even on invalid input, to prevent enumeration
|
||||
// via timing differences on malformed payloads (D-16, T-2-14).
|
||||
// Key uses lowercased raw email + IP so malformed inputs consume rate tokens.
|
||||
// Limiter may be nil in tests that don't exercise rate-limiting.
|
||||
ip := clientIP(r)
|
||||
key := strings.ToLower(email) + ":" + ip
|
||||
if deps.Limiter != nil && !deps.Limiter.Allow(key) {
|
||||
var rateLimitErrs templates.LoginErrors
|
||||
rateLimitErrs.General = "Too many attempts. Try again in a minute."
|
||||
renderLoginError(w, r, templates.LoginForm{Email: email}, rateLimitErrs, http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
var errs templates.LoginErrors
|
||||
|
||||
// 2. Validate email (specific error — D-20: validation errors ARE specific).
|
||||
// 3. Validate email (specific error — D-20: validation errors ARE specific).
|
||||
if _, err := mail.ParseAddress(email); err != nil {
|
||||
errs.Email = "Enter a valid email address"
|
||||
}
|
||||
|
||||
// 3. Validate password length BEFORE calling Verify (DoS guard T-2-14, D-25).
|
||||
// 4. Validate password length BEFORE calling Verify (DoS guard T-2-14, D-25).
|
||||
if len(password) < 12 {
|
||||
errs.Password = "Password must be at least 12 characters"
|
||||
} else if len(password) > 128 {
|
||||
|
|
@ -208,18 +221,8 @@ func LoginPostHandler(deps AuthDeps) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// 4. Normalize email for rate-limit key and DB lookup.
|
||||
// 5. Normalize email for DB lookup.
|
||||
normalized := strings.ToLower(email)
|
||||
ip := clientIP(r)
|
||||
key := normalized + ":" + ip
|
||||
|
||||
// 5. Rate-limit check BEFORE user lookup and argon2 work (D-16, T-2-14).
|
||||
// Limiter may be nil in tests that don't exercise rate-limiting.
|
||||
if deps.Limiter != nil && !deps.Limiter.Allow(key) {
|
||||
errs.General = "Too many attempts. Try again in a minute."
|
||||
renderLoginError(w, r, templates.LoginForm{Email: email}, errs, http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
// 6. Look up user by email.
|
||||
user, err := deps.Queries.GetUserByEmail(ctx, normalized)
|
||||
|
|
|
|||
Loading…
Reference in a new issue