Inital build
Some checks failed
Automated Container Build / build-and-push (push) Failing after 12s

This commit is contained in:
Elijah 2026-05-22 12:29:43 -07:00
parent fa2be029a2
commit 724d70e58b
3339 changed files with 1075535 additions and 0 deletions

View file

@ -0,0 +1,104 @@
package middleware
import (
"sync"
"time"
"github.com/gofiber/fiber/v2"
)
type loginAttempt struct {
Count int
LockedAt time.Time
}
// BruteForceGuard tracks login attempts per IP and enforces exponential backoff.
type BruteForceGuard struct {
mu sync.Mutex
attempts map[string]*loginAttempt
maxAttempts int
lockoutSecs int
}
func NewBruteForceGuard(maxAttempts, lockoutSecs int) *BruteForceGuard {
guard := &BruteForceGuard{
attempts: make(map[string]*loginAttempt),
maxAttempts: maxAttempts,
lockoutSecs: lockoutSecs,
}
// Clean up stale entries every 10 minutes
go func() {
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for range ticker.C {
guard.cleanup()
}
}()
return guard
}
// Check returns a Fiber handler that blocks requests from locked-out IPs.
func (g *BruteForceGuard) Check() fiber.Handler {
return func(c *fiber.Ctx) error {
ip := c.IP()
g.mu.Lock()
attempt, exists := g.attempts[ip]
g.mu.Unlock()
if exists && attempt.Count >= g.maxAttempts {
lockoutDuration := time.Duration(g.lockoutSecs) * time.Second
if time.Since(attempt.LockedAt) < lockoutDuration {
remaining := lockoutDuration - time.Since(attempt.LockedAt)
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"error": "too many login attempts, account temporarily locked",
"retry_after": int(remaining.Seconds()),
})
}
// Lockout expired, reset
g.mu.Lock()
delete(g.attempts, ip)
g.mu.Unlock()
}
return c.Next()
}
}
// RecordFailure increments the failure count for an IP.
func (g *BruteForceGuard) RecordFailure(ip string) {
g.mu.Lock()
defer g.mu.Unlock()
attempt, exists := g.attempts[ip]
if !exists {
g.attempts[ip] = &loginAttempt{Count: 1, LockedAt: time.Now()}
return
}
attempt.Count++
if attempt.Count >= g.maxAttempts {
attempt.LockedAt = time.Now()
}
}
// RecordSuccess resets the failure count for an IP after a successful login.
func (g *BruteForceGuard) RecordSuccess(ip string) {
g.mu.Lock()
defer g.mu.Unlock()
delete(g.attempts, ip)
}
func (g *BruteForceGuard) cleanup() {
g.mu.Lock()
defer g.mu.Unlock()
cutoff := time.Duration(g.lockoutSecs*2) * time.Second
for ip, attempt := range g.attempts {
if time.Since(attempt.LockedAt) > cutoff {
delete(g.attempts, ip)
}
}
}