Files
2026-10-30 21:49:58 +00:00

173 lines
6.0 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Authentifizierung – getrennte Sessions für Kunden (users) und Mitarbeiter (staff)
*/
// ---------------------------------------------------------------
// Kunden-Auth
// ---------------------------------------------------------------
function customer_login(array $user): void
{
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
}
function customer_logout(): void
{
unset($_SESSION['user_id']);
}
function current_user(): ?array
{
if (empty($_SESSION['user_id'])) {
return null;
}
static $cache = null;
if ($cache !== null) {
return $cache;
}
$stmt = get_db()->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
return $cache = ($user ?: null);
}
function require_customer_login(): array
{
$user = current_user();
if (!$user) {
flash('error', 'Bitte melden Sie sich an, um fortzufahren.');
redirect(base_path() . '/login.php');
}
return $user;
}
// ---------------------------------------------------------------
// Mitarbeiter-Auth
// ---------------------------------------------------------------
function staff_login(array $staff): void
{
session_regenerate_id(true);
$_SESSION['staff_id'] = $staff['id'];
$_SESSION['staff_last_activity'] = time();
$_SESSION['staff_login_at'] = time();
staff_touch_activity(get_db(), (int) $staff['id']);
login_guard_success($staff, 'web');
}
function staff_logout(): void
{
// Automatische Zeiterfassung: offene Arbeits-Sitzungen beim Logout sofort
// abschließen, statt bis zum nächsten Stale-Sweep (Auto-Cron) zu warten.
if (!empty($_SESSION['staff_id'])) {
finalize_all_sessions_for_staff(get_db(), (int) $_SESSION['staff_id']);
}
unset($_SESSION['staff_id']);
}
function current_staff(): ?array
{
if (empty($_SESSION['staff_id'])) {
return null;
}
static $cache = null;
if ($cache !== null) {
return $cache;
}
$stmt = get_db()->prepare('SELECT * FROM staff WHERE id = ? AND is_active = 1');
$stmt->execute([$_SESSION['staff_id']]);
$staff = $stmt->fetch();
// „Das war ich nicht“ (Anmelde-Wächter): ältere Browser-Sitzungen dieses Kontos sind ungültig
if ($staff && login_guard_session_revoked((int) $staff['id'])) {
unset($_SESSION['staff_id']);
return null;
}
return $cache = ($staff ?: null);
}
function require_staff_login(): array
{
$staff = current_staff();
if (!$staff) {
// War die Sitzung gerade wegen Inaktivität abgelaufen (ein Hintergrund-Abruf hat sie schon beendet), den Grund nennen
if (!empty($_SESSION['staff_timed_out'])) {
unset($_SESSION['staff_timed_out']);
flash('error', 'Sie wurden wegen Inaktivität abgemeldet. Bitte melden Sie sich erneut an.');
} else {
flash('error', 'Bitte melden Sie sich an, um fortzufahren.');
}
redirect(staff_base_path() . '/login.php');
}
// Inaktivitäts-Timeout
$timeout = (int)(get_setting('staff_session_timeout', '30') ?: 30); // Minuten
$last = $_SESSION['staff_last_activity'] ?? 0;
$userNavigation = staff_is_user_navigation();
if ($last && (time() - $last) > ($timeout * 60)) {
staff_logout();
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
$_SESSION['staff_timed_out'] = time(); // merken, damit die Anmeldeseite den Grund nennen kann (siehe oben)
if (!staff_is_document_request()) {
// Hintergrund-Abruf (Aktualisierung der Liste, Chat-Abfrage …): kurze Antwort statt Weiterleitung auf die Anmeldeseite.
// Das Laden einer Seite dagegen führt immer zur Anmeldung, auch wenn der Browser es selbst ausgelöst hat (Tab neu geladen).
http_response_code(401);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'Session abgelaufen', 'sessionExpired' => true]);
exit;
}
unset($_SESSION['staff_timed_out']);
$_SESSION['flash'] = [['type'=>'error','message'=>'Sie wurden wegen Inaktivität abgemeldet. Bitte melden Sie sich erneut an.']];
redirect('/staff/login.php');
}
// Nur echte Bedienung hält die Sitzung am Leben: ein vom Nutzer ausgelöster Seitenaufruf oder der Ping der Seite bei Klicks und
// Tastatureingaben (staff/session_ping.php). Hintergrund-Abfragen der offenen Seiten (Listen, Chat, Anwesenheit alle paar
// Sekunden) zählen nicht, sonst würde eine offene Seite die Sitzung ewig verlängern und die Abmeldung nie greifen.
if ($userNavigation) {
$_SESSION['staff_last_activity'] = time();
staff_touch_activity(get_db(), (int) $staff['id']);
}
// "Eingebauter" Ersatz für echte Server-Cronjobs (siehe includes/pseudo_cron.php):
// bei jeder authentifizierten Backend-Anfrage werden fällige Jobs im Hintergrund geprüft.
pseudo_cron_schedule_background();
return $staff;
}
function require_admin(): array
{
$staff = require_staff_login();
if ($staff['role'] !== 'admin') {
flash('error', 'Diese Funktion ist nur für Administratoren verfügbar.');
redirect(staff_base_path() . '/reports.php');
}
return $staff;
}
/**
* Wie require_admin(), lässt aber zusätzlich Agenten mit einer benutzerdefinierten Rolle
* durch, die genau dieses Adminportal-Recht zugewiesen bekommen haben ("Co-Admin" für
* einzelne Bereiche, siehe admin_section_permissions() / staff/roles.php).
*/
function require_admin_or_permission(string $permission): array
{
$staff = require_staff_login();
if ($staff['role'] !== 'admin' && !has_permission($staff, $permission)) {
flash('error', 'Für diese Funktion fehlt die erforderliche Berechtigung.');
redirect(staff_base_path() . '/reports.php');
}
return $staff;
}
/** Pfad-Helfer, damit Includes unabhängig vom Aufrufort funktionieren */
function base_path(): string
{
return '';
}
function staff_base_path(): string
{
return '/staff';
}