DB-gestützte Sessions (opt-in) + 'überall abmelden'
- includes/DbSessionHandler.php (SessionHandlerInterface, fehlertolerant) - Auth: opt-in-Registrierung (Setting session_driver=db), activeSessionCount(), logoutOtherSessions() - Migration 0004 (sessions.user_id NULL) + database.sql - Settings-Toggle in integrations.php; 'überall abmelden' in security.php
This commit is contained in:
parent
ebfa27d5c3
commit
1a51721477
6 changed files with 144 additions and 2 deletions
|
|
@ -18,7 +18,16 @@ class Auth {
|
|||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.use_strict_mode', 1);
|
||||
ini_set('session.cookie_samesite', 'Lax');
|
||||
|
||||
|
||||
// Opt-in: Sessions in der DB ablegen (für "überall abmelden" / Skalierung)
|
||||
try {
|
||||
if ($this->db->getSetting('session_driver', 'php') === 'db') {
|
||||
require_once __DIR__ . '/DbSessionHandler.php';
|
||||
$ttl = defined('SESSION_LIFETIME') ? (int)SESSION_LIFETIME : 3600;
|
||||
session_set_save_handler(new DbSessionHandler($this->db, $ttl), true);
|
||||
}
|
||||
} catch (\Throwable $e) { /* Fallback: Standard-PHP-Sessions */ }
|
||||
|
||||
if (!session_start()) {
|
||||
die("Session konnte nicht gestartet werden");
|
||||
}
|
||||
|
|
@ -434,6 +443,31 @@ class Auth {
|
|||
} catch (\Exception $e) { /* Richtlinie nie blockierend */ }
|
||||
}
|
||||
|
||||
/** Anzahl aktiver (nicht abgelaufener) DB-Sessions des aktuellen Nutzers. */
|
||||
public function activeSessionCount() {
|
||||
if (!$this->isLoggedIn()) return 0;
|
||||
try {
|
||||
$r = $this->db->fetchOne(
|
||||
"SELECT COUNT(*) c FROM sessions WHERE user_id = ? AND expires_at > NOW()",
|
||||
[$_SESSION['user_id']]
|
||||
);
|
||||
return (int)($r['c'] ?? 0);
|
||||
} catch (\Exception $e) { return 0; }
|
||||
}
|
||||
|
||||
/** Alle anderen Sessions des Nutzers beenden ("überall abmelden"). */
|
||||
public function logoutOtherSessions() {
|
||||
if (!$this->isLoggedIn()) return;
|
||||
try {
|
||||
$current = session_id();
|
||||
$this->db->query(
|
||||
"DELETE FROM sessions WHERE user_id = ? AND id != ?",
|
||||
[$_SESSION['user_id'], $current]
|
||||
);
|
||||
$this->writeAuditLog($_SESSION['user_id'], 'logout_other_sessions', 'user', $_SESSION['user_id'], 'Andere Sessions beendet');
|
||||
} catch (\Exception $e) { /* nur bei DB-Sessions wirksam */ }
|
||||
}
|
||||
|
||||
// Login erforderlich
|
||||
public function requireLogin() {
|
||||
if (!$this->isLoggedIn()) {
|
||||
|
|
|
|||
75
includes/DbSessionHandler.php
Normal file
75
includes/DbSessionHandler.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
/**
|
||||
* DbSessionHandler – speichert PHP-Sessions in der `sessions`-Tabelle.
|
||||
*
|
||||
* Opt-in über Setting `session_driver = db` (Standard 'php' = unverändert).
|
||||
* Ermöglicht "überall abmelden" und eine Übersicht aktiver Sessions.
|
||||
*
|
||||
* Alle DB-Operationen sind fehlertolerant gekapselt – schlägt etwas fehl,
|
||||
* degradiert die Session still, statt die Anwendung lahmzulegen.
|
||||
*/
|
||||
class DbSessionHandler implements SessionHandlerInterface
|
||||
{
|
||||
private $db;
|
||||
private $ttl;
|
||||
|
||||
public function __construct($db, $ttl = 3600)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->ttl = max(300, (int)$ttl);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function open($path, $name) { return true; }
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function close() { return true; }
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function read($id)
|
||||
{
|
||||
try {
|
||||
$row = $this->db->fetchOne(
|
||||
"SELECT data FROM sessions WHERE id = ? AND expires_at > NOW()", [$id]
|
||||
);
|
||||
return $row && $row['data'] !== null ? (string)$row['data'] : '';
|
||||
} catch (\Throwable $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function write($id, $data)
|
||||
{
|
||||
try {
|
||||
$uid = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null;
|
||||
$expires = date('Y-m-d H:i:s', time() + $this->ttl);
|
||||
$this->db->query(
|
||||
"INSERT INTO sessions (id, user_id, data, expires_at) VALUES (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), data = VALUES(data), expires_at = VALUES(expires_at)",
|
||||
[$id, $uid, $data, $expires]
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
// still ignorieren
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function destroy($id)
|
||||
{
|
||||
try {
|
||||
$this->db->query("DELETE FROM sessions WHERE id = ?", [$id]);
|
||||
} catch (\Throwable $e) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function gc($max_lifetime)
|
||||
{
|
||||
try {
|
||||
$this->db->query("DELETE FROM sessions WHERE expires_at < NOW()");
|
||||
} catch (\Throwable $e) {}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue