Files
MSPPPPaM/auth/admin.php
T

602 lines
22 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
// ============================================================
// ADMINISTRAČNÍ ROZHRANÍ
// ============================================================
// Tato stránka je dostupná pouze přihlášenému adminovi.
// Umožňuje:
// - zobrazit seznam uživatelů
// - založit nového uživatele
// - změnit heslo uživatele
// - smazat uživatele
// - zobrazit konfiguraci systému (citlivé údaje skryté)
// - odeslat testovací email
// ============================================================
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/mail.php';
require_once __DIR__ . '/auth.php';
// ------------------------------------------------------------
// KONTROLA: Je přihlášený uživatel admin?
// ------------------------------------------------------------
// auth.php již zajistil, že uživatel je přihlášen (nebo přesměroval).
// Zde navíc kontrolujeme příznak admin.
if (!$auth_uzivatel['admin']) {
// Uživatel je přihlášen, ale není admin
// zobrazíme obecnou chybu (nechceme prozradit, že stránka existuje)
http_response_code(404);
die('<!DOCTYPE html><html lang="cs"><head><meta charset="UTF-8"><title>Stránka nenalezena</title></head><body><p>Stránka nebyla nalezena.</p></body></html>');
}
// ------------------------------------------------------------
// CSRF TOKEN
// ------------------------------------------------------------
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrf_token = $_SESSION['csrf_token'];
// ------------------------------------------------------------
// ZPRACOVÁNÍ AKCÍ (POST požadavky)
// ------------------------------------------------------------
$zprava = ''; // zpráva o úspěchu
$chyba = ''; // chybová hláška
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.';
}
if (empty($chyba)) {
$akce = $_POST['akce'] ?? '';
// ====================================================
// AKCE: Založení nového uživatele
// ====================================================
if ($akce === 'novy_uzivatel') {
$email = trim($_POST['email'] ?? '');
$heslo = $_POST['heslo'] ?? '';
$heslo2 = $_POST['heslo2'] ?? '';
$admin = isset($_POST['admin']) ? 1 : 0;
// Validace emailu
if (empty($email)) {
$chyba = 'Email nesmí být prázdný.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$chyba = 'Email není platná emailová adresa.';
}
// Validace hesla
if (empty($chyba) && empty($heslo)) {
$chyba = 'Heslo nesmí být prázdné.';
} elseif (empty($chyba) && mb_strlen($heslo) < HESLO_MIN_DELKA) {
$chyba = 'Heslo musí mít alespoň ' . HESLO_MIN_DELKA . ' znaků.';
} elseif (empty($chyba) && $heslo !== $heslo2) {
$chyba = 'Hesla se neshodují.';
}
if (empty($chyba)) {
// Kontrola, zda email již neexistuje
$stmt = $pdo->prepare("
SELECT COUNT(*) AS pocet
FROM `" . DB_TABULKA_UZIVATELE . "`
WHERE `email` = :email
");
$stmt->execute([':email' => $email]);
if ($stmt->fetch()['pocet'] > 0) {
$chyba = 'Uživatel s tímto emailem již existuje.';
}
}
if (empty($chyba)) {
try {
$heslo_hash = password_hash($heslo, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("
INSERT INTO `" . DB_TABULKA_UZIVATELE . "`
(`email`, `heslo`, `admin`)
VALUES
(:email, :heslo, :admin)
");
$stmt->execute([
':email' => $email,
':heslo' => $heslo_hash,
':admin' => $admin,
]);
$novy_id = $pdo->lastInsertId();
// Vytvoříme prázdný řádek v tabulce služby
if (DB_TABULKA_SLUZBA !== '') {
$stmt2 = $pdo->prepare("
INSERT INTO `" . DB_TABULKA_SLUZBA . "`
(`uzivatel_id`)
VALUES
(:uzivatel_id)
");
$stmt2->execute([':uzivatel_id' => $novy_id]);
}
$zprava = 'Uživatel ' . htmlspecialchars($email) . ' byl úspěšně vytvořen.';
} catch (PDOException $e) {
error_log('Admin chyba při vytváření uživatele: ' . $e->getMessage());
$chyba = 'Při vytváření uživatele došlo k chybě databáze.';
}
}
}
// ====================================================
// AKCE: Změna hesla uživatele
// ====================================================
elseif ($akce === 'zmena_hesla') {
$uzivatel_id = (int) ($_POST['uzivatel_id'] ?? 0);
$heslo = $_POST['heslo'] ?? '';
$heslo2 = $_POST['heslo2'] ?? '';
if ($uzivatel_id <= 0) {
$chyba = 'Neplatné ID uživatele.';
} elseif (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 {
$heslo_hash = password_hash($heslo, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("
UPDATE `" . DB_TABULKA_UZIVATELE . "`
SET `heslo` = :heslo
WHERE `id` = :id
");
$stmt->execute([
':heslo' => $heslo_hash,
':id' => $uzivatel_id,
]);
// Po změně hesla smažeme všechny remember me tokeny
// tohoto uživatele odhlásíme ho ze všech zařízení
$stmt2 = $pdo->prepare("
DELETE FROM `" . DB_TABULKA_TOKENY . "`
WHERE `uzivatel_id` = :uzivatel_id
");
$stmt2->execute([':uzivatel_id' => $uzivatel_id]);
$zprava = 'Heslo uživatele bylo úspěšně změněno. Uživatel byl odhlášen ze všech zařízení.';
} catch (PDOException $e) {
error_log('Admin chyba při změně hesla: ' . $e->getMessage());
$chyba = 'Při změně hesla došlo k chybě databáze.';
}
}
}
// ====================================================
// AKCE: Smazání uživatele
// ====================================================
elseif ($akce === 'smazat_uzivatele') {
$uzivatel_id = (int) ($_POST['uzivatel_id'] ?? 0);
if ($uzivatel_id <= 0) {
$chyba = 'Neplatné ID uživatele.';
}
// Admin nesmí smazat sám sebe
if (empty($chyba) && $uzivatel_id === (int) $auth_uzivatel['id']) {
$chyba = 'Nemůžeš smazat svůj vlastní účet.';
}
if (empty($chyba)) {
try {
// Díky FOREIGN KEY s ON DELETE CASCADE se automaticky
// smažou i záznamy v auth_remember_tokens a auth_password_resets
// a v tabulce služby (users)
$stmt = $pdo->prepare("
DELETE FROM `" . DB_TABULKA_UZIVATELE . "`
WHERE `id` = :id
");
$stmt->execute([':id' => $uzivatel_id]);
$zprava = 'Uživatel byl úspěšně smazán.';
} catch (PDOException $e) {
error_log('Admin chyba při mazání uživatele: ' . $e->getMessage());
$chyba = 'Při mazání uživatele došlo k chybě databáze.';
}
}
}
// ====================================================
// AKCE: Testovací email
// ====================================================
elseif ($akce === 'test_email') {
$vysledek = odesli_mail(
MAIL_TEST_ADRESA,
'Testovací email ' . PROJEKT_NAZEV,
"Toto je testovací email ze systému přihlašování.\n\n"
. "Projekt: " . PROJEKT_NAZEV . "\n"
. "URL: " . PROJEKT_URL . "\n"
. "Čas odeslání: " . date('Y-m-d H:i:s') . "\n\n"
. "Pokud tento email vidíš, odesílání emailů funguje správně."
);
if ($vysledek) {
$zprava = 'Testovací email byl odeslán na ' . htmlspecialchars(MAIL_TEST_ADRESA) . '.';
} else {
$chyba = 'Odeslání testovacího emailu selhalo. Zkontroluj PHP error log a nastavení v config.php.';
}
}
}
}
// ------------------------------------------------------------
// NAČTENÍ SEZNAMU UŽIVATELŮ
// ------------------------------------------------------------
$uzivatele = [];
try {
$stmt = $pdo->query("
SELECT `id`, `email`, `admin`, `vytvoreno`
FROM `" . DB_TABULKA_UZIVATELE . "`
ORDER BY `vytvoreno` ASC
");
$uzivatele = $stmt->fetchAll();
} catch (PDOException $e) {
error_log('Admin chyba při načítání uživatelů: ' . $e->getMessage());
$chyba = 'Nepodařilo se načíst seznam uživatelů.';
}
// ------------------------------------------------------------
// HTML VÝSTUP
// ------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<title>Administrace <?php echo htmlspecialchars(PROJEKT_NAZEV); ?></title>
<?php if (AUTH_CSS !== ''): ?>
<link rel="stylesheet" href="<?php echo htmlspecialchars(AUTH_CSS); ?>">
<?php endif; ?>
</head>
<body>
<h1>Administrace</h1>
<h2><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h2>
<p>Přihlášen jako: <strong><?php echo htmlspecialchars($auth_uzivatel['email']); ?></strong></p>
<?php echo $auth_logout_html; ?>
<?php if (!empty($zprava)): ?>
<p><strong>OK: <?php echo $zprava; ?></strong></p>
<?php endif; ?>
<?php if (!empty($chyba)): ?>
<p><strong>Chyba: <?php echo htmlspecialchars($chyba); ?></strong></p>
<?php endif; ?>
<hr>
<!-- ============================================================
SEZNAM UŽIVATELŮ
============================================================ -->
<h3>Seznam uživatelů</h3>
<?php if (empty($uzivatele)): ?>
<p>Žádní uživatelé.</p>
<?php else: ?>
<table border="1" cellpadding="5">
<tr>
<th>ID</th>
<th>Email</th>
<th>Admin</th>
<th>Registrován</th>
<th>Akce</th>
</tr>
<?php foreach ($uzivatele as $u): ?>
<tr>
<td><?php echo htmlspecialchars($u['id']); ?></td>
<td><?php echo htmlspecialchars($u['email']); ?></td>
<td><?php echo $u['admin'] ? 'ANO' : 'ne'; ?></td>
<td><?php echo htmlspecialchars($u['vytvoreno']); ?></td>
<td>
<!-- Formulář pro změnu hesla tohoto uživatele -->
<form method="POST" action="" style="display:inline;">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<input type="hidden" name="akce" value="zmena_hesla">
<input type="hidden" name="uzivatel_id" value="<?php echo htmlspecialchars($u['id']); ?>">
<input
type="password"
name="heslo"
placeholder="nové heslo"
minlength="<?php echo (int) HESLO_MIN_DELKA; ?>"
required
>
<input
type="password"
name="heslo2"
placeholder="nové heslo znovu"
required
>
<button type="submit">Změnit heslo</button>
</form>
&nbsp;
<?php
// Admin nemůže smazat sám sebe skryjeme tlačítko
if ($u['id'] !== $auth_uzivatel['id']):
?>
<!-- Formulář pro smazání tohoto uživatele -->
<form method="POST" action="" style="display:inline;"
onsubmit="return confirm('Opravdu smazat uživatele <?php echo htmlspecialchars(addslashes($u['email'])); ?>? Tato akce je nevratná.');">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<input type="hidden" name="akce" value="smazat_uzivatele">
<input type="hidden" name="uzivatel_id" value="<?php echo htmlspecialchars($u['id']); ?>">
<button type="submit">Smazat</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<hr>
<!-- ============================================================
FORMULÁŘ: Nový uživatel
============================================================ -->
<h3>Přidat nového uživatele</h3>
<form method="POST" action="">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<input type="hidden" name="akce" value="novy_uzivatel">
<p>
<label for="novy-email">Email:</label><br>
<input
type="email"
id="novy-email"
name="email"
required
autocomplete="off"
>
</p>
<p>
<label for="novy-heslo">Heslo:</label><br>
<input
type="password"
id="novy-heslo"
name="heslo"
required
autocomplete="new-password"
>
<br>
<span id="novy-heslo-sila">Síla hesla: zadej heslo</span>
</p>
<p>
<label for="novy-heslo2">Heslo znovu:</label><br>
<input
type="password"
id="novy-heslo2"
name="heslo2"
required
autocomplete="new-password"
>
</p>
<p>
<label>
<input type="checkbox" name="admin" value="1">
Administrátor
</label>
</p>
<p>
<button type="submit" id="novy-tlacitko" disabled>
Vytvořit uživatele
</button>
<span id="novy-tlacitko-duvod"> (čekám na dostatečně silné heslo)</span>
</p>
</form>
<hr>
<!-- ============================================================
KONFIGURACE SYSTÉMU
============================================================ -->
<h3>Konfigurace systému</h3>
<p>Hodnoty jsou načteny z <code>auth/config.php</code>. Citlivé údaje jsou skryté zobrazíš je kliknutím.</p>
<table border="1" cellpadding="5">
<tr>
<th>Konstanta</th>
<th>Hodnota</th>
</tr>
<!-- Databáze -->
<tr><td>DB_HOST</td>
<td><?php echo htmlspecialchars(DB_HOST); ?></td></tr>
<tr><td>DB_NAME</td>
<td><?php echo htmlspecialchars(DB_NAME); ?></td></tr>
<tr><td>DB_USER</td>
<td><?php echo htmlspecialchars(DB_USER); ?></td></tr>
<tr><td>DB_PASS</td>
<td>
<!-- Heslo je skryté, zobrazí se až po kliknutí -->
<span id="db-pass-skryto">[skryto]
<button type="button" onclick="
document.getElementById('db-pass-skryto').style.display='none';
document.getElementById('db-pass-hodnota').style.display='inline';
">Zobrazit</button>
</span>
<span id="db-pass-hodnota" style="display:none;">
<?php echo htmlspecialchars(DB_PASS); ?>
</span>
</td>
</tr>
<tr><td>DB_TABULKA_UZIVATELE</td>
<td><?php echo htmlspecialchars(DB_TABULKA_UZIVATELE); ?></td></tr>
<tr><td>DB_TABULKA_TOKENY</td>
<td><?php echo htmlspecialchars(DB_TABULKA_TOKENY); ?></td></tr>
<tr><td>DB_TABULKA_BRUTE</td>
<td><?php echo htmlspecialchars(DB_TABULKA_BRUTE); ?></td></tr>
<tr><td>DB_TABULKA_RESET</td>
<td><?php echo htmlspecialchars(DB_TABULKA_RESET); ?></td></tr>
<tr><td>DB_TABULKA_SLUZBA</td>
<td><?php echo htmlspecialchars(DB_TABULKA_SLUZBA !== '' ? DB_TABULKA_SLUZBA : '(nevyužito)'); ?></td></tr>
<!-- Projekt -->
<tr><td>PROJEKT_NAZEV</td>
<td><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></td></tr>
<tr><td>PROJEKT_URL</td>
<td><?php echo htmlspecialchars(PROJEKT_URL); ?></td></tr>
<tr><td>AUTH_LOGIN_URL</td>
<td><?php echo htmlspecialchars(AUTH_LOGIN_URL); ?></td></tr>
<tr><td>AUTH_LOGOUT_URL</td>
<td><?php echo htmlspecialchars(AUTH_LOGOUT_URL); ?></td></tr>
<tr><td>AUTH_REDIRECT_PO_PRIHLASENI</td>
<td><?php echo htmlspecialchars(AUTH_REDIRECT_PO_PRIHLASENI); ?></td></tr>
<!-- Registrace a přístup -->
<tr><td>REGISTRACE_OTEVRENA</td>
<td><?php echo REGISTRACE_OTEVRENA ? 'true (otevřená)' : 'false (pouze admin)'; ?></td></tr>
<tr><td>VYZADOVAT_PRIHLASENI</td>
<td><?php echo VYZADOVAT_PRIHLASENI ? 'true (vždy přesměrovat)' : 'false (stránka rozhoduje sama)'; ?></td></tr>
<!-- Session -->
<tr><td>SESSION_NAZEV</td>
<td><?php echo htmlspecialchars(SESSION_NAZEV); ?></td></tr>
<tr><td>SESSION_EXPIRACE</td>
<td><?php echo (int) SESSION_EXPIRACE; ?> sekund
(<?php echo round(SESSION_EXPIRACE / 3600, 1); ?> hodin)</td></tr>
<!-- Remember me -->
<tr><td>REMEMBER_EXPIRACE</td>
<td><?php echo (int) REMEMBER_EXPIRACE; ?> sekund
(<?php echo round(REMEMBER_EXPIRACE / 86400, 1); ?> dní)</td></tr>
<!-- Brute force -->
<tr><td>BRUTE_MAX_POKUSU</td>
<td><?php echo (int) BRUTE_MAX_POKUSU; ?></td></tr>
<tr><td>BRUTE_OKNO</td>
<td><?php echo (int) BRUTE_OKNO; ?> sekund
(<?php echo round(BRUTE_OKNO / 60, 1); ?> minut)</td></tr>
<!-- Heslo -->
<tr><td>HESLO_MIN_SILA</td>
<td><?php echo (int) HESLO_MIN_SILA; ?> / 4</td></tr>
<tr><td>HESLO_MIN_DELKA</td>
<td><?php echo (int) HESLO_MIN_DELKA; ?> znaků</td></tr>
<!-- Email -->
<tr><td>MAIL_ODESILATEL</td>
<td><?php echo htmlspecialchars(MAIL_ODESILATEL); ?></td></tr>
<tr><td>MAIL_ODESILATEL_JMENO</td>
<td><?php echo htmlspecialchars(MAIL_ODESILATEL_JMENO); ?></td></tr>
<tr><td>MAIL_TEST_ADRESA</td>
<td><?php echo htmlspecialchars(MAIL_TEST_ADRESA); ?></td></tr>
<!-- Styl -->
<tr><td>AUTH_CSS</td>
<td><?php echo AUTH_CSS !== '' ? htmlspecialchars(AUTH_CSS) : '(nevyužito)'; ?></td></tr>
</table>
<hr>
<!-- ============================================================
TESTOVACÍ EMAIL
============================================================ -->
<h3>Test odesílání emailů</h3>
<p>Odešle testovací email na adresu <strong><?php echo htmlspecialchars(MAIL_TEST_ADRESA); ?></strong>
(nastaveno v config.php jako MAIL_TEST_ADRESA).</p>
<form method="POST" action="">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<input type="hidden" name="akce" value="test_email">
<button type="submit">Odeslat testovací email</button>
</form>
<hr>
<!-- ============================================================
zxcvbn pro formulář nového uživatele
============================================================ -->
<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('novy-heslo');
const spanSila = document.getElementById('novy-heslo-sila');
const tlacitko = document.getElementById('novy-tlacitko');
const spanDuvod = document.getElementById('novy-tlacitko-duvod');
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>