Add page to view and delete user sessions

This commit is contained in:
Igor Scheller 2023-09-17 20:15:49 +02:00 committed by Michael Weimann
parent 102c8428c8
commit 5c59fec1cf
8 changed files with 242 additions and 0 deletions

View File

@ -41,6 +41,8 @@ $route->addGroup(
$route->get('/certificates', 'SettingsController@ifsgCertificate');
$route->post('/certificates', 'SettingsController@saveIfsgCertificate');
$route->get('/oauth', 'SettingsController@oauth');
$route->get('/sessions', 'SettingsController@sessions');
$route->post('/sessions', 'SettingsController@sessionsDelete');
}
);

View File

@ -145,6 +145,9 @@ msgstr "Bitte gib Dein geplantes Abreisedatum an, damit wir ein Gefühl für die
msgid "settings.profile.success"
msgstr "Einstellungen gespeichert."
msgid "settings.sessions.delete_success"
msgstr "Sitzung erfolgreich gelöscht."
msgid "faq.delete.success"
msgstr "FAQ Eintrag erfolgreich gelöscht."

View File

@ -1122,6 +1122,9 @@ msgstr "Senden"
msgid "Y-m-d H:i"
msgstr "d.m.Y H:i"
msgid "Y-m-d H:i:s"
msgstr "d.m.Y H:i:s"
msgid "mark as read"
msgstr "als gelesen markieren"
@ -1974,6 +1977,9 @@ msgstr "Vorschau"
msgid "form.delete"
msgstr "Löschen"
msgid "form.delete_all"
msgstr "Alle löschen"
msgid "form.updated"
msgstr "Aktualisiert"
@ -2201,6 +2207,21 @@ msgstr "Passwort wiederholen"
msgid "settings.password.success"
msgstr "Passwort wurde erfolgreich geändert."
msgid "settings.sessions"
msgstr "Sitzungen"
msgid "settings.sessions.info"
msgstr "Hier kannst Du deine Bowser-Sitzungen sehen und löschen."
msgid "settings.sessions.current"
msgstr "Aktuelle Sitzung"
msgid "settings.sessions.id"
msgstr "Sitzungs-ID"
msgid "settings.sessions.last_activity"
msgstr "Zuletzt verwendet"
msgid "settings.theme"
msgstr "Theme"

View File

@ -144,6 +144,9 @@ msgstr "Please enter your planned date of departure. "
msgid "settings.profile.success"
msgstr "Settings saved."
msgid "settings.sessions.delete_success"
msgstr "Session deleted successfully."
msgid "faq.delete.success"
msgstr "FAQ entry successfully deleted."

View File

@ -70,6 +70,9 @@ msgstr "Preview"
msgid "form.delete"
msgstr "Delete"
msgid "form.delete_all"
msgstr "Delete all"
msgid "form.updated"
msgstr "Updated"
@ -299,6 +302,21 @@ msgstr "Password confirmation"
msgid "settings.password.success"
msgstr "Password was changed successfully."
msgid "settings.sessions"
msgstr "Sessions"
msgid "settings.sessions.info"
msgstr "Here you can see and delete your browser sessions."
msgid "settings.sessions.current"
msgstr "Current session"
msgid "settings.sessions.id"
msgstr "Session ID"
msgid "settings.sessions.last_activity"
msgstr "Last activity"
msgid "settings.theme"
msgstr "Theme"

View File

@ -0,0 +1,61 @@
{% extends 'pages/settings/settings.twig' %}
{% import 'macros/form.twig' as f %}
{% import 'macros/base.twig' as m %}
{% block title %}{{ __('settings.sessions') }}{% endblock %}
{% block row_content %}
<div class="row">
<div class="col-md-12">
{{ m.info(__('settings.sessions.info')) }}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{{ __('settings.sessions.id') }}</th>
<th>{{ __('settings.sessions.last_activity') }}</th>
<th>
{% if sessions|length > 1 %}
<form action="" enctype="multipart/form-data" method="post">
{{ csrf() }}
{{ f.hidden('id', 'all') }}
{{ f.submit(
__('form.delete_all'),
{'name': 'delete', 'btn_type': 'danger', 'size': 'sm', 'icon_left': 'trash'}
) }}
</form>
{% endif %}
</th>
</tr>
</thead>
<tbody>
{% for session in sessions %}
<tr>
<td>
<pre>{{ session['id'] }}</pre>
</td>
<td>{{ session.last_activity.format(__('Y-m-d H:i:s')) }}</td>
<td>
{% if session.id != current_session %}
<form action="" enctype="multipart/form-data" method="post">
{{ csrf() }}
{{ f.hidden('id', session.id) }}
{{ f.submit(
__('form.delete'),
{'name': 'delete', 'btn_type': 'danger', 'size': 'sm', 'icon_left': 'trash'}
) }}
</form>
{% else %}
{{ __('settings.sessions.current') }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -279,6 +279,38 @@ class SettingsController extends BaseController
);
}
public function sessions(): Response
{
$sessions = $this->auth->user()->sessions->sortByDesc('last_activity');
return $this->response->withView(
'pages/settings/sessions',
[
'settings_menu' => $this->settingsMenu(),
'sessions' => $sessions,
'current_session' => session()->getId(),
],
);
}
public function sessionsDelete(Request $request): Response
{
$id = $request->postData('id');
$query = $this->auth->user()
->sessions()
->getQuery()
->where('id', '!=', session()->getId());
if ($id != 'all') {
$query = $query->where('id', $id);
}
$query->delete();
$this->addNotification('settings.sessions.delete_success');
return $this->redirect->to('/settings/sessions');
}
public function settingsMenu(): array
{
$menu = [
@ -298,6 +330,8 @@ class SettingsController extends BaseController
$menu[url('/settings/certificates')] = 'settings.certificates';
}
$menu[url('/settings/sessions')] = 'settings.sessions';
if (!empty(config('oauth'))) {
$menu[url('/settings/oauth')] = ['title' => 'settings.oauth', 'hidden' => $this->checkOauthHidden()];
}

View File

@ -11,6 +11,7 @@ use Engelsystem\Controllers\NotificationType;
use Engelsystem\Controllers\SettingsController;
use Engelsystem\Http\Exceptions\HttpNotFound;
use Engelsystem\Http\Response;
use Engelsystem\Models\Session as SessionModel;
use Engelsystem\Models\User\License;
use Engelsystem\Models\User\Settings;
use PHPUnit\Framework\MockObject\MockObject;
@ -32,6 +33,10 @@ class SettingsControllerTest extends ControllerTest
protected SettingsController $controller;
protected SessionModel $currentSession;
protected SessionModel $secondSession;
protected SessionModel $otherSession;
protected function setUpProfileTest(): array
{
$body = [
@ -574,7 +579,95 @@ class SettingsControllerTest extends ControllerTest
$this->controller->oauth();
}
/**
* @covers \Engelsystem\Controllers\SettingsController::sessions
*/
public function testSessions(): void
{
$this->setExpects($this->auth, 'user', null, $this->user, $this->once());
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function ($view, $data) {
$this->assertEquals('pages/settings/sessions', $view);
$this->assertArrayHasKey('sessions', $data);
$this->assertCount(3, $data['sessions']);
$this->assertArrayHasKey('current_session', $data);
$this->assertEquals($this->currentSession->id, $data['current_session']);
return $this->response;
});
$this->controller->sessions();
}
/**
* @covers \Engelsystem\Controllers\SettingsController::sessionsDelete
*/
public function testSessionsDelete(): void
{
$this->setExpects($this->auth, 'user', null, $this->user, $this->once());
$this->setExpects($this->response, 'redirectTo', ['http://localhost/settings/sessions'], $this->response);
// Delete old user session
$this->request = $this->request->withParsedBody(['id' => $this->secondSession->id]);
$this->controller->sessionsDelete($this->request);
$this->assertHasNotification('settings.sessions.delete_success');
$this->assertCount(3, SessionModel::all());
$this->assertNull(SessionModel::find($this->secondSession->id));
}
/**
* @covers \Engelsystem\Controllers\SettingsController::sessionsDelete
*/
public function testSessionsDeleteActiveSession(): void
{
$this->setExpects($this->auth, 'user', null, $this->user, $this->once());
$this->setExpects($this->response, 'redirectTo', null, $this->response);
// Delete active user session
$this->request = $this->request->withParsedBody(['id' => $this->currentSession->id]);
$this->controller->sessionsDelete($this->request);
$this->assertCount(4, SessionModel::all()); // None got deleted
$this->assertNotNull(SessionModel::find($this->currentSession->id));
}
/**
* @covers \Engelsystem\Controllers\SettingsController::sessionsDelete
*/
public function testSessionsDeleteOtherUsersSession(): void
{
$this->setExpects($this->auth, 'user', null, $this->user, $this->once());
$this->setExpects($this->response, 'redirectTo', null, $this->response);
// Delete another users session
$this->request = $this->request->withParsedBody(['id' => $this->otherSession->id]);
$this->controller->sessionsDelete($this->request);
$this->assertCount(4, SessionModel::all()); // None got deleted
$this->assertNotNull(SessionModel::find($this->otherSession->id));
}
/**
* @covers \Engelsystem\Controllers\SettingsController::sessionsDelete
*/
public function testSessionsDeleteAllSessions(): void
{
$this->setExpects($this->auth, 'user', null, $this->user, $this->once());
$this->setExpects($this->response, 'redirectTo', null, $this->response);
// Delete all other user sessions
$this->request = $this->request->withParsedBody(['id' => 'all']);
$this->controller->sessionsDelete($this->request);
$this->assertCount(2, SessionModel::all()); // Two got deleted
$this->assertNotNull(SessionModel::find($this->currentSession->id));
$this->assertNull(SessionModel::find($this->secondSession->id));
}
/**
* @covers \Engelsystem\Controllers\SettingsController::__construct
@ -849,6 +942,13 @@ class SettingsControllerTest extends ControllerTest
->has(License::factory())
->create();
// Create 4 sessions, 3 for the active user
$this->otherSession = SessionModel::factory()->create()->first(); // Other users sessions
$sessions = SessionModel::factory(3)->create(['user_id' => $this->user->id]);
$this->currentSession = $sessions->first();
$this->secondSession = $sessions->last();
$this->session->setId($this->currentSession->id);
$this->controller = $this->app->make(SettingsController::class);
$this->controller->setValidator(new Validator());
}