@@ -23,7 +23,11 @@ use totp_rs::{Algorithm, Secret, TOTP};
2323
2424use chrono:: { Duration , Utc } ;
2525
26- use crate :: { auth, middleware:: AuthContext , AppState } ;
26+ use crate :: {
27+ auth,
28+ middleware:: { AuthContext , check_rate_limit_with_fallback, extract_client_ip, HeaderMap } ,
29+ AppState ,
30+ } ;
2731
2832// ── Constants ────────────────────────────────────────────────────────────────
2933
@@ -109,6 +113,21 @@ async fn setup(
109113 Extension ( auth_ctx) : Extension < AuthContext > ,
110114 State ( state) : State < Arc < AppState > > ,
111115) -> NexusResult < Json < SetupResponse > > {
116+ // Rate limiting: 5 TOTP setup attempts per user per hour (prevents enumeration)
117+ let ip = extract_client_ip ( & headers) ;
118+ check_rate_limit_with_fallback (
119+ state. db . redis . as_ref ( ) ,
120+ format ! ( "rl:2fa:setup:user:{}" , auth. user_id) ,
121+ 5 ,
122+ 3600 ,
123+ ) . await ?;
124+ check_rate_limit_with_fallback (
125+ state. db . redis . as_ref ( ) ,
126+ format ! ( "rl:2fa:setup:ip:{ip}" ) ,
127+ 10 ,
128+ 3600 ,
129+ ) . await ?;
130+
112131 let user = users:: find_by_id ( & state. db . pool , auth_ctx. user_id )
113132 . await ?
114133 . ok_or ( NexusError :: NotFound { resource : "User" . into ( ) } ) ?;
@@ -167,6 +186,21 @@ async fn enable(
167186 State ( state) : State < Arc < AppState > > ,
168187 Json ( body) : Json < EnableBody > ,
169188) -> NexusResult < ( ) > {
189+ // Rate limiting: 10 TOTP enable attempts per user per 10 minutes (prevents brute force on verification code)
190+ let ip = extract_client_ip ( & headers) ;
191+ check_rate_limit_with_fallback (
192+ state. db . redis . as_ref ( ) ,
193+ format ! ( "rl:2fa:enable:user:{}" , auth. user_id) ,
194+ 10 ,
195+ 600 ,
196+ ) . await ?;
197+ check_rate_limit_with_fallback (
198+ state. db . redis . as_ref ( ) ,
199+ format ! ( "rl:2fa:enable:ip:{ip}" ) ,
200+ 20 ,
201+ 600 ,
202+ ) . await ?;
203+
170204 let user = users:: find_by_id ( & state. db . pool , auth_ctx. user_id )
171205 . await ?
172206 . ok_or ( NexusError :: NotFound { resource : "User" . into ( ) } ) ?;
@@ -214,6 +248,21 @@ async fn disable(
214248 State ( state) : State < Arc < AppState > > ,
215249 Json ( body) : Json < DisableBody > ,
216250) -> NexusResult < ( ) > {
251+ // Rate limiting: 10 TOTP disable attempts per user per 10 minutes
252+ let ip = extract_client_ip ( & headers) ;
253+ check_rate_limit_with_fallback (
254+ state. db . redis . as_ref ( ) ,
255+ format ! ( "rl:2fa:disable:user:{}" , auth. user_id) ,
256+ 10 ,
257+ 600 ,
258+ ) . await ?;
259+ check_rate_limit_with_fallback (
260+ state. db . redis . as_ref ( ) ,
261+ format ! ( "rl:2fa:disable:ip:{ip}" ) ,
262+ 20 ,
263+ 600 ,
264+ ) . await ?;
265+
217266 let user = users:: find_by_id ( & state. db . pool , auth_ctx. user_id )
218267 . await ?
219268 . ok_or ( NexusError :: NotFound { resource : "User" . into ( ) } ) ?;
@@ -286,6 +335,22 @@ async fn verify_mfa(
286335 State ( state) : State < Arc < AppState > > ,
287336 Json ( body) : Json < VerifyMfaBody > ,
288337) -> NexusResult < Json < AuthResponse > > {
338+ // CRITICAL: Rate limiting on MFA verification to prevent brute force attacks
339+ // 5 attempts per user per 5 minutes (stricter than other endpoints)
340+ let ip = extract_client_ip ( & headers) ;
341+ check_rate_limit_with_fallback (
342+ state. db . redis . as_ref ( ) ,
343+ format ! ( "rl:2fa:verify:user:{}" , body. user_id) ,
344+ 5 ,
345+ 300 ,
346+ ) . await ?;
347+ check_rate_limit_with_fallback (
348+ state. db . redis . as_ref ( ) ,
349+ format ! ( "rl:2fa:verify:ip:{ip}" ) ,
350+ 10 ,
351+ 300 ,
352+ ) . await ?;
353+
289354 let config = nexus_common:: config:: get ( ) ;
290355
291356 // Validate the challenge token
@@ -400,6 +465,21 @@ async fn regenerate_backup_codes(
400465 State ( state) : State < Arc < AppState > > ,
401466 Json ( body) : Json < RegenerateBody > ,
402467) -> NexusResult < Json < RegenerateResponse > > {
468+ // Rate limiting: 3 backup code regenerations per user per hour
469+ let ip = extract_client_ip ( & headers) ;
470+ check_rate_limit_with_fallback (
471+ state. db . redis . as_ref ( ) ,
472+ format ! ( "rl:2fa:regen:user:{}" , auth. user_id) ,
473+ 3 ,
474+ 3600 ,
475+ ) . await ?;
476+ check_rate_limit_with_fallback (
477+ state. db . redis . as_ref ( ) ,
478+ format ! ( "rl:2fa:regen:ip:{ip}" ) ,
479+ 5 ,
480+ 3600 ,
481+ ) . await ?;
482+
403483 let user = users:: find_by_id ( & state. db . pool , auth_ctx. user_id )
404484 . await ?
405485 . ok_or ( NexusError :: NotFound { resource : "User" . into ( ) } ) ?;
0 commit comments