Skip to content

Commit 042038b

Browse files
feat: Harden login captcha gate by replacing request-body counter with server-side session counter (UserController::postLogin)
1 parent 9dda289 commit 042038b

4 files changed

Lines changed: 235 additions & 67 deletions

File tree

app/Http/Controllers/UserController.php

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
**/
1414

1515
use App\Http\Controllers\OpenId\DiscoveryController;
16+
use LaravelDoctrine\ORM\Facades\EntityManager;
1617
use RyanChandler\LaravelCloudflareTurnstile\Rules\Turnstile;
1718
use App\Jobs\RevokeUserGrantsOnExplicitLogout;
1819
use App\Http\Controllers\OpenId\OpenIdController;
@@ -396,8 +397,8 @@ public function postLogin()
396397
{
397398
$max_login_attempts_2_show_captcha = $this->server_configuration_service->getConfigValue("MaxFailed.LoginAttempts.2ShowCaptcha");
398399
$max_login_failed_attempts = intval($this->server_configuration_service->getConfigValue("MaxFailed.Login.Attempts"));
399-
$login_attempts = 0;
400-
$username = '';
400+
$login_attempts = (int) Session::get('captcha_failed_attempts', 0);
401+
$username = '';
401402
$user = null;
402403

403404
try
@@ -411,7 +412,6 @@ public function postLogin()
411412
if (isset($data['password']))
412413
$data['password'] = trim($data['password']);
413414

414-
$login_attempts = intval(Request::input('login_attempts'));
415415
// Build the validation constraint set.
416416
$rules = [
417417
'username' => 'required|email',
@@ -436,7 +436,15 @@ public function postLogin()
436436
$connection = $data['connection'] ?? null;
437437

438438
try {
439+
$user = $this->auth_service->getUserByUsername($username);
439440
if ($flow == "password" && $this->auth_service->login($username, $password, $remember)) {
441+
if ($user)
442+
{
443+
$user->setLoginFailedAttempt(0);
444+
EntityManager::flush();
445+
}
446+
Session::forget('captcha_failed_attempts');
447+
Session::save();
440448
return $this->login_strategy->postLogin();
441449
}
442450

@@ -468,15 +476,23 @@ public function postLogin()
468476

469477
$otpClaim = OAuth2OTP::fromParams($username, $connection, $password);
470478
$this->auth_service->loginWithOTP($otpClaim, $client);
479+
if ($user)
480+
{
481+
$user->setLoginFailedAttempt(0);
482+
EntityManager::flush();
483+
}
484+
Session::forget('captcha_failed_attempts');
485+
Session::save();
471486
return $this->login_strategy->postLogin();
472487
}
473488
} catch (AuthenticationException $ex) {
474489
// failed login attempt...
475490

476-
$user = $this->auth_service->getUserByUsername($username);
477-
if (!is_null($user)) {
478-
$login_attempts = $user->getLoginFailedAttempt();
479-
}
491+
$login_attempts = $user ? $user->updateLoginFailedAttempt() : $login_attempts + 1;
492+
Session::put('captcha_failed_attempts', $login_attempts);
493+
Session::save();
494+
495+
// User.loginFailedAttempt drives account lockout (persisted by auth_service).
480496

481497
return $this->login_strategy->errorLogin
482498
(
@@ -525,6 +541,9 @@ public function postLogin()
525541
Log::warning($ex1);
526542

527543
$user = $this->auth_service->getUserByUsername($username);
544+
$login_attempts = $user ? $user->updateLoginFailedAttempt() : $login_attempts + 1;
545+
Session::put('captcha_failed_attempts', $login_attempts);
546+
Session::save();
528547

529548
$response_data = [
530549
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,

resources/js/login/login.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ const PasswordInputForm = ({
185185
<input type="hidden" value={userNameValue} id="username" name="username"/>
186186
<input type="hidden" value={csrfToken} id="_token" name="_token"/>
187187
<input type="hidden" value="password" id="flow" name="flow"/>
188-
<input type="hidden" value={loginAttempts} id="login_attempts" name="login_attempts"/>
189188
{shouldShowCaptcha() && captchaPublicKey &&
190189
<Turnstile
191190
className={styles.turnstile}
@@ -271,7 +270,6 @@ const OTPInputForm = ({
271270
<input type="hidden" value="otp" id="flow" name="flow"/>
272271
<input type="hidden" value={otpCode} id="password" name="password"/>
273272
<input type="hidden" value="email" id="connection" name="connection"/>
274-
<input type="hidden" value={loginAttempts} id="login_attempts" name="login_attempts"/>
275273
{shouldShowCaptcha() && captchaPublicKey &&
276274
<Turnstile
277275
className={styles.turnstile}

resources/views/auth/login.blade.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@
6262
config.maxLoginFailedAttempts = {{Session::get("max_login_failed_attempts")}};
6363
@endif
6464
65-
@if(Session::has('login_attempts'))
66-
config.loginAttempts = {{Session::get("login_attempts")}};
65+
@if(Session::has('captcha_failed_attempts'))
66+
config.loginAttempts = {{Session::get("captcha_failed_attempts")}};
6767
@endif
6868
6969
@if(Session::has('user_is_active'))

0 commit comments

Comments
 (0)