feat(auth): lock logins after repeated failures, trust proxies explicitly (FLAW-09)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
Failed logins are counted per account+address (3), per account (20) and per address (30) within 15 minutes; a full bucket answers 429 with Retry-After for 15 minutes. Locking the account+address bucket first means an attacker cannot lock the owner out from elsewhere. Unknown names count like accounts. Success clears the account buckets, a password reset (mail or admin) lifts the lock. LOGIN_PROTECTION=false switches it off. chi's RealIP believed X-Forwarded-For from anyone. The client address now comes from the headers only when the connection is from a trusted proxy: TRUSTED_PROXIES=private (default: loopback, Docker, LAN), all (old behaviour), none, or CIDRs. A proxy that sends no client address is logged once and per-address limits are skipped for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b9eb62caf
commit
11c14fa9ff
12 files changed
+503
-30
No files matched your search
+32
-25
@@ -2,29 +2,34 @@ package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"strconv"
|
||||
"encoding/hex"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port string
|
||||
DataDir string
|
||||
DatabasePath string
|
||||
JWTSecret string
|
||||
BaseURL string
|
||||
Port string
|
||||
DataDir string
|
||||
DatabasePath string
|
||||
JWTSecret string
|
||||
BaseURL string
|
||||
// BaseURLSet: BASE_URL was configured explicitly. Links in mails (password reset) are only built
|
||||
// from it, never from the request's Host header.
|
||||
BaseURLSet bool
|
||||
BaseURLSet bool
|
||||
AllowRegistration bool
|
||||
LogLevel string
|
||||
Debug bool
|
||||
DemoMode bool
|
||||
// DemoResetMinutes: in demo mode the database is reset to the sample data this often (0 = never)
|
||||
DemoResetMinutes int
|
||||
// TrustedProxies: whose X-Forwarded-For/X-Real-IP is believed ("private", "all", "none" or CIDRs)
|
||||
TrustedProxies string
|
||||
// DisableLoginProtection: LOGIN_PROTECTION=false switches off locking logins after repeated
|
||||
// failures (the zero value keeps it on)
|
||||
DisableLoginProtection bool
|
||||
|
||||
// Optional SMTP environment variables (can also be overridden/configured in DB system_settings)
|
||||
SMTPHost string
|
||||
@@ -154,24 +159,26 @@ func Load() *Config {
|
||||
smtpSkipVerify := strings.ToLower(getEnv("SMTP_SKIP_VERIFY", "false")) == "true"
|
||||
|
||||
return &Config{
|
||||
Port: port,
|
||||
DataDir: dataDir,
|
||||
DatabasePath: dbPath,
|
||||
JWTSecret: jwtSecret,
|
||||
BaseURL: baseURL,
|
||||
BaseURLSet: strings.TrimSpace(os.Getenv("BASE_URL")) != "",
|
||||
AllowRegistration: allowReg,
|
||||
LogLevel: logLevel,
|
||||
Debug: debug,
|
||||
DemoMode: demoMode,
|
||||
DemoResetMinutes: demoResetMinutes(),
|
||||
SMTPHost: smtpHost,
|
||||
SMTPPort: smtpPort,
|
||||
SMTPUser: smtpUser,
|
||||
SMTPPass: smtpPass,
|
||||
SMTPFrom: smtpFrom,
|
||||
SMTPTLS: smtpTLS,
|
||||
SMTPSkipVerify: smtpSkipVerify,
|
||||
Port: port,
|
||||
DataDir: dataDir,
|
||||
DatabasePath: dbPath,
|
||||
JWTSecret: jwtSecret,
|
||||
BaseURL: baseURL,
|
||||
BaseURLSet: strings.TrimSpace(os.Getenv("BASE_URL")) != "",
|
||||
AllowRegistration: allowReg,
|
||||
LogLevel: logLevel,
|
||||
Debug: debug,
|
||||
DemoMode: demoMode,
|
||||
DemoResetMinutes: demoResetMinutes(),
|
||||
TrustedProxies: getEnv("TRUSTED_PROXIES", "private"),
|
||||
DisableLoginProtection: strings.ToLower(getEnv("LOGIN_PROTECTION", "true")) == "false",
|
||||
SMTPHost: smtpHost,
|
||||
SMTPPort: smtpPort,
|
||||
SMTPUser: smtpUser,
|
||||
SMTPPass: smtpPass,
|
||||
SMTPFrom: smtpFrom,
|
||||
SMTPTLS: smtpTLS,
|
||||
SMTPSkipVerify: smtpSkipVerify,
|
||||
Legal: LegalTexts{
|
||||
ImprintURL: legalURL("LEGAL_IMPRINT_URL"),
|
||||
ImprintContent: legalContent("LEGAL_IMPRINT_CONTENT"),
|
||||
|
||||
Reference in new issue
Block a user