package api import ( "context" "log/slog" "atlas9.dev/c/core/throttle" ) // AccountThrottle provides brute force protection for login attempts // using per-IP and per-email token buckets. type AccountThrottle struct { Bucket throttle.TokenBucket FailOpen bool EmailCapacity int EmailRefillRate float64 IPCapacity int IPRefillRate float64 } // NewAccountThrottle creates an AccountThrottle with sensible defaults: // - Per-email: capacity 5, refill 1 token per 3 minutes // - Per-IP: capacity 20, refill 1 token per minute func NewAccountThrottle(bucket throttle.TokenBucket) *AccountThrottle { return &AccountThrottle{ Bucket: bucket, FailOpen: true, EmailCapacity: 5, EmailRefillRate: 1.0 / 180, // 1 token per 3 minutes (in tokens/second) IPCapacity: 20, IPRefillRate: 1.0 / 60, // 1 token per minute (in tokens/second) } } // Check consumes a token from both the IP and email buckets. // Returns true if the request is allowed, false if either bucket is exhausted. func (t *AccountThrottle) Check(ctx context.Context, email string) bool { emailOK, err := t.Bucket.Take(ctx, "account:email:"+email, t.EmailCapacity, t.EmailRefillRate) if err != nil { slog.Error("account throttle: email check", "email", email, "err", err) return t.FailOpen } return emailOK }