fix(06-07): make limiter admission atomic

- Replace split store checks with one mutex-guarded Attempt operation
- Use domainless trusted-client keys for anonymous inline throttles
- Preserve fixed-window headers, expiry, stacking, and principal isolation
This commit is contained in:
Jakub Zych
2026-09-20 17:02:53 +02:00
parent 5bcd7ba011
commit 6852a8f8c3
4 changed files with 56 additions and 83 deletions

View File

@@ -93,8 +93,8 @@ func (l *FixedWindowLimiter) Middleware(param string) pact.Middleware {
return
}
key := b.Key(r)
if l.store.TooManyAttempts(key, b.Max) {
retryAfter := l.store.AvailableIn(key)
allowed, attempts, retryAfter := l.store.Attempt(key, b.Max, b.Decay)
if !allowed {
secs := int(retryAfter / time.Second)
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Retry-After", strconv.Itoa(secs))
@@ -105,7 +105,6 @@ func (l *FixedWindowLimiter) Middleware(param string) pact.Middleware {
_, _ = w.Write([]byte(tooManyAttemptsBody))
return
}
attempts := l.store.Hit(key, b.Decay)
remaining := b.Max - attempts
if remaining < 0 {
remaining = 0
@@ -157,11 +156,7 @@ func (l *FixedWindowLimiter) resolve(param string) (Bucket, error) {
return "u:" + strconv.FormatUint(uint64(u.ID), 10)
}
}
host := ""
if r != nil {
host = r.Host
}
return host + "|" + ClientIP(r, trusted)
return "inline:domainless|" + ClientIP(r, trusted)
},
}
l.inline[param] = b