77use App \Http \Controllers \Controller ;
88use App \Models \User ;
99use Illuminate \Http \Request ;
10+ use Illuminate \Support \Facades \RateLimiter ;
11+ use Illuminate \Validation \ValidationException ;
12+ use Illuminate \Support \Str ;
1013
1114class TwoFactorAuthChallengeController extends Controller
1215{
@@ -26,6 +29,9 @@ public function store(Request $request)
2629 // If we made it here, user is available via the EnsureTwoFactorChallengeSession middleware
2730 $ user = $ request ->two_factor_auth_user ;
2831
32+ // Ensure the 2FA challenge is not rate limited
33+ $ this ->ensureIsNotRateLimited ($ user );
34+
2935 // Handle one-time password (OTP) code
3036 if ($ request ->filled ('code ' )) {
3137 return $ this ->authenticateUsingCode ($ request , $ user );
@@ -53,9 +59,11 @@ protected function authenticateUsingCode(Request $request, User $user)
5359
5460 if ($ valid ) {
5561 app (CompleteTwoFactorAuthentication::class)($ user );
62+ RateLimiter::clear ($ this ->throttleKey ($ user ));
5663 return redirect ()->intended (route ('dashboard ' , absolute: false ));
5764 }
5865
66+ RateLimiter::hit ($ this ->throttleKey ($ user ));
5967 return back ()->withErrors (['code ' => __ ('The provided two factor authentication code was invalid. ' )]);
6068 }
6169
@@ -75,6 +83,7 @@ protected function authenticateUsingRecoveryCode(Request $request, User $user)
7583
7684 // If ProcessRecoveryCode returns false, the code was invalid
7785 if ($ updatedCodes === false ) {
86+ RateLimiter::hit ($ this ->throttleKey ($ user ));
7887 return back ()->withErrors (['recovery_code ' => __ ('The provided two factor authentication recovery code was invalid. ' )]);
7988 }
8089
@@ -84,8 +93,45 @@ protected function authenticateUsingRecoveryCode(Request $request, User $user)
8493 // Complete the authentication process
8594 app (CompleteTwoFactorAuthentication::class)($ user );
8695
96+ // Clear rate limiter after successful authentication
97+ RateLimiter::clear ($ this ->throttleKey ($ user ));
98+
8799 // Redirect to the intended page
88100 return redirect ()->intended (route ('dashboard ' , absolute: false ));
89101 }
102+
103+ /**
104+ * Ensure the 2FA challenge is not rate limited.
105+ *
106+ * @param \App\Models\User $user
107+ * @return void
108+ *
109+ * @throws \Illuminate\Validation\ValidationException
110+ */
111+ protected function ensureIsNotRateLimited (User $ user ): void
112+ {
113+ if (! RateLimiter::tooManyAttempts ($ this ->throttleKey ($ user ), 5 )) {
114+ return ;
115+ }
116+
117+ $ seconds = RateLimiter::availableIn ($ this ->throttleKey ($ user ));
118+
119+ throw ValidationException::withMessages ([
120+ 'code ' => __ ('Too many two factor authentication attempts. Please try again in :seconds seconds. ' , [
121+ 'seconds ' => $ seconds ,
122+ ]),
123+ ]);
124+ }
125+
126+ /**
127+ * Get the rate limiting throttle key for the given user.
128+ *
129+ * @param \App\Models\User $user
130+ * @return string
131+ */
132+ protected function throttleKey (User $ user ): string
133+ {
134+ return Str::transliterate ($ user ->id . '|2fa| ' . request ()->ip ());
135+ }
90136}
91137
0 commit comments