diff --git a/cp/app/Auth/Auth.php b/cp/app/Auth/Auth.php index 8034e60..6caf170 100644 --- a/cp/app/Auth/Auth.php +++ b/cp/app/Auth/Auth.php @@ -13,6 +13,7 @@ use Pinga\Auth\ResetDisabledException; use Pinga\Auth\TokenExpiredException; use Pinga\Auth\TooManyRequestsException; use Pinga\Auth\UserAlreadyExistsException; +use Pinga\Auth\UnknownIdException; use RobThree\Auth\TwoFactorAuth; use RobThree\Auth\Providers\Qr\BaconQrCodeProvider; @@ -350,7 +351,7 @@ class Auth $auth->admin()->logInAsUserById($userId); redirect()->route('home')->with('success','Registrar impersonation started'); } - catch (UnknownIdException $e) { + catch (UnknownIdException $e) { redirect()->route('registrars')->with('error','Unknown ID'); } catch (EmailNotVerifiedException $e) { @@ -373,6 +374,21 @@ class Auth } } + /** + * Log out user from all other sessions except the current one + * @throws \Pinga\Auth\AuthError + */ + public static function logoutEverywhereElse() { + $auth = self::$auth; + try { + $auth->logOutEverywhereElse(); + redirect()->route('profile')->with('success', 'Logged out from all other devices'); + } + catch (NotLoggedInException $e) { + redirect()->route('login')->with('error', 'You are not logged in'); + } + } + /** * @throws \Pinga\Auth\AuthError */ diff --git a/cp/app/Controllers/ProfileController.php b/cp/app/Controllers/ProfileController.php index eb62989..d8e54b2 100644 --- a/cp/app/Controllers/ProfileController.php +++ b/cp/app/Controllers/ProfileController.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Container\ContainerInterface; use RobThree\Auth\TwoFactorAuth; use RobThree\Auth\Providers\Qr\BaconQrCodeProvider; +use App\Auth\Auth; class ProfileController extends Controller { @@ -235,4 +236,34 @@ class ProfileController extends Controller } } + public function logoutEverywhereElse(Request $request, Response $response) + { + global $container; + $db = $container->get('db'); + + $currentDateTime = new \DateTime(); + $currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp + $db->insert( + 'users_audit', + [ + 'user_id' => $_SESSION['auth_user_id'], + 'user_event' => 'user.logout.everywhere', + 'user_resource' => 'control.panel', + 'user_agent' => $_SERVER['HTTP_USER_AGENT'], + 'user_ip' => get_client_ip(), + 'user_location' => get_client_location(), + 'event_time' => $currentDate, + 'user_data' => json_encode([ + 'remaining_session_id' => session_id(), + 'logged_out_sessions' => 'All other sessions terminated', + 'previous_ip' => $_SESSION['previous_ip'] ?? null, + 'previous_user_agent' => $_SESSION['previous_user_agent'] ?? null, + 'timestamp' => $currentDate, + ]) + ] + ); + + Auth::logoutEverywhereElse(); + } + } \ No newline at end of file diff --git a/cp/resources/views/admin/dashboard/index.twig b/cp/resources/views/admin/dashboard/index.twig index b5d188b..721f7d7 100644 --- a/cp/resources/views/admin/dashboard/index.twig +++ b/cp/resources/views/admin/dashboard/index.twig @@ -41,8 +41,8 @@
-
+ {% include 'partials/flash.twig' %}
{% if registrars %}
@@ -252,7 +252,6 @@
-
{% include 'partials/footer.twig' %} diff --git a/cp/resources/views/admin/profile/profile.twig b/cp/resources/views/admin/profile/profile.twig index d1ba21b..706da8f 100644 --- a/cp/resources/views/admin/profile/profile.twig +++ b/cp/resources/views/admin/profile/profile.twig @@ -38,6 +38,9 @@ + @@ -203,6 +206,16 @@ {% endif %} {% endif %} +
+

{{ __('Security') }}

+

{{ __('If you’ve logged in on multiple devices or browsers and want to ensure your account remains secure, you can log out from all other sessions except this one. This will end access from any other logged-in devices.') }}

+
+ {{ csrf.field | raw }} + +
+

{{ __('User Audit Log') }}

{{ __('Track and review all user activities in your account below. Monitor logins, profile changes, and other key actions to ensure security and transparency.') }}

diff --git a/cp/routes/web.php b/cp/routes/web.php index 5fcee35..bd67d8c 100644 --- a/cp/routes/web.php +++ b/cp/routes/web.php @@ -149,6 +149,7 @@ $app->group('', function ($route) { $route->get('/profile', ProfileController::class .':profile')->setName('profile'); $route->post('/profile/2fa', ProfileController::class .':activate2fa')->setName('activate2fa'); + $route->post('/profile/logout-everywhere', ProfileController::class . ':logoutEverywhereElse')->setName('profile.logout.everywhere'); $route->get('/webauthn/register/challenge', ProfileController::class . ':getRegistrationChallenge')->setName('webauthn.register.challenge'); $route->post('/webauthn/register/verify', ProfileController::class . ':verifyRegistration')->setName('webauthn.register.verify');