Files
MSPPPPaM/auth/auth.php
T

247 lines
8.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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.
}