feat(ucp): NexusPBX v2.5.0 Portail Utilisateur isolé (Refs #4)
This commit is contained in:
1 parent
7922e338e3
commit
72e43f1a9a
18 files changed
+3499
-62
No files matched your search
+196
@@ -0,0 +1,196 @@
|
||||
<?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, 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user