diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 546a635d..5ba2e762 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -73,12 +73,14 @@ class Kernel extends HttpKernel protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'admin' => \App\Http\Middleware\AdminOnly::class, - 'guest' => \App\Http\Middleware\RejectIfAuthenticated::class, + 'rejectIfAuthenticated' => \App\Http\Middleware\RejectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'rejectIfDemoMode' => \App\Http\Middleware\RejectIfDemoMode::class, 'rejectIfReverseProxy' => \App\Http\Middleware\RejectIfReverseProxy::class, 'RejectIfSsoOnlyAndNotForAdmin' => \App\Http\Middleware\RejectIfSsoOnlyAndNotForAdmin::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'kickOutInactiveUser' => \App\Http\Middleware\KickOutInactiveUser::class, + 'forceLogout' => \App\Http\Middleware\ForceLogout::class, // 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, // 'signed' => \App\Http\Middleware\ValidateSignature::class, ]; diff --git a/app/Http/Middleware/ForceLogout.php b/app/Http/Middleware/ForceLogout.php new file mode 100644 index 00000000..fea4399e --- /dev/null +++ b/app/Http/Middleware/ForceLogout.php @@ -0,0 +1,25 @@ +logoutCurrentDevice(); + } + + return $next($request); + } +} diff --git a/app/Listeners/Authentication/LogoutListener.php b/app/Listeners/Authentication/LogoutListener.php index ccb66b36..ad98a6b4 100644 --- a/app/Listeners/Authentication/LogoutListener.php +++ b/app/Listeners/Authentication/LogoutListener.php @@ -43,27 +43,30 @@ public function handle(mixed $event) : void * @var \App\Models\User */ $user = $event->user; - $ip = config('2fauth.proxy_headers.forIp') - ? $this->request->header(config('2fauth.proxy_headers.forIp'), $this->request->ip()) - : $this->request->ip(); - $userAgent = $this->request->userAgent(); - $log = $user->authentications() - ->whereIpAddress($ip) - ->whereUserAgent($userAgent) - ->whereGuard($event->guard) - ->orderByDesc('login_at') - ->first(); - if (! $log) { - $log = new AuthLog([ - 'ip_address' => $ip, - 'user_agent' => $userAgent, - 'guard' => $event->guard, - 'login_method' => $this->loginMethod(), - ]); - } + if ($user != null) { + $ip = config('2fauth.proxy_headers.forIp') + ? $this->request->header(config('2fauth.proxy_headers.forIp'), $this->request->ip()) + : $this->request->ip(); + $userAgent = $this->request->userAgent(); + $log = $user->authentications() + ->whereIpAddress($ip) + ->whereUserAgent($userAgent) + ->whereGuard($event->guard) + ->orderByDesc('login_at') + ->first(); + + if (! $log) { + $log = new AuthLog([ + 'ip_address' => $ip, + 'user_agent' => $userAgent, + 'guard' => $event->guard, + 'login_method' => $this->loginMethod(), + ]); + } - $log->logout_at = now(); - $user->authentications()->save($log); + $log->logout_at = now(); + $user->authentications()->save($log); + } } } diff --git a/routes/web.php b/routes/web.php index 4ce77b55..57307004 100644 --- a/routes/web.php +++ b/routes/web.php @@ -34,8 +34,9 @@ /** * Routes that only work for unauthenticated user (return an error otherwise) + * 'kickOutInactiveUser', */ -Route::group(['middleware' => ['guest', 'rejectIfDemoMode', 'RejectIfSsoOnlyAndNotForAdmin']], function () { +Route::group(['middleware' => ['rejectIfDemoMode', 'RejectIfSsoOnlyAndNotForAdmin', 'forceLogout']], function () { Route::post('user', [RegisterController::class, 'register'])->name('user.register'); Route::post('user/password/lost', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('user.password.lost'); Route::post('user/password/reset', [ResetPasswordController::class, 'reset'])->name('password.reset'); @@ -46,15 +47,15 @@ /** * Routes that can be requested max 10 times per minute by the same IP */ -Route::group(['middleware' => ['rejectIfDemoMode', 'throttle:10,1', 'RejectIfSsoOnlyAndNotForAdmin']], function () { +Route::group(['middleware' => ['rejectIfDemoMode', 'throttle:10,1', 'RejectIfSsoOnlyAndNotForAdmin', 'forceLogout']], function () { Route::post('webauthn/recover', [WebAuthnRecoveryController::class, 'recover'])->name('webauthn.recover'); }); /** * Routes that only work for unauthenticated user (return an error otherwise) - * that can be requested max 10 times per minute by the same IP + * that can be requested max 10 times per minute by the same IP 'kickOutInactiveUser', */ -Route::group(['middleware' => ['guest', 'throttle:10,1']], function () { +Route::group(['middleware' => ['forceLogout', 'throttle:10,1']], function () { Route::post('user/login', [LoginController::class, 'login'])->name('user.login')->middleware('RejectIfSsoOnlyAndNotForAdmin'); Route::post('webauthn/login', [WebAuthnLoginController::class, 'login'])->name('webauthn.login')->middleware('RejectIfSsoOnlyAndNotForAdmin'); diff --git a/tests/Feature/Http/Auth/ForgotPasswordControllerTest.php b/tests/Feature/Http/Auth/ForgotPasswordControllerTest.php index 339e94a7..89ccf18b 100644 --- a/tests/Feature/Http/Auth/ForgotPasswordControllerTest.php +++ b/tests/Feature/Http/Auth/ForgotPasswordControllerTest.php @@ -95,7 +95,7 @@ public function test_submit_email_password_request_in_demo_mode_returns_unauthor } #[Test] - public function test_submit_email_password_request_when_authenticated_returns_bad_request() + public function test_submit_email_password_request_when_authenticated_returns_ok() { /** * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable @@ -106,7 +106,7 @@ public function test_submit_email_password_request_when_authenticated_returns_ba ->json('POST', '/user/password/lost', [ 'email' => $user->email, ]) - ->assertStatus(400) + ->assertStatus(200) ->assertJsonStructure([ 'message', ]); diff --git a/tests/Feature/Http/Auth/LoginTest.php b/tests/Feature/Http/Auth/LoginTest.php index 33a42019..3b5489a1 100644 --- a/tests/Feature/Http/Auth/LoginTest.php +++ b/tests/Feature/Http/Auth/LoginTest.php @@ -250,10 +250,7 @@ public function test_user_login_already_authenticated_is_rejected() 'email' => $this->user->email, 'password' => self::PASSWORD, ]) - ->assertStatus(400) - ->assertJsonStructure([ - 'message', - ]); + ->assertStatus(200); } #[Test] diff --git a/tests/Feature/Http/Auth/WebAuthnLoginControllerTest.php b/tests/Feature/Http/Auth/WebAuthnLoginControllerTest.php index b3647a4d..153236d1 100644 --- a/tests/Feature/Http/Auth/WebAuthnLoginControllerTest.php +++ b/tests/Feature/Http/Auth/WebAuthnLoginControllerTest.php @@ -275,10 +275,7 @@ public function test_webauthn_login_already_authenticated_is_rejected() ->assertOk(); $this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE) - ->assertStatus(400) - ->assertJsonStructure([ - 'message', - ]); + ->assertStatus(200);; } #[Test]