feat: add login attempt rate limiting

This commit is contained in:
OpenClaw Agent
2026-09-17 02:20:38 +08:00
parent dcf01e8f4c
commit 5fbf9fe252
+31
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
@@ -15,6 +16,30 @@ import (
const sessionCookie = "am_session"
var loginLimiter = struct {
sync.Mutex
attempts map[string][]time.Time
}{attempts: map[string][]time.Time{}}
func loginAllowed(key string) bool {
now := time.Now()
loginLimiter.Lock()
defer loginLimiter.Unlock()
items := loginLimiter.attempts[key]
cutoff := now.Add(-15 * time.Minute)
kept := items[:0]
for _, t := range items {
if t.After(cutoff) {
kept = append(kept, t)
}
}
if len(kept) >= 10 {
return false
}
loginLimiter.attempts[key] = append(kept, now)
return true
}
func hashToken(token string) string {
sum := sha256.Sum256([]byte(token))
return hex.EncodeToString(sum[:])
@@ -104,6 +129,11 @@ func (a *App) login(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "username and password required"})
return
}
key := c.ClientIP() + ":" + strings.ToLower(strings.TrimSpace(in.Username))
if !loginAllowed(key) {
c.JSON(http.StatusTooManyRequests, gin.H{"error": "too many login attempts; try again later"})
return
}
var user User
if a.db.Where("username = ?", strings.TrimSpace(in.Username)).First(&user).Error != nil || user.Status != UserActive || checkPassword(user.PasswordHash, in.Password) != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
@@ -120,6 +150,7 @@ func (a *App) login(c *gin.Context) {
return
}
http.SetCookie(c.Writer, &http.Cookie{Name: sessionCookie, Value: token, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: c.Request.TLS != nil, Expires: session.ExpiresAt})
user.PasswordHash = ""
c.JSON(http.StatusOK, gin.H{"user": user})
}