1 parent
f19a7442de
commit
b3139b577a
7 files changed
+87
-1
No files matched your search
@@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|||||||
is_admin TINYINT(1) DEFAULT 0,
|
is_admin TINYINT(1) DEFAULT 0,
|
||||||
is_active TINYINT(1) DEFAULT 1,
|
is_active TINYINT(1) DEFAULT 1,
|
||||||
lang VARCHAR(5) DEFAULT 'fr',
|
lang VARCHAR(5) DEFAULT 'fr',
|
||||||
|
remember_token VARCHAR(255) DEFAULT NULL, -- 👈 NOUVEAU CHAMP ICI
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (family_id) REFERENCES families(id)
|
FOREIGN KEY (family_id) REFERENCES families(id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ if (session_status() === PHP_SESSION_NONE) {
|
|||||||
session_start();
|
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é.
|
* Vérifie qu'un utilisateur est connecté.
|
||||||
* Si non, le redirige vers la page de login avec ?redirect=URL_DEMANDEE
|
* 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();
|
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($_SESSION['user'])) {
|
||||||
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) || (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) {
|
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) || (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ return [
|
|||||||
'error_missing_fields' => 'Camps obligatoris.',
|
'error_missing_fields' => 'Camps obligatoris.',
|
||||||
'error_invalid_credentials' => 'Identificadors incorrectes.',
|
'error_invalid_credentials' => 'Identificadors incorrectes.',
|
||||||
'login_subtitle' => 'Si us plau, identifiqueu-vos per continuar',
|
'login_subtitle' => 'Si us plau, identifiqueu-vos per continuar',
|
||||||
|
'login_remember_me' => "Recorda'm",
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// ACCUEIL / INDEX
|
// ACCUEIL / INDEX
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ return [
|
|||||||
'error_missing_fields' => 'All fields are required.',
|
'error_missing_fields' => 'All fields are required.',
|
||||||
'error_invalid_credentials' => 'Invalid credentials.',
|
'error_invalid_credentials' => 'Invalid credentials.',
|
||||||
'login_subtitle' => 'Please sign in to continue',
|
'login_subtitle' => 'Please sign in to continue',
|
||||||
|
'login_remember_me' => 'Remember me',
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// HOME / INDEX
|
// HOME / INDEX
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ return [
|
|||||||
'tests_live_preview' => 'Aperçu en direct',
|
'tests_live_preview' => 'Aperçu en direct',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// GLOBAL & NAVIGATION
|
// GLOBAL & NAVIGATION
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -75,6 +77,7 @@ return [
|
|||||||
'error_missing_fields' => 'Champs obligatoires.',
|
'error_missing_fields' => 'Champs obligatoires.',
|
||||||
'error_invalid_credentials' => 'Identifiants incorrects.',
|
'error_invalid_credentials' => 'Identifiants incorrects.',
|
||||||
'login_subtitle' => 'Veuillez vous identifier pour continuer',
|
'login_subtitle' => 'Veuillez vous identifier pour continuer',
|
||||||
|
'login_remember_me' => 'Se souvenir de moi',
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// ACCUEIL / INDEX
|
// ACCUEIL / INDEX
|
||||||
|
|||||||
@@ -51,6 +51,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$_SESSION['family_db'] = $user['db_name'];
|
$_SESSION['family_db'] = $user['db_name'];
|
||||||
$_SESSION['app_lang'] = $user['lang'] ?? 'fr';
|
$_SESSION['app_lang'] = $user['lang'] ?? 'fr';
|
||||||
$_SESSION['enabled_modules'] = json_decode($user['enabled_modules'] ?? '["calendar","budget","holidays","gifts","calendar_ios"]', true);
|
$_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';
|
$redirectTo = $_GET['redirect'] ?? '/index.php';
|
||||||
header('Location: ' . $redirectTo);
|
header('Location: ' . $redirectTo);
|
||||||
exit;
|
exit;
|
||||||
@@ -95,6 +109,11 @@ require __DIR__ . '/header.php';
|
|||||||
required placeholder="••••••" autocomplete="current-password">
|
required placeholder="••••••" autocomplete="current-password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pf-form-group" style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<input type="checkbox" id="remember_me" name="remember_me" value="1" class="pf-checkbox-lg">
|
||||||
|
<label class="pf-label" for="remember_me" style="margin: 0; cursor: pointer;"><?= tr('login_remember_me') ?? 'Se souvenir de moi' ?></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="pf-btn pf-btn-block">
|
<button type="submit" class="pf-btn pf-btn-block">
|
||||||
<?= tr('btn_login_submit') ?>
|
<?= tr('btn_login_submit') ?>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+15
-1
@@ -1,6 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
|
// --- NOUVEAU : Révocation du "Se souvenir de moi" ---
|
||||||
|
if (isset($_SESSION['user']['id'])) {
|
||||||
|
require_once __DIR__ . '/includes/meta_db.php';
|
||||||
|
// On invalide le token en base de données
|
||||||
|
$stmt = $meta_pdo->prepare("UPDATE users SET remember_token = NULL WHERE id = ?");
|
||||||
|
$stmt->execute([$_SESSION['user']['id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On détruit le cookie côté navigateur
|
||||||
|
if (isset($_COOKIE['hh_remember'])) {
|
||||||
|
setcookie('hh_remember', '', time() - 3600, '/', '', isset($_SERVER['HTTPS']), true);
|
||||||
|
}
|
||||||
|
// ----------------------------------------------------
|
||||||
|
|
||||||
// On vide toutes les variables de session
|
// On vide toutes les variables de session
|
||||||
$_SESSION = [];
|
$_SESSION = [];
|
||||||
|
|
||||||
@@ -21,6 +35,6 @@ if (ini_get("session.use_cookies")) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// On renvoie vers la page de login (ou index si tu préfères)
|
// On renvoie vers la page de login
|
||||||
header('Location: /login.php');
|
header('Location: /login.php');
|
||||||
exit;
|
exit;
|
||||||
Reference in new issue
Block a user