true, 'cookie_samesite' => 'Lax', 'cookie_path' => '/', ]); } if (empty($_SESSION['ucp_csrf'])) { $_SESSION['ucp_csrf'] = bin2hex(random_bytes(24)); } } public static function getCsrfToken(): string { self::startSession(); return (string)$_SESSION['ucp_csrf']; } public static function verifyCsrfToken(string $token): bool { self::startSession(); $expected = (string)($_SESSION['ucp_csrf'] ?? ''); return $expected !== '' && hash_equals($expected, $token); } public static function isUcpEnabled(PDO $db): bool { try { $val = $db->query('SELECT ucp_enabled FROM settings WHERE id = 1')->fetchColumn(); return $val === false ? true : ((int)$val === 1); } catch (\Throwable) { return true; } } public static function getCurrentUser(PDO $db): ?array { self::startSession(); if (empty($_SESSION['ucp_user_id']) || !self::isUcpEnabled($db)) { return null; } $stmt = $db->prepare(' SELECT id, username, display_name, email, extensions_json, enabled, last_login_at, last_login_ip FROM ucp_users WHERE id = :id LIMIT 1 '); $stmt->execute([':id' => (int)$_SESSION['ucp_user_id']]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if (!$user || (int)$user['enabled'] !== 1) { self::logout(); return null; } return $user; } public static function isAuthenticated(PDO $db): bool { return self::getCurrentUser($db) !== null; } public static function login(PDO $db, string $username, string $password): bool { self::startSession(); if (!self::isUcpEnabled($db)) { return false; } $cleanUser = trim($username); $stmt = $db->prepare(' SELECT id, username, display_name, password_hash, enabled FROM ucp_users WHERE username = :u LIMIT 1 '); $stmt->execute([':u' => $cleanUser]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if ($user && (int)$user['enabled'] === 1 && password_verify($password, (string)$user['password_hash'])) { session_regenerate_id(true); $_SESSION['ucp_user_id'] = (int)$user['id']; $_SESSION['ucp_username'] = (string)$user['username']; if (empty($_SESSION['ucp_csrf'])) { $_SESSION['ucp_csrf'] = bin2hex(random_bytes(24)); } $ip = FirewallManager::resolveClientIp(); $db->prepare('UPDATE ucp_users SET last_login_at = CURRENT_TIMESTAMP, last_login_ip = :ip WHERE id = :id') ->execute([':ip' => $ip, ':id' => (int)$user['id']]); return true; } self::logAuthFailure($cleanUser); return false; } public static function logAuthFailure(string $username): void { $clientIp = FirewallManager::resolveClientIp(); $safeUser = preg_replace('/[^a-zA-Z0-9._@\-]/', '', $username) ?: 'vide'; $logMsg = date('[Y-m-d H:i:s] ') . "[NEXUS-UCP-AUTH-FAIL]: {$clientIp} (username: {$safeUser})\n"; @file_put_contents(FirewallManager::UCP_SECURITY_LOG, $logMsg, FILE_APPEND | LOCK_EX); @chmod(FirewallManager::UCP_SECURITY_LOG, 0664); } public static function logout(): void { self::startSession(); $_SESSION = []; if (ini_get('session.use_cookies')) { $params = session_get_cookie_params(); setcookie( session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly'] ); } session_destroy(); } /** * Retourne la liste fraîche des extensions autorisées et actives pour un utilisateur UCP. */ public static function getAllowedExtensions(PDO $db, int $ucpUserId): array { $stmt = $db->prepare('SELECT extensions_json, enabled FROM ucp_users WHERE id = :id LIMIT 1'); $stmt->execute([':id' => $ucpUserId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!$row || (int)$row['enabled'] !== 1) { return []; } $linked = json_decode((string)($row['extensions_json'] ?? '[]'), true); if (!is_array($linked) || empty($linked)) { return []; } $linkedSet = array_values(array_unique(array_map('strval', $linked))); $extRows = $db->query(' SELECT id, extension, name, transport, encryption, groundwire_enabled, voicemail_enabled, voicemail_email, voicemail_pin, ring_time FROM extensions WHERE enabled = 1 ORDER BY extension ASC ')->fetchAll(PDO::FETCH_ASSOC) ?: []; $allowed = []; foreach ($extRows as $ext) { if (in_array((string)$ext['extension'], $linkedSet, true)) { $allowed[] = $ext; } } return $allowed; } public static function updateOwnPassword(PDO $db, int $ucpUserId, string $currentPassword, string $newPassword): void { if (strlen($newPassword) < 6) { throw new InvalidArgumentException("Le nouveau mot de passe doit contenir au moins 6 caractères."); } $stmt = $db->prepare('SELECT password_hash FROM ucp_users WHERE id = :id LIMIT 1'); $stmt->execute([':id' => $ucpUserId]); $hash = $stmt->fetchColumn(); if (!$hash || !password_verify($currentPassword, (string)$hash)) { throw new InvalidArgumentException("Le mot de passe actuel est incorrect."); } $db->prepare('UPDATE ucp_users SET password_hash = :p, updated_at = CURRENT_TIMESTAMP WHERE id = :id') ->execute([ ':p' => password_hash($newPassword, PASSWORD_DEFAULT), ':id' => $ucpUserId, ]); } }