[FÁZE-1][auth] Přidán hlavní soubor autentizace se session a remember me

This commit is contained in:
stepan
2026-03-16 23:44:10 +01:00
parent c39ed094ba
commit e0c5a25fdc
+247
View File
@@ -0,0 +1,247 @@
<?php
// ============================================================
// HLAVNÍ SOUBOR AUTENTIZACE
// ============================================================
// Tento soubor vložíš na začátek každé stránky, kterou chceš
// chránit přihlášením:
//
// require_once 'auth/auth.php';
//
// Po jeho načtení máš k dispozici tyto proměnné:
//
// $auth_prihlasen ... true / false
// $auth_uzivatel['id'] ... ID přihlášeného uživatele
// $auth_uzivatel['email'] ... email přihlášeného uživatele
// $auth_uzivatel['admin'] ... true / false
//
// Pokud je v config.php VYZADOVAT_PRIHLASENI = true a uživatel
// není přihlášen, bude automaticky přesměrován na přihlašovací
// stránku. Jinak se stránka zobrazí a ty si sám rozhodneš,
// co nepřihlášenému uživateli ukážeš.
// ============================================================
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/db.php';
// ------------------------------------------------------------
// SPUŠTĚNÍ SESSION
// ------------------------------------------------------------
// Nastavíme parametry session cookie ještě PŘED session_start().
// HttpOnly = JavaScript nemůže cookie číst (ochrana před XSS)
// SameSite = cookie se neposílá při požadavcích z jiných webů
// (ochrana před CSRF)
// Secure = cookie se posílá jen přes HTTPS (pokud web běží na HTTPS)
session_name(SESSION_NAZEV);
session_set_cookie_params([
'lifetime' => 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.
}