Description
crates/api/src/lib.rs::build_router applies a permissive CORS layer but no rate limiting whatsoever, anywhere. crates/api/src/auth.rs::login already takes care to return an identical error for 'no such user' and 'wrong password' to prevent account enumeration, but with zero throttling, an attacker can still attempt unlimited password guesses per second against any known email address. This ticket adds basic per-IP middleware-level rate limiting as a first line of defense; a more complete persistent lockout policy is tracked separately as one of the hard/complex issues.
Requirements and Context
- Add
tower_governor (or tower-http's own rate-limit primitives if the team prefers staying within the already-depended-on tower-http, but tower_governor is the more common choice for per-IP limiting on axum/tower) as a new dependency scoped to crates/api.
- Apply a per-IP limit (propose something like 10 requests/minute, but justify your chosen figures in the PR description) specifically to
/v1/auth/login and /v1/auth/signup, not the whole API.
- Ensure the limiter correctly identifies the client IP behind a proxy consistent with the existing
crate::audit::client_ip header-parsing convention (X-Forwarded-For then X-Real-IP) rather than defaulting to the immediate TCP peer address, which would be the load balancer's IP in production.
Suggested Execution
Branch: feat/api/auth-rate-limiting-middleware
Implement Changes
- Add the rate-limiting layer in
crates/api/src/lib.rs::build_router, scoped only to the auth routes (e.g. via a nested Router with its own .layer(...), merged into the main router).
Test and Commit
login_is_rate_limited_after_the_configured_threshold_per_ip.
signup_is_rate_limited_after_the_configured_threshold_per_ip.
rate_limit_is_scoped_per_ip_not_global (two distinct simulated client IPs each get their own budget).
unrelated_routes_are_not_rate_limited_by_the_auth_layer.
- Run
cargo test -p octo-api locally before committing.
Example Commit Message
feat(api): add per-IP rate limiting to the login and signup routes
Neither login nor signup had any request throttling, allowing unlimited
password-guessing attempts per second against any known email address
despite the enumeration-safe error message. Adds per-IP rate limiting scoped
to just these two routes as a first line of defense.
Guidelines
- Scope the limiter narrowly to the auth routes — do not rate-limit the whole API in this PR, since wallet/withdrawal/sponsor routes have their own distinct throttling needs better addressed by the separate persistent-lockout hard issue.
- Make sure the client-IP extraction matches the existing
X-Forwarded-For/X-Real-IP convention already used in crates/api/src/audit.rs::client_ip, so behavior is consistent across the codebase.
- Reference this issue with
Closes #<issue-number> in the PR description.
Description
crates/api/src/lib.rs::build_routerapplies a permissive CORS layer but no rate limiting whatsoever, anywhere.crates/api/src/auth.rs::loginalready takes care to return an identical error for 'no such user' and 'wrong password' to prevent account enumeration, but with zero throttling, an attacker can still attempt unlimited password guesses per second against any known email address. This ticket adds basic per-IP middleware-level rate limiting as a first line of defense; a more complete persistent lockout policy is tracked separately as one of the hard/complex issues.Requirements and Context
tower_governor(ortower-http's own rate-limit primitives if the team prefers staying within the already-depended-ontower-http, buttower_governoris the more common choice for per-IP limiting on axum/tower) as a new dependency scoped tocrates/api./v1/auth/loginand/v1/auth/signup, not the whole API.crate::audit::client_ipheader-parsing convention (X-Forwarded-ForthenX-Real-IP) rather than defaulting to the immediate TCP peer address, which would be the load balancer's IP in production.Suggested Execution
Branch:
feat/api/auth-rate-limiting-middlewareImplement Changes
crates/api/src/lib.rs::build_router, scoped only to the auth routes (e.g. via a nestedRouterwith its own.layer(...), merged into the main router).Test and Commit
login_is_rate_limited_after_the_configured_threshold_per_ip.signup_is_rate_limited_after_the_configured_threshold_per_ip.rate_limit_is_scoped_per_ip_not_global(two distinct simulated client IPs each get their own budget).unrelated_routes_are_not_rate_limited_by_the_auth_layer.cargo test -p octo-apilocally before committing.Example Commit Message
Guidelines
X-Forwarded-For/X-Real-IPconvention already used incrates/api/src/audit.rs::client_ip, so behavior is consistent across the codebase.Closes #<issue-number>in the PR description.