diff --git a/auth/login.php b/auth/login.php new file mode 100644 index 0000000..70f843e --- /dev/null +++ b/auth/login.php @@ -0,0 +1,280 @@ + 0, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Strict', + // 'secure' => true, +]); +session_start(); + +// Pokud je uživatel již přihlášen, přesměrujeme ho rovnou dál +if (isset($_SESSION['uzivatel_id'])) { + header('Location: ' . AUTH_REDIRECT_PO_PRIHLASENI); + exit; +} + +// ------------------------------------------------------------ +// CSRF TOKEN +// ------------------------------------------------------------ +// CSRF token je náhodný řetězec, který se vygeneruje při načtení +// stránky a uloží do session. Formulář ho odešle jako skryté pole. +// Při zpracování formuláře ověříme, že token sedí – tím zabráníme +// útoku, kdy by cizí web odeslal formulář jménem přihlášeného +// uživatele. + +if (empty($_SESSION['csrf_token'])) { + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); +} +$csrf_token = $_SESSION['csrf_token']; + +// ------------------------------------------------------------ +// ZPRACOVÁNÍ FORMULÁŘE +// ------------------------------------------------------------ + +$chyba = ''; // chybová hláška pro uživatele +$email_hodnota = ''; // předvyplnění emailového pole po chybě + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + // -- Ověření CSRF tokenu ---------------------------------- + $csrf_z_formulare = $_POST['csrf_token'] ?? ''; + // hash_equals porovnává řetězce v konstantním čase – + // chrání před timing útoky na CSRF token + if (!hash_equals($csrf_token, $csrf_z_formulare)) { + $chyba = 'Neplatný požadavek. Zkuste stránku obnovit a přihlásit se znovu.'; + } + + if (empty($chyba)) { + + // -- Načtení hodnot z formuláře ----------------------- + $email = trim($_POST['email'] ?? ''); + $heslo = $_POST['heslo'] ?? ''; + $zapamatovat = isset($_POST['zapamatovat']); + + $email_hodnota = htmlspecialchars($email); // pro předvyplnění pole + + // -- Základní validace -------------------------------- + if (empty($email) || empty($heslo)) { + $chyba = 'Zadejte email a heslo.'; + } + } + + if (empty($chyba)) { + + // -- Kontrola brute force ----------------------------- + // Spočítáme neúspěšné pokusy z této IP adresy + // nebo pro tento email za poslední časové okno + $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + $okno_od = date('Y-m-d H:i:s', time() - BRUTE_OKNO); + + $stmt = $pdo->prepare(" + SELECT COUNT(*) AS pocet + FROM `" . DB_TABULKA_BRUTE . "` + WHERE (`ip_adresa` = :ip OR `email` = :email) + AND `cas` > :okno_od + "); + $stmt->execute([ + ':ip' => $ip, + ':email' => $email, + ':okno_od' => $okno_od, + ]); + $pocet_pokusu = $stmt->fetch()['pocet']; + + if ($pocet_pokusu >= BRUTE_MAX_POKUSU) { + // Nezradíme přesný důvod – jen obecná hláška + $chyba = 'Příliš mnoho neúspěšných pokusů. Zkuste to prosím za chvíli.'; + } + } + + if (empty($chyba)) { + + // -- Ověření emailu a hesla v DB ---------------------- + $stmt = $pdo->prepare(" + SELECT `id`, `email`, `heslo`, `admin` + FROM `" . DB_TABULKA_UZIVATELE . "` + WHERE `email` = :email + LIMIT 1 + "); + $stmt->execute([':email' => $email]); + $uzivatel = $stmt->fetch(); + + // Ověříme heslo pomocí password_verify(). + // DŮLEŽITÉ: I když uživatel neexistuje, zavoláme + // password_verify na fiktivní hash – aby útočník + // nemohl podle doby odpovědi poznat, zda email existuje. + $fiktivni_hash = '$2y$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ01234'; + $hash_k_overeni = $uzivatel ? $uzivatel['heslo'] : $fiktivni_hash; + $heslo_ok = password_verify($heslo, $hash_k_overeni); + + if (!$uzivatel || !$heslo_ok) { + + // Přihlášení selhalo – zapíšeme neúspěšný pokus + $stmt2 = $pdo->prepare(" + INSERT INTO `" . DB_TABULKA_BRUTE . "` + (`ip_adresa`, `email`) + VALUES + (:ip, :email) + "); + $stmt2->execute([ + ':ip' => $ip, + ':email' => $email, + ]); + + // Obecná hláška – neříkáme, zda byl špatný email nebo heslo + $chyba = 'Nesprávný email nebo heslo.'; + + } else { + + // -- Přihlášení úspěšné --------------------------- + + // Regenerujeme session ID – ochrana před session fixation + // (útočník mohl podstrčit uživateli konkrétní session ID) + session_regenerate_id(true); + + // Zapíšeme uživatele do session + $_SESSION['uzivatel_id'] = $uzivatel['id']; + $_SESSION['email'] = $uzivatel['email']; + $_SESSION['admin'] = (bool) $uzivatel['admin']; + $_SESSION['posledni_aktivita'] = time(); + + // Vygenerujeme nový CSRF token pro další požadavky + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + + // -- Remember me ---------------------------------- + if ($zapamatovat) { + + // Selector = veřejný identifikátor (jde do cookie i DB) + // Token = tajný řetězec (do cookie jde plaintext, + // do DB jen bcrypt hash) + $selector = bin2hex(random_bytes(16)); // 32 znaků hex + $token = bin2hex(random_bytes(32)); // 64 znaků hex + $token_hash = password_hash($token, PASSWORD_DEFAULT); + $expiruje = date('Y-m-d H:i:s', time() + REMEMBER_EXPIRACE); + + // Uložíme token do DB + $stmt3 = $pdo->prepare(" + INSERT INTO `" . DB_TABULKA_TOKENY . "` + (`uzivatel_id`, `selector`, `token_hash`, `expiruje`) + VALUES + (:uzivatel_id, :selector, :token_hash, :expiruje) + "); + $stmt3->execute([ + ':uzivatel_id' => $uzivatel['id'], + ':selector' => $selector, + ':token_hash' => $token_hash, + ':expiruje' => $expiruje, + ]); + + // Nastavíme cookie v prohlížeči + // Cookie obsahuje POUZE selector a token – žádné heslo, + // žádné ID, žádný příznak admin + $cookie_expirace = time() + REMEMBER_EXPIRACE; + setcookie('auth_selector', $selector, [ + 'expires' => $cookie_expirace, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Strict', + // 'secure' => true, + ]); + setcookie('auth_token', $token, [ + 'expires' => $cookie_expirace, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Strict', + // 'secure' => true, + ]); + } + + // -- Přesměrování po přihlášení ------------------- + // Pokud byl uživatel přesměrován z jiné stránky, + // vrátíme ho tam. Jinak jde na výchozí stránku. + $redirect = $_SESSION['redirect_po_prihlaseni'] ?? AUTH_REDIRECT_PO_PRIHLASENI; + unset($_SESSION['redirect_po_prihlaseni']); + + header('Location: ' . $redirect); + exit; + } + } +} + +// ------------------------------------------------------------ +// HTML VÝSTUP +// ------------------------------------------------------------ +?> + + + + + Přihlášení – <?php echo htmlspecialchars(PROJEKT_NAZEV); ?> + + + + + + +

Přihlášení

+

+ + +

Chyba:

+ + +
+ + + + +

+
+ +

+ +

+
+ +

+ +

+ +

+ +

+ +

+ +
+ + +

Nemáš účet? Zaregistruj se.

+ + +

Zapomněl jsi heslo?

+ + + \ No newline at end of file