summaryrefslogtreecommitdiffstats
path: root/internal/ratelimit/config.go
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-14 08:58:57 -0800
committerbndw <ben@bdw.to>2026-02-14 08:58:57 -0800
commitf0169fa1f9d2e2a5d1c292b9080da10ef0878953 (patch)
treec85d31dfbf270fe4ebbe2c53bdbb96c0a0a45ace /internal/ratelimit/config.go
parent44aa0591b0eed7851e961ea17bd1c9601570ac24 (diff)
feat: implement per-user rate limiting with token bucket algorithm
Add comprehensive rate limiting package that works seamlessly with NIP-98 authentication. Features: - Token bucket algorithm (allows bursts, smooth average rate) - Per-pubkey limits for authenticated users - Per-IP limits for unauthenticated users (fallback) - Method-specific overrides (e.g., stricter for PublishEvent) - Per-user custom limits (VIP/admin tiers) - Standard gRPC interceptors (chain after auth) - Automatic cleanup of idle limiters - Statistics tracking (allowed/denied/denial rate) Configuration options: - Default rate limits and burst sizes - Method-specific overrides - User-specific overrides (with method overrides) - Skip methods (health checks, public endpoints) - Skip users (admins, monitoring) - Configurable cleanup intervals Performance: - In-memory (200 bytes per user) - O(1) lookups with sync.RWMutex - ~85ns per rate limit check - Periodic cleanup to free memory Returns gRPC ResourceExhausted (HTTP 429) when limits exceeded. Includes comprehensive tests, benchmarks, and detailed README with usage examples, configuration reference, and security considerations.
Diffstat (limited to 'internal/ratelimit/config.go')
-rw-r--r--internal/ratelimit/config.go153
1 files changed, 153 insertions, 0 deletions
diff --git a/internal/ratelimit/config.go b/internal/ratelimit/config.go
new file mode 100644
index 0000000..132c96b
--- /dev/null
+++ b/internal/ratelimit/config.go
@@ -0,0 +1,153 @@
1package ratelimit
2
3import "time"
4
5// Config configures the rate limiter behavior.
6type Config struct {
7 // RequestsPerSecond is the default rate limit in requests per second.
8 // This applies to authenticated users (identified by pubkey).
9 // Default: 10
10 RequestsPerSecond float64
11
12 // BurstSize is the maximum burst size (token bucket capacity).
13 // Allows users to make burst requests up to this limit.
14 // Default: 20
15 BurstSize int
16
17 // IPRequestsPerSecond is the rate limit for unauthenticated users.
18 // These are identified by IP address.
19 // Typically set lower than authenticated user limits.
20 // Default: 5
21 IPRequestsPerSecond float64
22
23 // IPBurstSize is the burst size for IP-based rate limiting.
24 // Default: 10
25 IPBurstSize int
26
27 // MethodLimits provides per-method rate limit overrides.
28 // Key is the full gRPC method name (e.g., "/nostr.v1.NostrRelay/PublishEvent")
29 // If not specified, uses the default RequestsPerSecond and BurstSize.
30 MethodLimits map[string]MethodLimit
31
32 // UserLimits provides per-user custom rate limits.
33 // Key is the pubkey. Useful for VIP/premium users or admins.
34 // If not specified, uses the default limits.
35 UserLimits map[string]UserLimit
36
37 // SkipMethods is a list of gRPC methods that bypass rate limiting.
38 // Useful for health checks or public endpoints.
39 // Example: []string{"/grpc.health.v1.Health/Check"}
40 SkipMethods []string
41
42 // SkipUsers is a list of pubkeys that bypass rate limiting.
43 // Useful for admins or monitoring services.
44 SkipUsers []string
45
46 // CleanupInterval is how often to remove idle rate limiters from memory.
47 // Limiters that haven't been used recently are removed to save memory.
48 // Default: 5 minutes
49 CleanupInterval time.Duration
50
51 // MaxIdleTime is how long a limiter can be idle before being cleaned up.
52 // Default: 10 minutes
53 MaxIdleTime time.Duration
54}
55
56// MethodLimit defines rate limits for a specific gRPC method.
57type MethodLimit struct {
58 RequestsPerSecond float64
59 BurstSize int
60}
61
62// UserLimit defines custom rate limits for a specific user (pubkey).
63type UserLimit struct {
64 // RequestsPerSecond is the default rate for this user.
65 RequestsPerSecond float64
66
67 // BurstSize is the burst size for this user.
68 BurstSize int
69
70 // MethodLimits provides per-method overrides for this user.
71 // Allows fine-grained control like "VIP user gets 100 req/s for queries
72 // but still only 5 req/s for publishes"
73 MethodLimits map[string]MethodLimit
74}
75
76// DefaultConfig returns the default rate limit configuration.
77func DefaultConfig() *Config {
78 return &Config{
79 RequestsPerSecond: 10,
80 BurstSize: 20,
81 IPRequestsPerSecond: 5,
82 IPBurstSize: 10,
83 CleanupInterval: 5 * time.Minute,
84 MaxIdleTime: 10 * time.Minute,
85 }
86}
87
88// Validate checks if the configuration is valid.
89func (c *Config) Validate() error {
90 if c.RequestsPerSecond <= 0 {
91 c.RequestsPerSecond = 10
92 }
93 if c.BurstSize <= 0 {
94 c.BurstSize = 20
95 }
96 if c.IPRequestsPerSecond <= 0 {
97 c.IPRequestsPerSecond = 5
98 }
99 if c.IPBurstSize <= 0 {
100 c.IPBurstSize = 10
101 }
102 if c.CleanupInterval <= 0 {
103 c.CleanupInterval = 5 * time.Minute
104 }
105 if c.MaxIdleTime <= 0 {
106 c.MaxIdleTime = 10 * time.Minute
107 }
108 return nil
109}
110
111// GetLimitForMethod returns the rate limit for a specific method and user.
112// Precedence: UserLimit.MethodLimit > MethodLimit > UserLimit > Default
113func (c *Config) GetLimitForMethod(pubkey, method string) (requestsPerSecond float64, burstSize int) {
114 // Check user-specific method limit first (highest precedence)
115 if userLimit, ok := c.UserLimits[pubkey]; ok {
116 if methodLimit, ok := userLimit.MethodLimits[method]; ok {
117 return methodLimit.RequestsPerSecond, methodLimit.BurstSize
118 }
119 }
120
121 // Check global method limit
122 if methodLimit, ok := c.MethodLimits[method]; ok {
123 return methodLimit.RequestsPerSecond, methodLimit.BurstSize
124 }
125
126 // Check user-specific default limit
127 if userLimit, ok := c.UserLimits[pubkey]; ok {
128 return userLimit.RequestsPerSecond, userLimit.BurstSize
129 }
130
131 // Fall back to global default
132 return c.RequestsPerSecond, c.BurstSize
133}
134
135// ShouldSkipMethod returns true if the method should bypass rate limiting.
136func (c *Config) ShouldSkipMethod(method string) bool {
137 for _, skip := range c.SkipMethods {
138 if skip == method {
139 return true
140 }
141 }
142 return false
143}
144
145// ShouldSkipUser returns true if the user should bypass rate limiting.
146func (c *Config) ShouldSkipUser(pubkey string) bool {
147 for _, skip := range c.SkipUsers {
148 if skip == pubkey {
149 return true
150 }
151 }
152 return false
153}