fix: /login throttles failed logins per client address behind trusted proxies (surf.ClientIP), like /register

Behind a reverse proxy every visitor had the proxy's RemoteAddr, so one client's failed logins locked the account for all. The throttle key is now surf.ClientIP with http.trusted_proxies, the expression /register uses; X-Forwarded-For from an untrusted peer is still ignored. RemoteIP is unchanged.
This commit is contained in:
Jakub Zych
2026-10-22 22:53:29 +02:00
parent 4bf44fc6ea
commit b6f8055c56
3 changed files with 118 additions and 7 deletions

View File

@@ -54,7 +54,9 @@ const invalidLoginBody = "Invalid email or password"
// as a wrong password and counts as a failed attempt for the throttle; an
// account that is also deactivated is refused before the restore, so it
// stays deactivated and gets no mail. With the key off such an account signs
// in as before. Activation gates login, deactivation does not.
// in as before. Activation gates login, deactivation does not. The throttle
// counts failed attempts per user and client address, which is
// surf.ClientIP with http.trusted_proxies, as for /register.
func Login(app *backpack.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gdb, secret, ok := sessionDeps(w, app)
@@ -74,7 +76,7 @@ func Login(app *backpack.App) http.HandlerFunc {
writeOpaque500(w)
return
}
ip := remoteIP(r)
ip := surf.ClientIP(r, surf.TrustedProxies(appConfig(app)))
if user != nil {
if err := classes.RejectIfThrottled(ctx, gdb, user, ip); err != nil {
if classes.IsLoginRejected(err) {
@@ -1159,9 +1161,10 @@ func requestURL(r *http.Request) string {
return scheme + "://" + r.Host + r.URL.Path
}
// RemoteIP returns the host part of r.RemoteAddr, the address /login uses
// for its throttle (r.RemoteAddr itself when it has no port, "" when it is
// empty). /register records surf.ClientIP with the trusted proxies instead.
// RemoteIP returns the host part of r.RemoteAddr (r.RemoteAddr itself when
// it has no port, "" when it is empty), with no proxy handling. /login and
// /register key their throttles on surf.ClientIP with the trusted proxies;
// RemoteIP stays for callers that want the connection address.
func RemoteIP(r *http.Request) string {
return remoteIP(r)
}