diff --git a/public_html/_incl/db.php b/public_html/_incl/db.php index e728caa..7968cfd 100644 --- a/public_html/_incl/db.php +++ b/public_html/_incl/db.php @@ -773,26 +773,53 @@ function init_active_centro(?array $auth_data = null): void // ── User session helpers (Dispositivos conectados) ──────────────────────────── /** - * Register or refresh a session record in user_sessions. + * Register a new session record in user_sessions. + * $remember_token_hash is the SHA-256 hash of the raw remember-me cookie value. * Stores a SHA-256 hash of the PHP session_id so the raw token never reaches the DB. */ -function db_register_session(string $username): void +function db_register_session(string $username, string $remember_token_hash = ''): void { $token = hash('sha256', session_id()); $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? ''; - // Only keep the first IP if X-Forwarded-For contains a list $ip = trim(explode(',', $ip)[0]); $ua = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 512); db()->prepare( - "INSERT INTO user_sessions (session_token, username, ip_address, user_agent) - VALUES (?, ?, ?, ?) + "INSERT INTO user_sessions (session_token, username, ip_address, user_agent, remember_token_hash) + VALUES (?, ?, ?, ?, ?) ON CONFLICT(session_token) DO UPDATE SET - last_active = datetime('now'), - username = excluded.username" - )->execute([$token, strtolower($username), $ip, $ua]); + last_active = datetime('now'), + remember_token_hash = COALESCE(excluded.remember_token_hash, remember_token_hash)" + )->execute([$token, strtolower($username), $ip, $ua, $remember_token_hash ?: null]); +} + +/** + * Restore a session from a remember-me token hash. + * Updates the session_token to the current PHP session_id so revocation + * continues to work after the PHP session was re-created. + * Returns the session row (including username) on success, or null if not found. + */ +function db_restore_session_by_remember_token(string $token_hash): ?array +{ + $pdo = db(); + $stmt = $pdo->prepare( + 'SELECT * FROM user_sessions WHERE remember_token_hash = ? LIMIT 1' + ); + $stmt->execute([$token_hash]); + $row = $stmt->fetch(); + if (!$row) { + return null; + } + // Migrate the row to the new PHP session_id + $new_session_token = hash('sha256', session_id()); + $pdo->prepare( + "UPDATE user_sessions + SET session_token = ?, last_active = datetime('now') + WHERE remember_token_hash = ?" + )->execute([$new_session_token, $token_hash]); + return $row; } /** Update last_active for the current session. */ diff --git a/public_html/_incl/logout.php b/public_html/_incl/logout.php index dcf590c..7b1b2a2 100644 --- a/public_html/_incl/logout.php +++ b/public_html/_incl/logout.php @@ -5,8 +5,7 @@ require_once "db.php"; $redir = safe_redir($_GET["redir"] ?? "/"); $cookie_options_expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; -setcookie("auth_user", "", $cookie_options_expired); -setcookie("auth_pass_b64", "", $cookie_options_expired); +setcookie("auth_token", "", $cookie_options_expired); db_delete_session(); session_unset(); session_destroy(); diff --git a/public_html/_incl/migrations/005_remember_token.sql b/public_html/_incl/migrations/005_remember_token.sql new file mode 100644 index 0000000..4502e24 --- /dev/null +++ b/public_html/_incl/migrations/005_remember_token.sql @@ -0,0 +1,12 @@ +-- Axia4 Migration 005: Add remember_token_hash to user_sessions +-- Replaces the auth_user + auth_pass_b64 cookie pair with a secure opaque token. +-- The raw token lives only in the browser cookie; only its SHA-256 hash is stored. + +PRAGMA journal_mode = WAL; +PRAGMA foreign_keys = ON; + +ALTER TABLE user_sessions ADD COLUMN remember_token_hash TEXT DEFAULT NULL; + +CREATE UNIQUE INDEX IF NOT EXISTS idx_user_sessions_remember + ON user_sessions (remember_token_hash) + WHERE remember_token_hash IS NOT NULL; diff --git a/public_html/_incl/tools.auth.php b/public_html/_incl/tools.auth.php index eee350d..04a84fc 100644 --- a/public_html/_incl/tools.auth.php +++ b/public_html/_incl/tools.auth.php @@ -22,28 +22,36 @@ if (str_starts_with($ua, "Axia4Auth/")) { $_SESSION["auth_user"] = $username; $_SESSION["auth_data"] = db_build_auth_data($row); $_SESSION["auth_ok"] = true; - $_COOKIE["auth_user"] = $username; - $_COOKIE["auth_pass_b64"] = base64_encode($userpass); $_SESSION["auth_external_lock"] = "header"; init_active_org($_SESSION["auth_data"]); } -// ── Cookie-based auto-login ─────────────────────────────────────────────────── -if (($_SESSION["auth_ok"] ?? false) != true - && isset($_COOKIE["auth_user"], $_COOKIE["auth_pass_b64"]) -) { - $username = $_COOKIE["auth_user"]; - $userpass = base64_decode($_COOKIE["auth_pass_b64"]); - $row = db_get_user($username); - if ($row && password_verify($userpass, $row['password_hash'])) { - $_SESSION["auth_user"] = $username; - $_SESSION["auth_data"] = db_build_auth_data($row); - $_SESSION["auth_ok"] = true; - if (empty($_SESSION["session_created"])) { - $_SESSION["session_created"] = time(); +// ── Remember-token auto-login ───────────────────────────────────────────────── +// Restores the session from the opaque auth_token cookie (no password stored). +if (($_SESSION["auth_ok"] ?? false) != true && isset($_COOKIE["auth_token"])) { + $expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, + "secure" => true, "samesite" => "Lax"]; + $raw_token = $_COOKIE["auth_token"]; + $token_hash = hash('sha256', $raw_token); + $sess_row = db_restore_session_by_remember_token($token_hash); + if ($sess_row) { + $username = $sess_row['username']; + $row = db_get_user($username); + if ($row) { + $_SESSION["auth_user"] = $username; + $_SESSION["auth_data"] = db_build_auth_data($row); + $_SESSION["auth_ok"] = true; + if (empty($_SESSION["session_created"])) { + $_SESSION["session_created"] = time(); + } + init_active_org($_SESSION["auth_data"]); + } else { + // User no longer exists — clear the stale cookie + setcookie("auth_token", "", $expired); } - init_active_org($_SESSION["auth_data"]); - db_register_session($username); + } else { + // Token not found (revoked or expired) — clear the stale cookie + setcookie("auth_token", "", $expired); } } diff --git a/public_html/_login.php b/public_html/_login.php index a6c2eb4..21d6cad 100644 --- a/public_html/_login.php +++ b/public_html/_login.php @@ -100,10 +100,11 @@ if (($_GET["google_callback"] ?? "") === "1") { $_SESSION['auth_ok'] = true; $_SESSION['session_created'] = time(); init_active_org($_SESSION['auth_data']); - db_register_session($username); + $remember_token = bin2hex(random_bytes(32)); + $remember_token_hash = hash('sha256', $remember_token); + db_register_session($username, $remember_token_hash); $cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; - setcookie("auth_user", $username, $cookie_options); - setcookie("auth_pass_b64", base64_encode($password), $cookie_options); + setcookie("auth_token", $remember_token, $cookie_options); $redir = safe_redir($state["redir"] ?? "/"); @@ -141,8 +142,7 @@ if (($_GET["google"] ?? "") === "1") { if (($_GET["logout"] ?? "") === "1") { $redir = safe_redir($_GET["redir"] ?? "/"); $cookie_options_expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; - setcookie("auth_user", "", $cookie_options_expired); - setcookie("auth_pass_b64", "", $cookie_options_expired); + setcookie("auth_token", "", $cookie_options_expired); db_delete_session(); session_unset(); session_destroy(); @@ -168,16 +168,17 @@ if (isset($_POST["user"])) { if (!$row || !isset($row["password_hash"])) { $_GET["_result"] = "El usuario no existe."; } elseif (password_verify($password, $row["password_hash"])) { + $remember_token = bin2hex(random_bytes(32)); + $remember_token_hash = hash('sha256', $remember_token); session_regenerate_id(true); $_SESSION['auth_user'] = $user; $_SESSION['auth_data'] = db_build_auth_data($row); $_SESSION['auth_ok'] = true; $_SESSION['session_created'] = time(); init_active_org($_SESSION['auth_data']); - db_register_session($user); + db_register_session($user, $remember_token_hash); $cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; - setcookie("auth_user", $user, $cookie_options); - setcookie("auth_pass_b64", base64_encode($password), $cookie_options); + setcookie("auth_token", $remember_token, $cookie_options); $redir = safe_redir($_GET["redir"] ?? "/"); header("Location: $redir"); die();