0, 'path' => '/', 'httponly' => true, 'secure' => $secure, 'samesite' => 'Lax', ]); session_name('OMSORG_FELIX_APP'); session_start(); function _omsorgcore_config(): array { static $config = null; if ($config === null) { $config = require __DIR__ . '/config.php'; } return $config; } // Stellt sicher, dass $_SESSION['omsorgcore_access_token'] gültig ist - erneuert bei Bedarf still // über den Refresh-Token (Server-seitig, der Browser bekommt nie einen Token zu sehen). function _ensure_fresh_token(): bool { if (empty($_SESSION['omsorgcore_access_token']) || empty($_SESSION['omsorgcore_refresh_token'])) { return false; } $expiresAt = $_SESSION['omsorgcore_access_expires_at'] ?? null; $stillValid = $expiresAt && strtotime($expiresAt) > time() + 30; // 30s Puffer if ($stillValid) { return true; } $result = omsorgcore_refresh(_omsorgcore_config(), $_SESSION['omsorgcore_refresh_token']); if (!$result['ok']) { _clear_omsorgcore_session(); return false; } _apply_token_pair($result['data']); return true; } function _apply_token_pair(array $data): void { $_SESSION['omsorgcore_access_token'] = $data['accessToken']; $_SESSION['omsorgcore_refresh_token'] = $data['refreshToken']; $_SESSION['omsorgcore_access_expires_at'] = $data['expiresAt']; // Noch nicht verdrahtet (kein change-password.php in diesem Milestone) - Flag wird aber // schon gesetzt, damit das Nachziehen später keine Änderung an Login/Session-Core braucht. $_SESSION['omsorgcore_must_change_password'] = !empty($data['mustChangePassword']); } function _clear_omsorgcore_session(): void { unset( $_SESSION['omsorgcore_access_token'], $_SESSION['omsorgcore_refresh_token'], $_SESSION['omsorgcore_access_expires_at'], $_SESSION['omsorgcore_must_change_password'], $_SESSION['omsorgcore_profile'] ); } // Lädt/cached /api/auth/me in der Session (Username/Rolle/Rechte). Wird nach Login und bei // jedem is_logged_in()-Aufruf ohne gültigen Cache erneuert. function _refresh_profile(): bool { $result = omsorgcore_me(_omsorgcore_config(), $_SESSION['omsorgcore_access_token']); if (!$result['ok']) { return false; } $_SESSION['omsorgcore_profile'] = $result['data']; return true; } function is_logged_in(): bool { if (!_ensure_fresh_token()) { return false; } if (empty($_SESSION['omsorgcore_profile'])) { return _refresh_profile(); } return true; } function require_login(): void { if (!is_logged_in()) { header('Location: ../index.php'); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST)) { verify_csrf(); } } function csrf_token(): string { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function csrf_field(): string { return ''; } function verify_csrf(): void { $token = $_POST['csrf_token'] ?? ''; if (!hash_equals(csrf_token(), $token)) { http_response_code(403); exit('Ungültige Anfrage.'); } } function current_username(): string { return $_SESSION['omsorgcore_profile']['username'] ?? ''; } function current_name(): string { $profile = $_SESSION['omsorgcore_profile'] ?? []; $name = trim(($profile['firstName'] ?? '') . ' ' . ($profile['lastName'] ?? '')); return $name !== '' ? $name : current_username(); } function current_role(): string { return $_SESSION['omsorgcore_profile']['role'] ?? ''; } // Serverseitig ist jeder Endpunkt ohnehin über [RequirePermission] gegated (siehe // omsorgCore/CLAUDE.md, "Rechtesystem") - das hier ist nur die UI-Seite davon (Tab/Formular // ausblenden statt nur eine 403-Antwort abzuwarten), analog zu hasPermission() in // omsorgapp/src/app/AuthContext.jsx. $_SESSION['omsorgcore_profile']['permissions'] kommt aus // GET /api/auth/me (PermissionDto[] { module, action, scope }, siehe MeResponse in omsorgCore). function has_permission(string $module, string $action): bool { $permissions = $_SESSION['omsorgcore_profile']['permissions'] ?? []; foreach ($permissions as $permission) { if (($permission['module'] ?? null) === $module && ($permission['action'] ?? null) === $action) { return true; } } return false; } function e(string $s): string { return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }