Files
MSPPPPaM/auth/auth.php
T

270 lines
8.8 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
// ============================================================
// Zpracovává přihlášení z dvou zdrojů:
// 1) Vlastní formulář na této stránce (login.php)
// 2) Formulář $auth_login_html vložený na jiné stránce
// (v tom případě přijde v POST i redirect_url)
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/db.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
// ------------------------------------------------------------
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrf_token = $_SESSION['csrf_token'];
// ------------------------------------------------------------
// ZPRACOVÁNÍ FORMULÁŘE
// ------------------------------------------------------------
$chyba = '';
$email_hodnota = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// -- Ověření CSRF tokenu ----------------------------------
$csrf_z_formulare = $_POST['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)) {
$email = trim($_POST['email'] ?? '');
$heslo = $_POST['heslo'] ?? '';
$zapamatovat = isset($_POST['zapamatovat']);
$email_hodnota = htmlspecialchars($email);
if (empty($email) || empty($heslo)) {
$chyba = 'Zadejte email a heslo.';
}
}
if (empty($chyba)) {
// -- Kontrola brute force -----------------------------
$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) {
$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();
// 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) {
// 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,
]);
$chyba = 'Nesprávný email nebo heslo.';
} else {
// -- Přihlášení úspěšné ---------------------------
session_regenerate_id(true);
$_SESSION['uzivatel_id'] = $uzivatel['id'];
$_SESSION['email'] = $uzivatel['email'];
$_SESSION['admin'] = (bool) $uzivatel['admin'];
$_SESSION['posledni_aktivita'] = time();
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// -- Remember me ----------------------------------
if ($zapamatovat) {
$selector = bin2hex(random_bytes(16));
$token = bin2hex(random_bytes(32));
$token_hash = password_hash($token, PASSWORD_DEFAULT);
$expiruje = date('Y-m-d H:i:s', time() + REMEMBER_EXPIRACE);
$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,
]);
$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í -------------------
// Priorita zdrojů pro redirect URL:
// 1) redirect_url z POST (formulář byl na jiné stránce)
// 2) redirect_po_prihlaseni v session (uloženo v auth.php)
// 3) výchozí stránka z config.php
$redirect_z_post = trim($_POST['redirect_url'] ?? '');
$redirect_ze_session = $_SESSION['redirect_po_prihlaseni'] ?? '';
// Bezpečnostní kontrola redirect URL:
// Povolujeme pouze relativní URL začínající lomítkem,
// aby útočník nemohl přesměrovat na cizí web
// (tzv. open redirect útok)
if (!empty($redirect_z_post) && str_starts_with($redirect_z_post, '/')) {
$redirect = $redirect_z_post;
} elseif (!empty($redirect_ze_session) && str_starts_with($redirect_ze_session, '/')) {
$redirect = $redirect_ze_session;
} else {
$redirect = 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="">
<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(AUTH_REGISTRACE_URL); ?>">Nemáš účet? Zaregistruj se.</a></p>
<?php endif; ?>
<p><a href="<?php echo htmlspecialchars(AUTH_RESET_URL); ?>">Zapomněl jsi heslo?</a></p>
</body>
</html>