add loger, access log and bans/whitelist

This commit is contained in:
nahakubuilde
2025-08-26 07:46:01 +01:00
parent e21a0b5b10
commit 4cafd9848f
10 changed files with 1163 additions and 60 deletions

View File

@@ -30,6 +30,87 @@ func (h *Handlers) LoginPage(c *gin.Context) {
})
}
// --- Failed login tracking and automatic IP bans ---
const (
defaultPwdFailuresThreshold = 5
defaultMFAFailuresThreshold = 10
defaultFailuresWindow = 30 * time.Minute
defaultBanDuration = 12 * time.Hour
)
// recordFailedAttempt logs a failed attempt and applies IP ban if thresholds exceeded.
func (h *Handlers) recordFailedAttempt(c *gin.Context, typ, username string, userID *int64) {
ip := c.ClientIP()
var uid interface{}
if userID != nil {
uid = *userID
}
// Insert failed attempt (best-effort)
_, _ = h.authSvc.DB.Exec(`INSERT INTO failed_logins (ip, user_id, username, type) VALUES (?,?,?,?)`, ip, uid, username, typ)
// Determine threshold using config overrides (fallback to defaults when zero)
threshold := h.config.PwdFailuresThreshold
if threshold <= 0 {
threshold = defaultPwdFailuresThreshold
}
if typ == "mfa" {
t2 := h.config.MFAFailuresThreshold
if t2 <= 0 {
t2 = defaultMFAFailuresThreshold
}
threshold = t2
}
// Count recent failures for this IP and type
var cnt int
// Window from config (minutes)
windowMinutes := h.config.FailuresWindowMinutes
if windowMinutes <= 0 {
windowMinutes = int(defaultFailuresWindow / time.Minute)
}
cutoff := time.Now().Add(-time.Duration(windowMinutes) * time.Minute)
_ = h.authSvc.DB.QueryRow(`SELECT COUNT(1) FROM failed_logins WHERE ip = ? AND type = ? AND created_at >= ?`, ip, typ, cutoff).Scan(&cnt)
if cnt >= threshold {
// Duration/permanence from config
banHours := h.config.AutoBanDurationHours
if banHours <= 0 {
banHours = int(defaultBanDuration / time.Hour)
}
makePermanent := h.config.AutoBanPermanent
var until interface{}
if makePermanent {
until = nil
} else {
until = time.Now().Add(time.Duration(banHours) * time.Hour)
}
reason := fmt.Sprintf("auto-ban: %s failures=%d within %d minutes", typ, cnt, windowMinutes)
// Upsert ban unless whitelisted or permanent existing
if makePermanent {
_, _ = h.authSvc.DB.Exec(`
INSERT INTO ip_bans (ip, reason, until, permanent, whitelisted, created_at, updated_at)
VALUES (?, ?, NULL, 1, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT(ip) DO UPDATE SET
reason=excluded.reason,
until=NULL,
permanent=1,
updated_at=CURRENT_TIMESTAMP
WHERE ip_bans.whitelisted = 0
`, ip, reason)
} else {
_, _ = h.authSvc.DB.Exec(`
INSERT INTO ip_bans (ip, reason, until, permanent, whitelisted, created_at, updated_at)
VALUES (?, ?, ?, 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT(ip) DO UPDATE SET
reason=excluded.reason,
until=excluded.until,
permanent=0,
updated_at=CURRENT_TIMESTAMP
WHERE ip_bans.whitelisted = 0 AND ip_bans.permanent = 0
`, ip, reason, until)
}
}
}
// isAllDigits returns true if s consists only of ASCII digits 0-9
func isAllDigits(s string) bool {
if s == "" {
@@ -65,6 +146,8 @@ func (h *Handlers) MFALoginVerify(c *gin.Context) {
code := strings.TrimSpace(c.PostForm("code"))
if len(code) != 6 || !isAllDigits(code) {
token, _ := c.Get("csrf_token")
// record failed MFA attempt
h.recordFailedAttempt(c, "mfa", "", nil)
c.HTML(http.StatusUnauthorized, "mfa", gin.H{
"app_name": h.config.AppName,
"csrf_token": token,
@@ -84,11 +167,13 @@ func (h *Handlers) MFALoginVerify(c *gin.Context) {
uid, _ := uidAny.(int64)
var secret string
if err := h.authSvc.DB.QueryRow(`SELECT mfa_secret FROM users WHERE id = ?`, uid).Scan(&secret); err != nil || secret == "" {
h.recordFailedAttempt(c, "mfa", "", &uid)
c.HTML(http.StatusUnauthorized, "mfa", gin.H{"error": "MFA not enabled", "Page": "mfa", "ContentTemplate": "mfa_content", "ScriptsTemplate": "mfa_scripts", "app_name": h.config.AppName})
return
}
if !verifyTOTP(secret, code, time.Now()) {
token, _ := c.Get("csrf_token")
h.recordFailedAttempt(c, "mfa", "", &uid)
c.HTML(http.StatusUnauthorized, "mfa", gin.H{
"app_name": h.config.AppName,
"csrf_token": token,
@@ -227,6 +312,8 @@ func (h *Handlers) LoginPost(c *gin.Context) {
user, err := h.authSvc.Authenticate(username, password)
if err != nil {
token, _ := c.Get("csrf_token")
// record failed password attempt
h.recordFailedAttempt(c, "password", username, nil)
c.HTML(http.StatusUnauthorized, "login", gin.H{
"app_name": h.config.AppName,
"csrf_token": token,