From e0c5a25fdc490f4117057fe4b08d82f8fdb93e37 Mon Sep 17 00:00:00 2001 From: stepan Date: Mon, 16 Mar 2026 23:44:10 +0100 Subject: [PATCH] =?UTF-8?q?[F=C3=81ZE-1][auth]=20P=C5=99id=C3=A1n=20hlavn?= =?UTF-8?q?=C3=AD=20soubor=20autentizace=20se=20session=20a=20remember=20m?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth/auth.php | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 auth/auth.php diff --git a/auth/auth.php b/auth/auth.php new file mode 100644 index 0000000..83bdd9d --- /dev/null +++ b/auth/auth.php @@ -0,0 +1,247 @@ + 0, // 0 = cookie platí do zavření prohlížeče + // (trvalost zajišťuje remember me, ne session) + 'path' => '/', + 'httponly' => true, // JavaScript k cookie nemá přístup + 'samesite' => 'Strict', // ochrana před CSRF + // 'secure' => true, // odkomentuj pokud web běží na HTTPS +]); +session_start(); + +// ------------------------------------------------------------ +// VÝCHOZÍ STAV – uživatel není přihlášen +// ------------------------------------------------------------ + +$auth_prihlasen = false; +$auth_uzivatel = [ + 'id' => null, + 'email' => null, + 'admin' => false, +]; + +// ------------------------------------------------------------ +// KROK 1: Existuje platná session? +// ------------------------------------------------------------ +// Session je nejrychlejší způsob ověření – nevyžaduje dotaz do DB. +// Pokud session existuje a obsahuje uzivatel_id, uživatel je +// přihlášen. Session data jsou uložena na serveru, uživatel +// je nemůže zfalšovat. + +if (isset($_SESSION['uzivatel_id']) && isset($_SESSION['email'])) { + + // Kontrola expirace session (nečinnost) + // Pokud uživatel byl příliš dlouho neaktivní, session zrušíme + if (isset($_SESSION['posledni_aktivita']) && + (time() - $_SESSION['posledni_aktivita']) > SESSION_EXPIRACE) { + + // Session vypršela – zrušíme ji + $_SESSION = []; + session_destroy(); + + } else { + + // Session je platná – uživatel je přihlášen + $auth_prihlasen = true; + $auth_uzivatel = [ + 'id' => $_SESSION['uzivatel_id'], + 'email' => $_SESSION['email'], + // Přetypování na bool – ochrana pro případ, že by v session + // byla jiná hodnota než true/false + 'admin' => (bool) $_SESSION['admin'], + ]; + + // Aktualizujeme čas poslední aktivity + $_SESSION['posledni_aktivita'] = time(); + + } +} + +// ------------------------------------------------------------ +// KROK 2: Session neexistuje – zkusíme remember me cookie +// ------------------------------------------------------------ +// Cookie obsahuje pouze selector a token. Nikdy neobsahuje +// heslo, ID uživatele ani příznak admin. Vše citlivé je na serveru. + +if (!$auth_prihlasen && + isset($_COOKIE['auth_selector']) && + isset($_COOKIE['auth_token'])) { + + $cookie_selector = $_COOKIE['auth_selector']; + $cookie_token = $_COOKIE['auth_token']; + + // Vyhledáme záznam v DB podle selectoru (přesná shoda, rychlé) + $stmt = $pdo->prepare(" + SELECT + t.id AS token_id, + t.uzivatel_id, + t.token_hash, + t.expiruje, + u.email, + u.admin + FROM `" . DB_TABULKA_TOKENY . "` t + JOIN `" . DB_TABULKA_UZIVATELE . "` u ON u.id = t.uzivatel_id + WHERE t.selector = :selector + LIMIT 1 + "); + $stmt->execute([':selector' => $cookie_selector]); + $zaznam = $stmt->fetch(); + + // Příznak, zda je cookie platná + $cookie_ok = false; + + if ($zaznam) { + + // Zkontrolujeme, zda token ještě nevypršel + $expiruje_timestamp = strtotime($zaznam['expiruje']); + + if (time() < $expiruje_timestamp) { + + // Ověříme tajný token pomocí password_verify(). + // Tato funkce záměrně trvá stejně dlouho bez ohledu + // na výsledek – chrání před timing útoky. + if (password_verify($cookie_token, $zaznam['token_hash'])) { + $cookie_ok = true; + } + } + } + + if ($cookie_ok) { + + // Cookie je platná – obnovíme session + // Nejprve regenerujeme session ID (ochrana před session fixation) + session_regenerate_id(true); + + $_SESSION['uzivatel_id'] = $zaznam['uzivatel_id']; + $_SESSION['email'] = $zaznam['email']; + $_SESSION['admin'] = (bool) $zaznam['admin']; + $_SESSION['posledni_aktivita'] = time(); + + $auth_prihlasen = true; + $auth_uzivatel = [ + 'id' => $zaznam['uzivatel_id'], + 'email' => $zaznam['email'], + 'admin' => (bool) $zaznam['admin'], + ]; + + // Prodloužíme platnost tokenu v DB i v cookie + $nova_expirace = time() + REMEMBER_EXPIRACE; + $nova_expirace_dt = date('Y-m-d H:i:s', $nova_expirace); + + $stmt2 = $pdo->prepare(" + UPDATE `" . DB_TABULKA_TOKENY . "` + SET `expiruje` = :expiruje + WHERE `id` = :id + "); + $stmt2->execute([ + ':expiruje' => $nova_expirace_dt, + ':id' => $zaznam['token_id'], + ]); + + // Prodloužíme cookie v prohlížeči + setcookie('auth_selector', $cookie_selector, [ + 'expires' => $nova_expirace, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Strict', + // 'secure' => true, // odkomentuj pokud web běží na HTTPS + ]); + setcookie('auth_token', $cookie_token, [ + 'expires' => $nova_expirace, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Strict', + // 'secure' => true, // odkomentuj pokud web běží na HTTPS + ]); + + // Smažeme staré (expirované) tokeny z DB + // Děláme to zde příležitostně, aby se DB neplnila starými záznamy + $stmt3 = $pdo->prepare(" + DELETE FROM `" . DB_TABULKA_TOKENY . "` + WHERE `expiruje` < :ted + "); + $stmt3->execute([':ted' => date('Y-m-d H:i:s')]); + + } else { + + // Cookie je neplatná nebo expirovaná – smažeme ji z prohlížeče + // i případný záznam z DB (pokud selector existoval) + if ($zaznam) { + $stmt4 = $pdo->prepare(" + DELETE FROM `" . DB_TABULKA_TOKENY . "` + WHERE `id` = :id + "); + $stmt4->execute([':id' => $zaznam['token_id']]); + } + + // Smažeme cookies nastavením expirace do minulosti + setcookie('auth_selector', '', [ + 'expires' => time() - 3600, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Strict', + ]); + setcookie('auth_token', '', [ + 'expires' => time() - 3600, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Strict', + ]); + } +} + +// ------------------------------------------------------------ +// KROK 3: Rozhodnutí – přesměrovat nebo pokračovat? +// ------------------------------------------------------------ + +if (!$auth_prihlasen && VYZADOVAT_PRIHLASENI) { + + // Uložíme URL aktuální stránky, aby se uživatel mohl po + // přihlášení vrátit tam, kde byl + $aktualni_url = $_SERVER['REQUEST_URI'] ?? ''; + if (!empty($aktualni_url)) { + $_SESSION['redirect_po_prihlaseni'] = $aktualni_url; + } + + // Přesměujeme na přihlašovací stránku + header('Location: ' . AUTH_LOGIN_URL); + exit; + + // Pokud je VYZADOVAT_PRIHLASENI = false, skript pokračuje dál. + // Stránka pak sama rozhodne, co nepřihlášenému uživateli ukáže, + // pomocí proměnných $auth_prihlasen a $auth_uzivatel. +} \ No newline at end of file