0, 'path' => $cookiePath, 'domain' => $cookieDomain, 'secure' => $cookieSecure, 'httponly' => $cookieHttpOnly, 'samesite' => $cookieSameSite, ]); session_start(); } } /** ===== Auth Core ===== */ function auth_login(PDO $pdoCustomers, array $cfg, string $email, string $password): array { auth_start_session($cfg); $sql = "SELECT cu.id, cu.customer_id, cu.email, cu.password_hash, cu.role, c.slug AS customer_slug, c.plan, c.status FROM customer_users cu JOIN customers c ON c.id = cu.customer_id WHERE cu.email = :email AND cu.is_active = 1 LIMIT 1"; $st = $pdoCustomers->prepare($sql); $st->execute([':email' => $email]); $u = $st->fetch(PDO::FETCH_ASSOC); if (!$u || !password_verify($password, $u['password_hash'])) { return ['ok' => false, 'error' => 'invalid_credentials']; } if (($u['status'] ?? 'active') !== 'active') { return ['ok' => false, 'error' => 'customer_inactive']; } // neue Session-ID, alte wird invalidiert session_regenerate_id(true); $_SESSION['user'] = [ 'id' => (int)$u['id'], 'email' => $u['email'], 'role' => $u['role'], 'customer_id' => (int)$u['customer_id'], 'customer_slug' => $u['customer_slug'], 'plan' => $u['plan'], ]; return ['ok' => true, 'user' => $_SESSION['user']]; } function auth_logout(array $cfg): void { auth_start_session($cfg); // Sessiondaten löschen $_SESSION = []; // Cookie-Parameter aus der aktiven Session $params = session_get_cookie_params(); $name = session_name(); // Kandidaten für Domain/Path, um "falsch" gesetzte Cookies sicher zu treffen $host = $_SERVER['HTTP_HOST'] ?? ''; $cfgDomain = $cfg['auth']['cookie_domain'] ?? ''; $paths = array_values(array_unique([$params['path'] ?? '/', '/', ''])); $domains = array_values(array_unique([ $params['domain'] ?? '', $cfgDomain, $host, ltrim($host, '.'), (strpos($host, '.') !== false ? '.' . ltrim($host, '.') : $host), ])); // Alle Varianten invalidieren (secure/httponly wie gesetzt) foreach ($paths as $p) { foreach ($domains as $d) { if ($d === null) continue; setcookie($name, '', time() - 3600, $p, $d, $params['secure'] ?? true, $params['httponly'] ?? true); } // zusätzlich: ohne Domain (trifft Host-spezifische Cookies) setcookie($name, '', time() - 3600, $p, '', $params['secure'] ?? true, $params['httponly'] ?? true); } // Session beenden session_destroy(); session_write_close(); // In Staging aggressiv: Browser bitten, Cookies zu löschen (nicht jeder Browser respektiert das sofort) if (($cfg['env'] ?? 'prod') === 'staging') { header('Clear-Site-Data: "cookies"', false); } } function auth_require(array $cfg): void { auth_start_session($cfg); if (empty($_SESSION['user'])) { http_response_code(401); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'unauthorized']); exit; } } function require_role(array $cfg, array $roles): void { auth_start_session($cfg); $r = $_SESSION['user']['role'] ?? null; if (!$r || !in_array($r, $roles, true)) { http_response_code(403); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => false, 'error' => 'forbidden']); exit; } } function current_user(array $cfg): ?array { auth_start_session($cfg); return $_SESSION['user'] ?? null; } function current_customer_id(array $cfg): ?int { $u = current_user($cfg); return $u['customer_id'] ?? null; }