[FÁZE-2][auth] Přidána stránka pro žádost o obnovu hesla

This commit is contained in:
stepan
2026-03-17 00:07:10 +01:00
parent feae0537cb
commit b866eaafd0
+196
View File
@@ -0,0 +1,196 @@
<?php
// ============================================================
// OBNOVA HESLA KROK 1
// ============================================================
// Uživatel zadá svůj email. Pokud existuje v DB, odešleme mu
// email s odkazem na stránku pro zadání nového hesla.
//
// DŮLEŽITÉ: Vždy zobrazíme stejnou hlášku bez ohledu na to,
// zda email v DB existuje nebo ne. Útočník tak nezjistí,
// které emaily jsou v systému registrovány.
// ============================================================
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/mail.php';
session_name(SESSION_NAZEV);
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'httponly' => true,
'samesite' => 'Strict',
// 'secure' => true,
]);
session_start();
// Pokud je uživatel přihlášen, přesměrujeme ho
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
// ------------------------------------------------------------
// Tuto hlášku zobrazíme vždy po odeslání formuláře
// bez ohledu na to, zda email existuje
$zprava_po_odeslani = 'Pokud je zadaný email registrován, '
. 'přijde ti na něj odkaz pro obnovu hesla. '
. 'Odkaz bude platný 1 hodinu.';
$odeslano = false;
$chyba = '';
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 zkusit znovu.';
}
if (empty($chyba)) {
$email = trim($_POST['email'] ?? '');
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$chyba = 'Zadejte platnou emailovou adresu.';
}
}
if (empty($chyba)) {
// Vyhledáme uživatele v DB
$stmt = $pdo->prepare("
SELECT `id`
FROM `" . DB_TABULKA_UZIVATELE . "`
WHERE `email` = :email
LIMIT 1
");
$stmt->execute([':email' => $email]);
$uzivatel = $stmt->fetch();
if ($uzivatel) {
// -- Uživatel existuje vytvoříme reset token ----
// Smažeme případné staré (nepoužité) tokeny tohoto uživatele
$stmt2 = $pdo->prepare("
DELETE FROM `" . DB_TABULKA_RESET . "`
WHERE `uzivatel_id` = :uzivatel_id
");
$stmt2->execute([':uzivatel_id' => $uzivatel['id']]);
// Vygenerujeme nový token
// Do emailu jde plaintext, do DB jde jen hash
$token = bin2hex(random_bytes(32)); // 64 znaků hex
$token_hash = password_hash($token, PASSWORD_DEFAULT);
$expiruje = date('Y-m-d H:i:s', time() + 3600); // 1 hodina
$stmt3 = $pdo->prepare("
INSERT INTO `" . DB_TABULKA_RESET . "`
(`uzivatel_id`, `token_hash`, `expiruje`)
VALUES
(:uzivatel_id, :token_hash, :expiruje)
");
$stmt3->execute([
':uzivatel_id' => $uzivatel['id'],
':token_hash' => $token_hash,
':expiruje' => $expiruje,
]);
$reset_id = $pdo->lastInsertId();
// Sestavíme odkaz pro obnovu hesla
// Odkaz obsahuje ID záznamu a plaintext token
$odkaz = PROJEKT_URL . '/auth/nove_heslo.php'
. '?id=' . urlencode($reset_id)
. '&token=' . urlencode($token);
// Odešleme email
$predmet = 'Obnova hesla ' . PROJEKT_NAZEV;
$text = "Ahoj,\n\n"
. "požádal(a) jsi o obnovu hesla na " . PROJEKT_NAZEV . ".\n\n"
. "Pro nastavení nového hesla klikni na tento odkaz:\n"
. $odkaz . "\n\n"
. "Odkaz je platný 1 hodinu.\n\n"
. "Pokud jsi o obnovu hesla nežádal(a), tento email ignoruj.\n\n"
. PROJEKT_NAZEV;
odesli_mail($email, $predmet, $text);
}
// Zobrazíme stejnou hlášku bez ohledu na existenci emailu
$odeslano = true;
}
}
// ------------------------------------------------------------
// HTML VÝSTUP
// ------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<title>Obnova hesla <?php echo htmlspecialchars(PROJEKT_NAZEV); ?></title>
<?php if (AUTH_CSS !== ''): ?>
<link rel="stylesheet" href="<?php echo htmlspecialchars(AUTH_CSS); ?>">
<?php endif; ?>
</head>
<body>
<h1>Obnova hesla</h1>
<h2><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h2>
<?php if ($odeslano): ?>
<p><?php echo htmlspecialchars($zprava_po_odeslani); ?></p>
<p><a href="<?php echo htmlspecialchars(AUTH_LOGIN_URL); ?>">Zpět na přihlášení</a></p>
<?php else: ?>
<?php if (!empty($chyba)): ?>
<p><strong>Chyba: <?php echo htmlspecialchars($chyba); ?></strong></p>
<?php endif; ?>
<p>Zadej svůj email a pošleme ti odkaz pro obnovu hesla.</p>
<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"
required
autocomplete="email"
>
</p>
<p>
<button type="submit">Odeslat odkaz pro obnovu hesla</button>
</p>
</form>
<p><a href="<?php echo htmlspecialchars(AUTH_LOGIN_URL); ?>">Zpět na přihlášení</a></p>
<?php endif; ?>
</body>
</html>