From f6014a55e41247eec0654e1a5a221daffc51589b Mon Sep 17 00:00:00 2001 From: ginctronic Date: Sun, 23 Nov 2025 15:31:32 +0100 Subject: [PATCH] gestioneutente --- app/UsersManager.class.php | 402 +++++++++++++++++++++++++++++++++++++ config/.DS_Store | Bin 0 -> 6148 bytes config/db.php | 3 + config/init.php | 6 + config/test_db.php | 25 --- index.php | 4 +- 6 files changed, 412 insertions(+), 28 deletions(-) create mode 100644 app/UsersManager.class.php create mode 100644 config/.DS_Store create mode 100644 config/init.php delete mode 100644 config/test_db.php diff --git a/app/UsersManager.class.php b/app/UsersManager.class.php new file mode 100644 index 0000000..a044f8b --- /dev/null +++ b/app/UsersManager.class.php @@ -0,0 +1,402 @@ +db = $db; + $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + + /** + * Registra un nuovo utente + */ + public function register(string $username, string $email, string $password, string $fullName = null): bool { + try { + $passwordHash = password_hash($password, PASSWORD_ARGON2ID); + + $stmt = $this->db->prepare(" + INSERT INTO users (username, email, password_hash, full_name) + VALUES (:username, :email, :password_hash, :full_name) + "); + + $result = $stmt->execute([ + 'username' => $username, + 'email' => $email, + 'password_hash' => $passwordHash, + 'full_name' => $fullName + ]); + + if ($result) { + $userId = $this->db->lastInsertId(); + $this->logActivity($userId, $username, 'REGISTER', "Nuovo utente registrato: $username"); + } + + return $result; + } catch (PDOException $e) { + error_log("Errore registrazione: " . $e->getMessage()); + return false; + } + } + + /** + * Login utente con username/email e password + */ + public function login(string $identifier, string $password, bool $rememberMe = true): array { + // Verifica blocco per troppi tentativi + if ($this->isAccountLocked($identifier)) { + return [ + 'success' => false, + 'message' => 'Account temporaneamente bloccato per troppi tentativi falliti' + ]; + } + + // Cerca utente per username o email + $stmt = $this->db->prepare(" + SELECT id, username, email, password_hash, is_active, full_name + FROM users + WHERE (username = :identifier OR email = :identifier) AND is_active = 1 + "); + $stmt->execute(['identifier' => $identifier]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$user || !password_verify($password, $user['password_hash'])) { + $this->logFailedAttempt($identifier); + $this->logActivity(null, $identifier, 'LOGIN_FAILED', "Tentativo di login fallito"); + return [ + 'success' => false, + 'message' => 'Credenziali non valide' + ]; + } + + // Login riuscito - pulisce tentativi falliti + $this->clearFailedAttempts($identifier); + + // Aggiorna last_login + $stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = :id"); + $stmt->execute(['id' => $user['id']]); + + // Crea token di autenticazione se richiesto + if ($rememberMe) { + $this->createAuthToken($user['id']); + } + + // Salva in sessione dati base + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['full_name'] = $user['full_name']; + + $this->logActivity($user['id'], $user['username'], 'LOGIN', "Login effettuato"); + + return [ + 'success' => true, + 'user' => [ + 'id' => $user['id'], + 'username' => $user['username'], + 'email' => $user['email'], + 'full_name' => $user['full_name'] + ] + ]; + } + + /** + * Re-autenticazione con solo password (quando cookie è scaduto ma username è memorizzato) + */ + public function reAuthenticate(string $username, string $password): array { + return $this->login($username, $password, true); + } + + /** + * Verifica validità sessione/cookie + */ + public function checkAuth(): ?array { + // Prima controlla se esiste sessione attiva + if (isset($_SESSION['user_id'])) { + return $this->getUserById($_SESSION['user_id']); + } + + // Altrimenti verifica cookie + if (isset($_COOKIE[$this->cookieName])) { + return $this->validateAuthToken($_COOKIE[$this->cookieName]); + } + + return null; + } + + /** + * Crea token di autenticazione e imposta cookie + */ + private function createAuthToken(int $userId): bool { + try { + // Genera selector e token casuali + $selector = bin2hex(random_bytes(16)); + $token = bin2hex(random_bytes(32)); + $tokenHash = hash('sha256', $token); + + $expiresAt = date('Y-m-d H:i:s', time() + $this->cookieLifetime); + + // Salva nel database + $stmt = $this->db->prepare(" + INSERT INTO auth_tokens (user_id, token, selector, expires_at, ip_address, user_agent) + VALUES (:user_id, :token, :selector, :expires_at, :ip, :ua) + "); + + $stmt->execute([ + 'user_id' => $userId, + 'token' => $tokenHash, + 'selector' => $selector, + 'expires_at' => $expiresAt, + 'ip' => $_SERVER['REMOTE_ADDR'] ?? null, + 'ua' => $_SERVER['HTTP_USER_AGENT'] ?? null + ]); + + // Imposta cookie (selector:token) + $cookieValue = $selector . ':' . $token; + setcookie( + $this->cookieName, + $cookieValue, + time() + $this->cookieLifetime, + '/', + '', + true, // Secure (solo HTTPS in produzione) + true // HttpOnly + ); + + return true; + } catch (Exception $e) { + error_log("Errore creazione token: " . $e->getMessage()); + return false; + } + } + + /** + * Valida token di autenticazione dal cookie + */ + private function validateAuthToken(string $cookieValue): ?array { + try { + $parts = explode(':', $cookieValue); + if (count($parts) !== 2) { + return null; + } + + [$selector, $token] = $parts; + $tokenHash = hash('sha256', $token); + + // Cerca token nel database + $stmt = $this->db->prepare(" + SELECT at.*, u.id, u.username, u.email, u.full_name, u.is_active + FROM auth_tokens at + JOIN users u ON at.user_id = u.id + WHERE at.selector = :selector AND at.expires_at > NOW() AND u.is_active = 1 + "); + $stmt->execute(['selector' => $selector]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$result || !hash_equals($result['token'], $tokenHash)) { + $this->deleteAuthToken($selector); + return null; + } + + // Token valido - rigenera sessione + $_SESSION['user_id'] = $result['id']; + $_SESSION['username'] = $result['username']; + $_SESSION['full_name'] = $result['full_name']; + + // Aggiorna last_login + $stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = :id"); + $stmt->execute(['id' => $result['id']]); + + $this->logActivity($result['id'], $result['username'], 'AUTO_LOGIN', "Login automatico via cookie"); + + return [ + 'id' => $result['id'], + 'username' => $result['username'], + 'email' => $result['email'], + 'full_name' => $result['full_name'] + ]; + } catch (Exception $e) { + error_log("Errore validazione token: " . $e->getMessage()); + return null; + } + } + + /** + * Logout - elimina sessione e cookie + */ + public function logout(): void { + $userId = $_SESSION['user_id'] ?? null; + $username = $_SESSION['username'] ?? 'unknown'; + + // Elimina token dal database se presente + if (isset($_COOKIE[$this->cookieName])) { + $parts = explode(':', $_COOKIE[$this->cookieName]); + if (count($parts) === 2) { + $this->deleteAuthToken($parts[0]); + } + } + + // Elimina cookie + setcookie($this->cookieName, '', time() - 3600, '/', '', true, true); + + // Distrugge sessione + session_unset(); + session_destroy(); + + if ($userId) { + $this->logActivity($userId, $username, 'LOGOUT', "Logout effettuato"); + } + } + + /** + * Elimina token dal database + */ + private function deleteAuthToken(string $selector): void { + try { + $stmt = $this->db->prepare("DELETE FROM auth_tokens WHERE selector = :selector"); + $stmt->execute(['selector' => $selector]); + } catch (Exception $e) { + error_log("Errore eliminazione token: " . $e->getMessage()); + } + } + + /** + * Ottiene utente per ID + */ + public function getUserById(int $userId): ?array { + $stmt = $this->db->prepare(" + SELECT id, username, email, full_name, is_active, created_at, last_login + FROM users WHERE id = :id AND is_active = 1 + "); + $stmt->execute(['id' => $userId]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + return $user ?: null; + } + + /** + * Verifica se account è bloccato per troppi tentativi + */ + private function isAccountLocked(string $identifier): bool { + $stmt = $this->db->prepare(" + SELECT COUNT(*) as attempts + FROM failed_login_attempts + WHERE username = :identifier + AND ip_address = :ip + AND attempted_at > DATE_SUB(NOW(), INTERVAL :lockout SECOND) + "); + $stmt->execute([ + 'identifier' => $identifier, + 'ip' => $_SERVER['REMOTE_ADDR'] ?? '', + 'lockout' => $this->lockoutTime + ]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + return $result['attempts'] >= $this->maxLoginAttempts; + } + + /** + * Registra tentativo di login fallito + */ + private function logFailedAttempt(string $identifier): void { + try { + $stmt = $this->db->prepare(" + INSERT INTO failed_login_attempts (username, ip_address) + VALUES (:username, :ip) + "); + $stmt->execute([ + 'username' => $identifier, + 'ip' => $_SERVER['REMOTE_ADDR'] ?? '' + ]); + } catch (Exception $e) { + error_log("Errore log tentativo fallito: " . $e->getMessage()); + } + } + + /** + * Pulisce tentativi falliti dopo login riuscito + */ + private function clearFailedAttempts(string $identifier): void { + try { + $stmt = $this->db->prepare(" + DELETE FROM failed_login_attempts + WHERE username = :username AND ip_address = :ip + "); + $stmt->execute([ + 'username' => $identifier, + 'ip' => $_SERVER['REMOTE_ADDR'] ?? '' + ]); + } catch (Exception $e) { + error_log("Errore pulizia tentativi: " . $e->getMessage()); + } + } + + /** + * Log attività utente + */ + public function logActivity(?int $userId, string $username, string $action, string $description = null): void { + try { + $stmt = $this->db->prepare(" + INSERT INTO user_activity_log (user_id, username, action, description, ip_address, user_agent) + VALUES (:user_id, :username, :action, :description, :ip, :ua) + "); + $stmt->execute([ + 'user_id' => $userId, + 'username' => $username, + 'action' => $action, + 'description' => $description, + 'ip' => $_SERVER['REMOTE_ADDR'] ?? null, + 'ua' => $_SERVER['HTTP_USER_AGENT'] ?? null + ]); + } catch (Exception $e) { + error_log("Errore log attività: " . $e->getMessage()); + } + } + + /** + * Pulizia token scaduti (da eseguire periodicamente) + */ + public function cleanExpiredTokens(): int { + try { + $stmt = $this->db->prepare("DELETE FROM auth_tokens WHERE expires_at < NOW()"); + $stmt->execute(); + return $stmt->rowCount(); + } catch (Exception $e) { + error_log("Errore pulizia token: " . $e->getMessage()); + return 0; + } + } + + /** + * Ottiene username da cookie (per form di re-autenticazione) + */ + public function getRememberedUsername(): ?string { + if (!isset($_COOKIE[$this->cookieName])) { + return null; + } + + $parts = explode(':', $_COOKIE[$this->cookieName]); + if (count($parts) !== 2) { + return null; + } + + try { + $stmt = $this->db->prepare(" + SELECT u.username + FROM auth_tokens at + JOIN users u ON at.user_id = u.id + WHERE at.selector = :selector + "); + $stmt->execute(['selector' => $parts[0]]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + return $result['username'] ?? null; + } catch (Exception $e) { + return null; + } + } +} \ No newline at end of file diff --git a/config/.DS_Store b/config/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 PDO::ERRMODE_EXCEPTION]); + + // try { // $pdo = new PDO( // "mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4", diff --git a/config/init.php b/config/init.php new file mode 100644 index 0000000..0bdc5d9 --- /dev/null +++ b/config/init.php @@ -0,0 +1,6 @@ +checkAuth(); diff --git a/config/test_db.php b/config/test_db.php deleted file mode 100644 index 18c192e..0000000 --- a/config/test_db.php +++ /dev/null @@ -1,25 +0,0 @@ -load(); - -$DB_HOST = $_ENV['DB_HOST']; -$DB_PORT = $_ENV['DB_PORT']; -$DB_NAME = $_ENV['DB_DATABASE']; -$DB_USER = $_ENV['DB_USERNAME']; -$DB_PASS = $_ENV['DB_PASSWORD']; - -try { - $pdo = new PDO( - "mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4", - $DB_USER, - $DB_PASS, - [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - ] - ); - echo "Connessione al database riuscita!"; -} catch (PDOException $e) { - echo "Errore connessione DB: " . $e->getMessage(); -} diff --git a/index.php b/index.php index f8ea88a..6ca1080 100644 --- a/index.php +++ b/index.php @@ -1,10 +1,8 @@ \ No newline at end of file