server: attach per-IP rate limit to guest download route
download_routes now attaches a lenient GovernorLayer (GUEST_DOWNLOAD_RATE_LIMIT_PER_SEC=2 / _BURST=60, >0 compile assert); the misleading 'more lenient rate limit' comment that claimed a layer that wasn't wired is replaced with an accurate one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 files changed,
+18 insertions,
-2 deletions
| 157 |
157 |
|
// Guest checkout (public, no auth): burst 10, then 1/sec
|
| 158 |
158 |
|
pub const GUEST_CHECKOUT_RATE_LIMIT_PER_SEC: u64 = 1;
|
| 159 |
159 |
|
pub const GUEST_CHECKOUT_RATE_LIMIT_BURST: u32 = 10;
|
|
160 |
+ |
// Guest download (public, no auth, token-gated): deliberately lenient — a buyer
|
|
161 |
+ |
// may pull several files in a row — but still a per-IP ceiling so the endpoint
|
|
162 |
+ |
// can't be hammered anonymously. Burst 60, then 2/sec.
|
|
163 |
+ |
pub const GUEST_DOWNLOAD_RATE_LIMIT_PER_SEC: u64 = 2;
|
|
164 |
+ |
pub const GUEST_DOWNLOAD_RATE_LIMIT_BURST: u32 = 60;
|
| 160 |
165 |
|
// License key validation (public): burst 20, then 5/sec
|
| 161 |
166 |
|
pub const LICENSE_KEY_RATE_LIMIT_MS: u64 = 200;
|
| 162 |
167 |
|
pub const LICENSE_KEY_RATE_LIMIT_BURST: u32 = 20;
|
| 464 |
469 |
|
const _: () = assert!(UPLOAD_RATE_LIMIT_BURST > 0);
|
| 465 |
470 |
|
const _: () = assert!(OAUTH_RATE_LIMIT_BURST > 0);
|
| 466 |
471 |
|
const _: () = assert!(OAUTH_TOKEN_RATE_LIMIT_BURST > 0);
|
|
472 |
+ |
const _: () = assert!(GUEST_CHECKOUT_RATE_LIMIT_BURST > 0);
|
|
473 |
+ |
const _: () = assert!(GUEST_DOWNLOAD_RATE_LIMIT_BURST > 0);
|
| 467 |
474 |
|
|
| 468 |
475 |
|
// Rate-limit burst ordering: read > write > auth
|
| 469 |
476 |
|
const _: () = assert!(API_READ_RATE_LIMIT_BURST > API_WRITE_RATE_LIMIT_BURST);
|
| 479 |
479 |
|
config: guest_checkout_rate_limit,
|
| 480 |
480 |
|
});
|
| 481 |
481 |
|
|
| 482 |
|
- |
// Guest download route — separate, more lenient rate limit
|
|
482 |
+ |
// Guest download route — separate, more lenient per-IP rate limit. Unauth'd
|
|
483 |
+ |
// and token-gated; the throttle is defense-in-depth against anonymous flooding
|
|
484 |
+ |
// (token entropy already makes enumeration impractical).
|
|
485 |
+ |
let download_rate_limit = crate::helpers::rate_limiter_per_sec(
|
|
486 |
+ |
constants::GUEST_DOWNLOAD_RATE_LIMIT_PER_SEC,
|
|
487 |
+ |
constants::GUEST_DOWNLOAD_RATE_LIMIT_BURST,
|
|
488 |
+ |
);
|
| 483 |
489 |
|
let download_routes = CsrfRouter::new()
|
| 484 |
|
- |
.route_get("/download/{token}", get(guest_checkout::guest_download));
|
|
490 |
+ |
.route_get("/download/{token}", get(guest_checkout::guest_download))
|
|
491 |
+ |
.route_layer(GovernorLayer {
|
|
492 |
+ |
config: download_rate_limit,
|
|
493 |
+ |
});
|
| 485 |
494 |
|
|
| 486 |
495 |
|
write_routes
|
| 487 |
496 |
|
.merge(totp_sensitive_routes)
|