Finanzmanager/src/Auth.php
fds2610-bot b1712bb7ea Phase 0: Projektgerüst mit nginx, php-fpm und MariaDB
Docker Compose mit drei Services (nginx auf Port 8000), Migrationsmechanik,
Update- und Backup-Skript. Anwendung: Router mit CSRF-Prüfung, Anmeldung und
Ersteinrichtung, zentraler Einstellungsdialog mit Reitern, ein- und
ausschaltbares Debug-Log mit Maskierung von Zugangsdaten.

Layout nach Vorbild des FinanzManagers: feste Navigation links,
Werkzeugleiste oben, Tab-Leiste; Farbschema hell/dunkel/System.

Tests: 51 PHP-Prüfungen und 7 Playwright-Tests, alle grün.
2026-08-09 20:21:42 +02:00

122 lines
3.5 KiB
PHP
Raw 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
declare(strict_types=1);
namespace Finanzmanager;
use Finanzmanager\Models\Benutzer;
class Auth
{
/** Absolutes Session-Timeout. */
private const SESSION_LAUFZEIT = 86400;
public static function startSession(): void
{
if (session_status() !== PHP_SESSION_NONE) {
return;
}
session_set_cookie_params(self::cookieParams());
session_start();
}
/**
* Cookie-Attribute als reine Funktion, getrennt von startSession() – so
* lässt sich die Secure-Erkennung testen, ohne eine Session zu starten.
*
* Secure richtet sich nach dem Request (HTTPS bzw. X-Forwarded-Proto hinter
* einem Reverse-Proxy), damit der lokale Betrieb auf http://localhost:8000
* ohne TLS funktioniert. SESSION_COOKIE_SECURE erzwingt (1) oder
* unterdrückt (0) das Attribut.
*/
public static function cookieParams(): array
{
$override = getenv('SESSION_COOKIE_SECURE');
if ($override === '1' || $override === '0') {
$secure = $override === '1';
} else {
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';
}
return [
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
];
}
public static function verlange(): void
{
self::startSession();
$benutzerId = $_SESSION['user_id'] ?? null;
$anmeldung = $_SESSION['login_time'] ?? 0;
if (!$benutzerId || (time() - (int)$anmeldung) > self::SESSION_LAUFZEIT) {
$_SESSION = [];
session_destroy();
View::redirect('/login');
}
}
public static function anmelden(int $benutzerId): void
{
self::startSession();
session_regenerate_id(true);
$_SESSION['user_id'] = $benutzerId;
$_SESSION['login_time'] = time();
}
public static function abmelden(): void
{
self::startSession();
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$p = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$p['path'], $p['domain'], (bool)$p['secure'], (bool)$p['httponly']);
}
session_destroy();
}
public static function benutzerId(): ?int
{
return isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null;
}
public static function benutzer(): ?array
{
$id = self::benutzerId();
return $id === null ? null : Benutzer::find($id);
}
public static function csrfToken(): string
{
self::startSession();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function pruefeCsrfToken(string $token): void
{
self::startSession();
if (!self::istGueltigerCsrfToken($token)) {
http_response_code(403);
exit('CSRF-Token ungültig.');
}
}
/** Reiner Vergleich ohne Seiteneffekt, damit isoliert testbar. */
public static function istGueltigerCsrfToken(string $token): bool
{
$gespeichert = $_SESSION['csrf_token'] ?? '';
return $gespeichert !== '' && hash_equals($gespeichert, $token);
}
}