package ratelimit import ( "context" "crypto/sha256" "fmt" "github.com/redis/go-redis/v9" ) type Redis struct{ client *redis.Client } func NewRedis(client *redis.Client) *Redis { return &Redis{client: client} } var loginCounter = redis.NewScript(`local n = redis.call('INCR', KEYS[1]); if n == 1 then redis.call('EXPIRE', KEYS[1], 900) end; return n`) // Login permits at most 20 attempts per IP and 10 per login in a 15-minute window. // Redis errors fail closed. Neither credential nor IP is stored in plaintext keys. func (r *Redis) Login(ctx context.Context, ip, login string) (bool, error) { for _, item := range []struct { kind, value string limit int64 }{{"ip", ip, 20}, {"name", login, 10}} { key := fmt.Sprintf("veyra:login:%s:%x", item.kind, sha256.Sum256([]byte(item.value))) n, err := loginCounter.Run(ctx, r.client, []string{key}).Int64() if err != nil { return false, err } if n > item.limit { return false, nil } } return true, nil }