Skip to main content

cloudillo_core/rate_limit/
config.rs

1// SPDX-FileCopyrightText: Szilárd Hajba
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4//! Rate Limiting Configuration
5//!
6//! Configuration structs for hierarchical rate limiting with dual-tier
7//! (short-term burst + long-term sustained) limits.
8
9use std::num::NonZeroU32;
10use std::time::Duration;
11
12/// Dual-tier rate limit configuration for a single address level
13#[derive(Clone, Debug)]
14pub struct RateLimitTierConfig {
15	// Short-term: burst protection (per-second)
16	/// Requests per second
17	pub short_term_rps: NonZeroU32,
18	/// Burst capacity for short-term
19	pub short_term_burst: NonZeroU32,
20
21	// Long-term: sustained abuse protection (per-hour)
22	/// Requests per hour
23	pub long_term_rph: NonZeroU32,
24	/// Burst capacity for long-term
25	pub long_term_burst: NonZeroU32,
26}
27
28impl RateLimitTierConfig {
29	pub fn new(short_rps: u32, short_burst: u32, long_rph: u32, long_burst: u32) -> Self {
30		Self {
31			short_term_rps: NonZeroU32::new(short_rps).unwrap_or(NonZeroU32::MIN),
32			short_term_burst: NonZeroU32::new(short_burst).unwrap_or(NonZeroU32::MIN),
33			long_term_rph: NonZeroU32::new(long_rph).unwrap_or(NonZeroU32::MIN),
34			long_term_burst: NonZeroU32::new(long_burst).unwrap_or(NonZeroU32::MIN),
35		}
36	}
37}
38
39/// Configuration for an endpoint category with all address levels
40#[derive(Clone, Debug)]
41pub struct EndpointCategoryConfig {
42	/// Category name (e.g., "auth", "federation", "general")
43	pub name: &'static str,
44	/// IPv4 individual (/32) limits
45	pub ipv4_individual: RateLimitTierConfig,
46	/// IPv4 network (/24) limits
47	pub ipv4_network: RateLimitTierConfig,
48	/// IPv6 subnet (/64) limits
49	pub ipv6_subnet: RateLimitTierConfig,
50	/// IPv6 provider (/48) limits
51	pub ipv6_provider: RateLimitTierConfig,
52}
53
54/// Main rate limit configuration
55#[derive(Clone, Debug)]
56pub struct RateLimitConfig {
57	/// Auth endpoints (login, register, password reset)
58	pub auth: EndpointCategoryConfig,
59	/// DAV endpoints (CardDAV / CalDAV sync)
60	pub dav: EndpointCategoryConfig,
61	/// Federation endpoints (inbox)
62	pub federation: EndpointCategoryConfig,
63	/// General public endpoints (profile, refs)
64	pub general: EndpointCategoryConfig,
65	/// Full-text search — its own bucket: unauthenticated, and far more expensive
66	/// per request than the rest of `general`
67	pub search: EndpointCategoryConfig,
68	/// WebSocket endpoints
69	pub websocket: EndpointCategoryConfig,
70	/// Maximum number of IPs to track (memory limit)
71	pub max_tracked_ips: usize,
72	/// How long to retain entries after last access
73	pub entry_ttl: Duration,
74}
75
76impl Default for RateLimitConfig {
77	fn default() -> Self {
78		Self {
79			auth: EndpointCategoryConfig {
80				name: "auth",
81				// Auth: strict limits but allow a few rapid page refreshes
82				ipv4_individual: RateLimitTierConfig::new(5, 10, 60, 60),
83				ipv4_network: RateLimitTierConfig::new(15, 30, 200, 200),
84				ipv6_subnet: RateLimitTierConfig::new(5, 10, 60, 60),
85				ipv6_provider: RateLimitTierConfig::new(15, 30, 200, 200),
86			},
87			dav: EndpointCategoryConfig {
88				name: "dav",
89				// DAV: authenticated CardDAV / CalDAV sync. Clients like DAVx5 fire a
90				// burst of PROPFIND / REPORT per sync cycle (principal + each home set +
91				// each collection), and can poll every few minutes. Has to be generous
92				// enough for routine sync traffic while still bounded in case a token
93				// leaks — rate limiting runs before auth so this is our only brute-force
94				// defense on the DAV router.
95				ipv4_individual: RateLimitTierConfig::new(30, 60, 2000, 500),
96				ipv4_network: RateLimitTierConfig::new(60, 120, 5000, 1000),
97				ipv6_subnet: RateLimitTierConfig::new(30, 60, 2000, 500),
98				ipv6_provider: RateLimitTierConfig::new(60, 120, 5000, 1000),
99			},
100			federation: EndpointCategoryConfig {
101				name: "federation",
102				// Federation: moderate limits for inter-instance communication
103				ipv4_individual: RateLimitTierConfig::new(100, 200, 1000, 100),
104				ipv4_network: RateLimitTierConfig::new(500, 750, 5000, 500),
105				ipv6_subnet: RateLimitTierConfig::new(100, 200, 1000, 100),
106				ipv6_provider: RateLimitTierConfig::new(500, 750, 5000, 500),
107			},
108			general: EndpointCategoryConfig {
109				name: "general",
110				// General: relaxed limits for normal browsing
111				ipv4_individual: RateLimitTierConfig::new(300, 500, 5000, 500),
112				ipv4_network: RateLimitTierConfig::new(600, 1000, 50000, 5000),
113				ipv6_subnet: RateLimitTierConfig::new(300, 500, 5000, 500),
114				ipv6_provider: RateLimitTierConfig::new(600, 1000, 50000, 5000),
115			},
116			search: EndpointCategoryConfig {
117				name: "search",
118				// Tighter than `general`: an FTS5 scan plus a capped COUNT(*) over the
119				// whole corpus, reachable unauthenticated. The omnibox debounces at
120				// 250ms, so a live typist needs only a handful per second.
121				ipv4_individual: RateLimitTierConfig::new(30, 60, 600, 100),
122				ipv4_network: RateLimitTierConfig::new(60, 120, 2000, 300),
123				ipv6_subnet: RateLimitTierConfig::new(30, 60, 600, 100),
124				ipv6_provider: RateLimitTierConfig::new(60, 120, 2000, 300),
125			},
126			websocket: EndpointCategoryConfig {
127				name: "websocket",
128				// WebSocket: relaxed limits for collaborative scenarios (connections are long-lived)
129				ipv4_individual: RateLimitTierConfig::new(100, 200, 1000, 500),
130				ipv4_network: RateLimitTierConfig::new(100, 200, 1000, 500),
131				ipv6_subnet: RateLimitTierConfig::new(100, 200, 1000, 500),
132				ipv6_provider: RateLimitTierConfig::new(100, 200, 1000, 500),
133			},
134			max_tracked_ips: 100_000,
135			entry_ttl: Duration::from_hours(1),
136		}
137	}
138}
139
140/// Proof-of-Work counter configuration
141#[derive(Clone, Debug)]
142pub struct PowConfig {
143	/// Maximum counter value (caps PoW difficulty)
144	pub max_counter: u32,
145	/// Counter decay: decrease by 1 every N seconds of no violations
146	pub decay_interval_secs: u64,
147	/// LRU cache size for individual IPs
148	pub max_individual_entries: usize,
149	/// LRU cache size for networks
150	pub max_network_entries: usize,
151}
152
153impl Default for PowConfig {
154	fn default() -> Self {
155		Self {
156			max_counter: 10,           // Max "AAAAAAAAAA" required
157			decay_interval_secs: 3600, // 1 hour decay
158			max_individual_entries: 50_000,
159			max_network_entries: 10_000,
160		}
161	}
162}
163
164// vim: ts=4