Files
MSPPPPaM/auth/login.php
T

280 lines
9.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
// ============================================================
// PŘIHLAŠOVACÍ STRÁNKA
// ============================================================
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/db.php';
// Spuštění session (stejné nastavení jako v auth.php)
session_name(SESSION_NAZEV);
session_set_cookie_params([
'lifetime' => 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
// ------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<title>Přihlášení <?php echo htmlspecialchars(PROJEKT_NAZEV); ?></title>
<?php if (AUTH_CSS !== ''): ?>
<link rel="stylesheet" href="<?php echo htmlspecialchars(AUTH_CSS); ?>">
<?php endif; ?>
</head>
<body>
<h1>Přihlášení</h1>
<h2><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h2>
<?php if (!empty($chyba)): ?>
<p><strong>Chyba: <?php echo htmlspecialchars($chyba); ?></strong></p>
<?php endif; ?>
<form method="POST" action="">
<!-- CSRF token skryté pole, ověřuje se při odeslání formuláře -->
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<p>
<label for="email">Email:</label><br>
<input
type="email"
id="email"
name="email"
value="<?php echo $email_hodnota; ?>"
required
autocomplete="email"
>
</p>
<p>
<label for="heslo">Heslo:</label><br>
<input
type="password"
id="heslo"
name="heslo"
required
autocomplete="current-password"
>
</p>
<p>
<label>
<input type="checkbox" name="zapamatovat" value="1">
Zapamatovat si mě
</label>
</p>
<p>
<button type="submit">Přihlásit se</button>
</p>
</form>
<?php if (REGISTRACE_OTEVRENA): ?>
<p><a href="<?php echo htmlspecialchars(__DIR__); ?>/registrace.php">Nemáš účet? Zaregistruj se.</a></p>
<?php endif; ?>
<p><a href="<?php echo htmlspecialchars(__DIR__); ?>/reset_hesla.php">Zapomněl jsi heslo?</a></p>
</body>
</html>