198 lines
6.4 KiB
PHP
198 lines
6.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App;
|
|
|
|
use PDO;
|
|
use InvalidArgumentException;
|
|
|
|
class UcpAuth
|
|
{
|
|
public const SESSION_NAME = 'NEXUS_UCP_SESS';
|
|
|
|
public static function startSession(): void
|
|
{
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_name(self::SESSION_NAME);
|
|
session_start([
|
|
'cookie_httponly' => 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,
|
|
]);
|
|
}
|
|
}
|
|
|