package main import ( "context" "atlas9.dev/c/core/throttle" ) // LoginThrottle provides brute force protection for login attempts // using per-IP and per-email token buckets. type LoginThrottle struct { Bucket throttle.TokenBucket EmailCapacity int EmailRefillRate float64 IPCapacity int IPRefillRate float64 } // NewLoginThrottle creates a LoginThrottle with sensible defaults: // - Per-email: capacity 5, refill 1 token per 3 minutes // - Per-IP: capacity 20, refill 1 token per minute func NewLoginThrottle(bucket throttle.TokenBucket) *LoginThrottle { return &LoginThrottle{ Bucket: bucket, 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 *LoginThrottle) Check(ctx context.Context, ip, email string) (bool, error) { ipAllowed, err := t.Bucket.Take(ctx, "login:ip:"+ip, t.IPCapacity, t.IPRefillRate) if err != nil { return false, err } emailAllowed, err := t.Bucket.Take(ctx, "login:email:"+email, t.EmailCapacity, t.EmailRefillRate) if err != nil { return false, err } return ipAllowed && emailAllowed, nil } // ResetEmail refills the email bucket on successful login. func (t *LoginThrottle) ResetEmail(ctx context.Context, email string) error { return t.Bucket.Reset(ctx, "login:email:"+email) }