ratelimit

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package ratelimit owns the cross-surface counter used by S35's rate-limit middleware. It generalizes S05's auth/throttle pattern against the shared rate_limits table so any new surface (API, search, git transports) plugs in with a single Allow() call.

Backend: Postgres-backed fixed-window counter via sqlc UPSERT. At launch scale this is well within Postgres's comfort zone and avoids introducing a Redis dependency. Move only if profiling demands it (S36).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClientIP

func ClientIP(r *http.Request, trustForwarded bool) (netip.Addr, bool)

ClientIP returns the parsed client IP using the same rules as IPKey. Used by signup-throttle's CIDR keying.

func StampHeaders

func StampHeaders(w http.ResponseWriter, d Decision)

StampHeaders writes the standard X-RateLimit-* headers from a Decision. Exposed so handlers that run a manual Allow can apply the same stamp without going through Middleware.

Types

type Decision

type Decision struct {
	Allowed    bool
	Remaining  int           // hits left in the current window (post-increment)
	Limit      int           // == Policy.Max, surfaced for the X-RateLimit-Limit header
	ResetIn    time.Duration // wall-clock until the current window rolls over
	RetryAfter time.Duration // 0 when Allowed; otherwise the wait the client should respect
}

Decision is the verdict from Allow.

type KeyFunc

type KeyFunc func(*http.Request) string

KeyFunc derives the per-request rate-limit key. Common choices: remote IP (anon), user id (authed), token id (PAT), repo id+user (per-repo), etc. Returning "" skips the limiter (caller should only return "" when there's nothing meaningful to throttle on).

func IPKey

func IPKey(trustForwarded bool) KeyFunc

IPKey is the canonical anonymous-request keyer: extracts the client IP from r.RemoteAddr or the X-Forwarded-For header. The trust-XFF flag should be set ONLY when the deployment runs behind a CDN/proxy we control; otherwise an attacker can spoof the header and dodge IP-keyed limits.

type Limiter

type Limiter struct {
	// contains filtered or unexported fields
}

Limiter is the package's primary handle. Construct with New.

func New

func New(pool *pgxpool.Pool) *Limiter

New wires a Limiter against a pool. The pool is required; constructing with nil panics so callers fail at boot, not at first request.

func (*Limiter) Allow

func (l *Limiter) Allow(ctx context.Context, p Policy, key string) (Decision, error)

Allow increments the (scope, key) counter and reports whether the caller is under or over the configured Max. Returns the post- increment Remaining + the time until the current window rolls.

On a Postgres error the request is allowed (fail-open). The caller is expected to log the error; refusing service over a transient counter glitch would be worse than the brief over-limit window.

func (*Limiter) AllowSignupIP

func (l *Limiter) AllowSignupIP(ctx context.Context, ip netip.Addr, max int, window time.Duration) (Decision, error)

AllowSignupIP is the inet-keyed sibling of Allow against the signup_ip_throttle table. ip is masked to /24 (v4) or /48 (v6) so a single residential allocation shares one counter — matches GitHub's approach (per-/24 signup throttle).

func (*Limiter) Middleware

func (l *Limiter) Middleware(p Policy, key KeyFunc) func(http.Handler) http.Handler

Middleware wraps next with a per-request Allow check. On allow, X-RateLimit-* headers are stamped on the response; on deny, the response is 429 with Retry-After.

`name` is the per-route identifier surfaced in logs and lets the admin observability pages disambiguate scopes that share the same counter (e.g. "api:anon" used by both /api/repos and /api/users).

type Policy

type Policy struct {
	Scope  string        // e.g. "api:anon", "search", "git:https"
	Max    int           // hits permitted within Window
	Window time.Duration // window length (15s, 1m, 1h, …)
}

Policy declares a per-(scope, key) limit: at most Max hits within the rolling Window.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL