feat(auth): implement strict and secure user registration
This commit is contained in:
81
internal/shared/middleware/ratelimit.go
Normal file
81
internal/shared/middleware/ratelimit.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type visitor struct {
|
||||
attempts int
|
||||
lastSeen time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
visitors = make(map[string]*visitor)
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
go cleanupVisitors()
|
||||
}
|
||||
|
||||
func cleanupVisitors() {
|
||||
for {
|
||||
time.Sleep(time.Minute)
|
||||
mu.Lock()
|
||||
for ip, v := range visitors {
|
||||
if time.Since(v.lastSeen) > 3*time.Minute {
|
||||
delete(visitors, ip)
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// getIP attempts to get the real IP, falling back to RemoteAddr
|
||||
func getIP(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
ips := strings.Split(xff, ",")
|
||||
return strings.TrimSpace(ips[0])
|
||||
}
|
||||
if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
|
||||
return realIP
|
||||
}
|
||||
ip := r.RemoteAddr
|
||||
if colonIdx := strings.LastIndex(ip, ":"); colonIdx != -1 {
|
||||
ip = ip[:colonIdx]
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// RateLimitAuth limits login/register attempts to prevent brute force
|
||||
func RateLimitAuth(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ip := getIP(r)
|
||||
|
||||
mu.Lock()
|
||||
v, exists := visitors[ip]
|
||||
if !exists {
|
||||
visitors[ip] = &visitor{1, time.Now()}
|
||||
} else {
|
||||
// Reset attempts if it's been more than a minute
|
||||
if time.Since(v.lastSeen) > time.Minute {
|
||||
v.attempts = 0
|
||||
}
|
||||
v.attempts++
|
||||
v.lastSeen = time.Now()
|
||||
}
|
||||
|
||||
// If more than 5 attempts within a minute, block
|
||||
if exists && v.attempts > 5 {
|
||||
mu.Unlock()
|
||||
http.Error(w, "Too many requests. Please try again later.", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user