diff --git a/docker/init/00-setup.sql b/docker/init/00-setup.sql index b94191d..8fdc672 100644 --- a/docker/init/00-setup.sql +++ b/docker/init/00-setup.sql @@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS users ( is_admin TINYINT(1) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, lang VARCHAR(5) DEFAULT 'fr', + remember_token VARCHAR(255) DEFAULT NULL, -- 👈 NOUVEAU CHAMP ICI created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (family_id) REFERENCES families(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/includes/auth.php b/includes/auth.php index e5ea1e5..f3d56b0 100644 --- a/includes/auth.php +++ b/includes/auth.php @@ -4,6 +4,12 @@ if (session_status() === PHP_SESSION_NONE) { session_start(); } +/** + * Vérifie qu'un utilisateur est connecté. + * Si non, le redirige vers la page de login avec ?redirect=URL_DEMANDEE + * + * @param string|null $loginPage URL de la page de login (par défaut /login.php) + */ /** * Vérifie qu'un utilisateur est connecté. * Si non, le redirige vers la page de login avec ?redirect=URL_DEMANDEE @@ -16,6 +22,47 @@ function require_login(?string $loginPage = '/login.php'): void session_start(); } + // --- NOUVEAU : Auto-login via Cookie --- + if (empty($_SESSION['user']) && isset($_COOKIE['hh_remember'])) { + $cookieParts = explode(':', $_COOKIE['hh_remember']); + + if (count($cookieParts) === 2) { + $cookieUserId = (int)$cookieParts[0]; + $cookieToken = $cookieParts[1]; + + global $meta_pdo; + require_once __DIR__ . '/meta_db.php'; + + $stmt = $meta_pdo->prepare(" + SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, u.is_admin, u.is_active, u.lang, u.remember_token, + f.db_name, f.is_active as family_active, f.enabled_modules + FROM users u + LEFT JOIN families f ON f.id = u.family_id + WHERE u.id = ? + "); + $stmt->execute([$cookieUserId]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + // On vérifie que le token correspond au hash stocké + if ($user && $user['remember_token'] && hash_equals($user['remember_token'], hash('sha256', $cookieToken))) { + if ($user['is_active'] && (!$user['family_id'] || $user['family_active'])) { + // Connexion silencieuse réussie, on peuple la session + $_SESSION['user'] = [ + 'id' => (int)$user['id'], + 'username' => $user['username'], + 'display_name' => $user['display_name'], + 'family_id' => (int)$user['family_id'], + 'is_admin' => (bool)$user['is_admin'], + ]; + $_SESSION['family_db'] = $user['db_name']; + $_SESSION['app_lang'] = $user['lang'] ?? 'fr'; + $_SESSION['enabled_modules'] = json_decode($user['enabled_modules'] ?? '["calendar","budget","holidays","gifts","calendar_ios"]', true); + } + } + } + } + // --------------------------------------- + if (empty($_SESSION['user'])) { if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) || (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) { header('Content-Type: application/json'); diff --git a/includes/lang/ca.php b/includes/lang/ca.php index 818e080..e26524d 100644 --- a/includes/lang/ca.php +++ b/includes/lang/ca.php @@ -72,6 +72,7 @@ return [ 'error_missing_fields' => 'Camps obligatoris.', 'error_invalid_credentials' => 'Identificadors incorrectes.', 'login_subtitle' => 'Si us plau, identifiqueu-vos per continuar', + 'login_remember_me' => "Recorda'm", // ========================================== // ACCUEIL / INDEX diff --git a/includes/lang/en.php b/includes/lang/en.php index 7742f82..99ca0e8 100644 --- a/includes/lang/en.php +++ b/includes/lang/en.php @@ -73,6 +73,7 @@ return [ 'error_missing_fields' => 'All fields are required.', 'error_invalid_credentials' => 'Invalid credentials.', 'login_subtitle' => 'Please sign in to continue', + 'login_remember_me' => 'Remember me', // ========================================== // HOME / INDEX diff --git a/includes/lang/fr.php b/includes/lang/fr.php index 120ca40..424f81f 100644 --- a/includes/lang/fr.php +++ b/includes/lang/fr.php @@ -15,6 +15,8 @@ return [ 'tests_report_copied' => 'Rapport copié dans le presse-papier !', 'tests_live_preview' => 'Aperçu en direct', + + // ========================================== // GLOBAL & NAVIGATION @@ -75,6 +77,7 @@ return [ 'error_missing_fields' => 'Champs obligatoires.', 'error_invalid_credentials' => 'Identifiants incorrects.', 'login_subtitle' => 'Veuillez vous identifier pour continuer', + 'login_remember_me' => 'Se souvenir de moi', // ========================================== // ACCUEIL / INDEX diff --git a/login.php b/login.php index a22c90c..a11def5 100644 --- a/login.php +++ b/login.php @@ -51,6 +51,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $_SESSION['family_db'] = $user['db_name']; $_SESSION['app_lang'] = $user['lang'] ?? 'fr'; $_SESSION['enabled_modules'] = json_decode($user['enabled_modules'] ?? '["calendar","budget","holidays","gifts","calendar_ios"]', true); + + // --- NOUVEAU : Logique "Se souvenir de moi" --- + if (isset($_POST['remember_me']) && $_POST['remember_me'] === '1') { + $token = bin2hex(random_bytes(32)); // Token aléatoire sécurisé + $hashedToken = hash('sha256', $token); // On stocke le hash en BDD + + $meta_pdo->prepare("UPDATE users SET remember_token = ? WHERE id = ?")->execute([$hashedToken, $user['id']]); + + // Création du cookie sécurisé (Expire dans 30 jours, HTTPOnly) + $cookieValue = $user['id'] . ':' . $token; + setcookie('hh_remember', $cookieValue, time() + (86400 * 30), '/', '', isset($_SERVER['HTTPS']), true); + } + // ---------------------------------------------- + $redirectTo = $_GET['redirect'] ?? '/index.php'; header('Location: ' . $redirectTo); exit; @@ -95,6 +109,11 @@ require __DIR__ . '/header.php'; required placeholder="••••••" autocomplete="current-password"> +