Files
MSPPPPaM/auth/nove_heslo.php
T

279 lines
8.4 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
// ============================================================
// OBNOVA HESLA KROK 2
// ============================================================
// Na tuto stránku přichází uživatel z odkazu v emailu.
// Odkaz obsahuje ID záznamu a plaintext token.
//
// Stránka ověří platnost tokenu a umožní nastavit nové heslo.
// ============================================================
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 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'];
// ------------------------------------------------------------
// OVĚŘENÍ ODKAZU (parametry z URL)
// ------------------------------------------------------------
$token_ok = false; // true pokud je token platný
$zaznam_id = 0; // ID záznamu v auth_password_resets
$uzivatel_id = 0; // ID uživatele
$id_z_url = $_GET['id'] ?? '';
$token_z_url = $_GET['token'] ?? '';
if (!empty($id_z_url) && !empty($token_z_url)) {
// Vyhledáme záznam podle ID
$stmt = $pdo->prepare("
SELECT `id`, `uzivatel_id`, `token_hash`, `expiruje`
FROM `" . DB_TABULKA_RESET . "`
WHERE `id` = :id
LIMIT 1
");
$stmt->execute([':id' => (int) $id_z_url]);
$zaznam = $stmt->fetch();
if ($zaznam) {
// Zkontrolujeme expiraci
if (time() < strtotime($zaznam['expiruje'])) {
// Ověříme token (password_verify = konstantní čas,
// ochrana před timing útoky)
if (password_verify($token_z_url, $zaznam['token_hash'])) {
$token_ok = true;
$zaznam_id = $zaznam['id'];
$uzivatel_id = $zaznam['uzivatel_id'];
}
}
}
}
// ------------------------------------------------------------
// ZPRACOVÁNÍ FORMULÁŘE (nastavení nového hesla)
// ------------------------------------------------------------
$chyba = '';
$uspech = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $token_ok) {
// -- 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)) {
$heslo = $_POST['heslo'] ?? '';
$heslo2 = $_POST['heslo2'] ?? '';
// Validace hesla
if (empty($heslo)) {
$chyba = 'Heslo nesmí být prázdné.';
} elseif (mb_strlen($heslo) < HESLO_MIN_DELKA) {
$chyba = 'Heslo musí mít alespoň ' . HESLO_MIN_DELKA . ' znaků.';
} elseif ($heslo !== $heslo2) {
$chyba = 'Hesla se neshodují.';
}
}
if (empty($chyba)) {
try {
// Zahashujeme nové heslo
$heslo_hash = password_hash($heslo, PASSWORD_DEFAULT);
// Aktualizujeme heslo uživatele v DB
$stmt = $pdo->prepare("
UPDATE `" . DB_TABULKA_UZIVATELE . "`
SET `heslo` = :heslo
WHERE `id` = :id
");
$stmt->execute([
':heslo' => $heslo_hash,
':id' => $uzivatel_id,
]);
// Smažeme použitý reset token je jednorázový
$stmt2 = $pdo->prepare("
DELETE FROM `" . DB_TABULKA_RESET . "`
WHERE `id` = :id
");
$stmt2->execute([':id' => $zaznam_id]);
// Smažeme všechny remember me tokeny tohoto uživatele
// po změně hesla jsou všechna zařízení odhlášena
$stmt3 = $pdo->prepare("
DELETE FROM `" . DB_TABULKA_TOKENY . "`
WHERE `uzivatel_id` = :uzivatel_id
");
$stmt3->execute([':uzivatel_id' => $uzivatel_id]);
$uspech = true;
} catch (PDOException $e) {
error_log('Chyba při změně hesla: ' . $e->getMessage());
$chyba = 'Při ukládání hesla došlo k chybě. Zkuste to prosím znovu.';
}
}
}
// ------------------------------------------------------------
// HTML VÝSTUP
// ------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<title>Nové heslo <?php echo htmlspecialchars(PROJEKT_NAZEV); ?></title>
<?php if (AUTH_CSS !== ''): ?>
<link rel="stylesheet" href="<?php echo htmlspecialchars(AUTH_CSS); ?>">
<?php endif; ?>
</head>
<body>
<h1>Nastavení nového hesla</h1>
<h2><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h2>
<?php if ($uspech): ?>
<p><strong>Heslo bylo úspěšně změněno.</strong></p>
<p>Všechna zařízení byla odhlášena. Můžeš se nyní
<a href="<?php echo htmlspecialchars(AUTH_LOGIN_URL); ?>">přihlásit</a>
novým heslem.</p>
<?php elseif (!$token_ok): ?>
<!-- Token je neplatný nebo vypršel -->
<p><strong>Tento odkaz pro obnovu hesla je neplatný nebo vypršel.</strong></p>
<p>Požádej o
<a href="<?php echo htmlspecialchars(dirname($_SERVER['PHP_SELF'])); ?>/reset_hesla.php">
nový odkaz pro obnovu hesla</a>.</p>
<?php else: ?>
<!-- Token je platný zobrazíme formulář -->
<?php if (!empty($chyba)): ?>
<p><strong>Chyba: <?php echo htmlspecialchars($chyba); ?></strong></p>
<?php endif; ?>
<p>Zadej nové heslo pro svůj účet.</p>
<form method="POST" action="">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<p>
<label for="heslo">Nové heslo:</label><br>
<input
type="password"
id="heslo"
name="heslo"
required
autocomplete="new-password"
>
<br>
<span id="heslo-sila">Síla hesla: zadej heslo</span>
</p>
<p>
<label for="heslo2">Nové heslo znovu (pro ověření):</label><br>
<input
type="password"
id="heslo2"
name="heslo2"
required
autocomplete="new-password"
>
</p>
<p>
<button type="submit" id="tlacitko-odeslat" disabled>
Nastavit nové heslo
</button>
<span id="tlacitko-duvod"> (čekám na dostatečně silné heslo)</span>
</p>
</form>
<?php endif; ?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.4.2/zxcvbn.js"></script>
<script>
const MIN_SILA = <?php echo (int) HESLO_MIN_SILA; ?>;
const POPISKY_SILY = [
'Velmi slabé',
'Slabé',
'Průměrné',
'Silné',
'Velmi silné'
];
const inputHeslo = document.getElementById('heslo');
const spanSila = document.getElementById('heslo-sila');
const tlacitko = document.getElementById('tlacitko-odeslat');
const spanDuvod = document.getElementById('tlacitko-duvod');
// Prvky existují jen pokud je zobrazen formulář
// (ne při úspěchu nebo neplatném tokenu)
if (inputHeslo) {
inputHeslo.addEventListener('input', function () {
const hodnota = this.value;
if (hodnota.length === 0) {
spanSila.textContent = 'Síla hesla: zadej heslo';
tlacitko.disabled = true;
spanDuvod.textContent = ' (čekám na dostatečně silné heslo)';
return;
}
const vysledek = zxcvbn(hodnota);
const skore = vysledek.score;
spanSila.textContent = 'Síla hesla: ' + POPISKY_SILY[skore] + ' (' + skore + '/4)';
if (skore >= MIN_SILA) {
tlacitko.disabled = false;
spanDuvod.textContent = '';
} else {
tlacitko.disabled = true;
spanDuvod.textContent = ' (heslo je příliš slabé, potřebuji alespoň ' + MIN_SILA + '/4)';
}
});
}
</script>
</body>
</html>