max / makenotwork
1 file changed,
+21 insertions,
-1 deletion
| @@ -37,6 +37,10 @@ use crate::AppState; | |||
| 37 | 37 | const WRITE_RATE_LIMIT_MS: u64 = 500; | |
| 38 | 38 | const WRITE_RATE_LIMIT_BURST: u32 = 10; | |
| 39 | 39 | ||
| 40 | + | /// Search endpoint: burst 5, then 1/sec — full-text + trigram queries are expensive. | |
| 41 | + | const SEARCH_RATE_LIMIT_MS: u64 = 1000; | |
| 42 | + | const SEARCH_RATE_LIMIT_BURST: u32 = 5; | |
| 43 | + | ||
| 40 | 44 | /// Build the forum route tree. | |
| 41 | 45 | pub fn forum_routes(state: AppState) -> Router { | |
| 42 | 46 | let write_rate_limit = std::sync::Arc::new( | |
| @@ -85,6 +89,22 @@ pub fn forum_routes(state: AppState) -> Router { | |||
| 85 | 89 | config: write_rate_limit, | |
| 86 | 90 | }); | |
| 87 | 91 | ||
| 92 | + | // Search — rate limited per IP (expensive full-text queries) | |
| 93 | + | let search_rate_limit = std::sync::Arc::new( | |
| 94 | + | GovernorConfigBuilder::default() | |
| 95 | + | .key_extractor(SmartIpKeyExtractor) | |
| 96 | + | .per_millisecond(SEARCH_RATE_LIMIT_MS) | |
| 97 | + | .burst_size(SEARCH_RATE_LIMIT_BURST) | |
| 98 | + | .finish() | |
| 99 | + | .expect("search rate limiter config"), | |
| 100 | + | ); | |
| 101 | + | ||
| 102 | + | let search_routes = Router::new() | |
| 103 | + | .route("/search", get(search::search_handler)) | |
| 104 | + | .route_layer(GovernorLayer { | |
| 105 | + | config: search_rate_limit, | |
| 106 | + | }); | |
| 107 | + | ||
| 88 | 108 | // GET routes + auth + health — no rate limiting | |
| 89 | 109 | let read_routes = Router::new() | |
| 90 | 110 | .route("/", get(forum::forum_directory)) | |
| @@ -101,7 +121,6 @@ pub fn forum_routes(state: AppState) -> Router { | |||
| 101 | 121 | .route("/p/{slug}/{category}/{thread_id}/edit", get(forum::edit_thread_form)) | |
| 102 | 122 | .route("/tracked", get(tracking::tracked_threads_page)) | |
| 103 | 123 | .route("/about/tracking", get(tracking::tracking_info_page)) | |
| 104 | - | .route("/search", get(search::search_handler)) | |
| 105 | 124 | .route("/_admin", get(admin::admin_dashboard)) | |
| 106 | 125 | .route("/auth/login", get(auth::login)) | |
| 107 | 126 | .route("/auth/callback", get(auth::callback)) | |
| @@ -111,6 +130,7 @@ pub fn forum_routes(state: AppState) -> Router { | |||
| 111 | 130 | .route("/api/health", get(health)); | |
| 112 | 131 | ||
| 113 | 132 | read_routes | |
| 133 | + | .merge(search_routes) | |
| 114 | 134 | .merge(write_routes) | |
| 115 | 135 | .fallback(not_found_handler) | |
| 116 | 136 | .with_state(state) |