starter code
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Package ratelimit enforces per-key and per-end-user request ceilings.
|
||||
//
|
||||
// Redis is authoritative for the live window; api.usage_daily holds completed
|
||||
// days. A Redis flush therefore loses at most the window in progress, which for
|
||||
// abuse detection is an acceptable trade.
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Result is what a limiter says about one request.
|
||||
type Result struct {
|
||||
Allowed bool
|
||||
Limit int
|
||||
Remaining int
|
||||
RetryAfter time.Duration
|
||||
}
|
||||
|
||||
// Limiter is deliberately narrow so the middleware can be tested without Redis,
|
||||
// and so a self-hoster running a single instance could drop in an in-process
|
||||
// implementation instead of standing up Redis at all.
|
||||
type Limiter interface {
|
||||
// Allow records one request against key and reports whether it may
|
||||
// proceed. Implementations must be atomic: a check-then-increment race
|
||||
// under load is exactly when the limit matters most.
|
||||
Allow(ctx context.Context, key string, limit int, window time.Duration) (Result, error)
|
||||
}
|
||||
|
||||
// Quota is the ceiling for one tier, from api.keys.quota_tier.
|
||||
type Quota struct {
|
||||
// Requests allowed per window for the key as a whole.
|
||||
KeyLimit int
|
||||
|
||||
// Requests allowed per window for a single end user within that key. One
|
||||
// Discord user should not be able to consume a whole self-hosted
|
||||
// instance's allowance, and a key whose traffic is 90% one user is the
|
||||
// enumeration signal worth alerting on.
|
||||
UserLimit int
|
||||
|
||||
Window time.Duration
|
||||
}
|
||||
|
||||
// Tiers maps api.keys.quota_tier to its ceiling. Values are a starting point,
|
||||
// not a considered policy: watch real usage before hardening them.
|
||||
var Tiers = map[string]Quota{
|
||||
"default": {KeyLimit: 600, UserLimit: 120, Window: time.Minute},
|
||||
"verified": {KeyLimit: 3000, UserLimit: 600, Window: time.Minute},
|
||||
"trusted": {KeyLimit: 12000, UserLimit: 2400, Window: time.Minute},
|
||||
"internal": {KeyLimit: 0, UserLimit: 0, Window: time.Minute}, // 0 = unlimited
|
||||
}
|
||||
|
||||
// QuotaFor falls back to the tightest tier for an unknown name, so a typo in
|
||||
// the database cannot accidentally grant unlimited access.
|
||||
func QuotaFor(tier string) Quota {
|
||||
if q, ok := Tiers[tier]; ok {
|
||||
return q
|
||||
}
|
||||
|
||||
return Tiers["default"]
|
||||
}
|
||||
Reference in New Issue
Block a user