35 lines
991 B
Go
35 lines
991 B
Go
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
|
|
}
|