Files
MSPPPPaM/auth/admin.php
T

446 lines
18 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Í
// ============================================================
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?
// ------------------------------------------------------------
if (!$auth_uzivatel['admin']) {
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Í
// ------------------------------------------------------------
$zprava = '';
$chyba = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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;
if (empty($email)) {
$chyba = 'Email nesmí být prázdný.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$chyba = 'Email není platná emailová adresa.';
} 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)) {
$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();
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,
]);
$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.';
} elseif ($uzivatel_id === (int) $auth_uzivatel['id']) {
$chyba = 'Nemůžeš smazat svůj vlastní účet.';
} elseif ($uzivatel_id === 1) {
// Uživatel s id=1 je první admin vytvořený při instalaci
// jeho smazání by mohlo zanechat systém bez správce
$chyba = 'Prvního uživatele systému nelze smazat.';
}
if (empty($chyba)) {
try {
// Díky FOREIGN KEY s ON DELETE CASCADE se automaticky
// smažou i záznamy v navázaných tabulkách
$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>
<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 -->
<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
// Tlačítko Smazat se nezobrazí pokud:
// - jde o samotného přihlášeného admina
// - jde o prvního uživatele systému (id = 1)
$nelze_smazat = ($u['id'] === $auth_uzivatel['id'])
|| ((int) $u['id'] === 1);
if (!$nelze_smazat):
?>
<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 else: ?>
<!-- Vysvětlení proč nelze smazat -->
<?php if ((int) $u['id'] === 1): ?>
<em>(první uživatel systému nelze smazat)</em>
<?php else: ?>
<em>(vlastní účet nelze smazat)</em>
<?php endif; ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<hr>
<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="heslo">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">Heslo znovu:</label><br>
<input type="password" id="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="tlacitko-odeslat" disabled>
Vytvořit uživatele
</button>
<span id="tlacitko-duvod"> (čekám na dostatečně silné heslo)</span>
</p>
</form>
<hr>
<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>
<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>
<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>
<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_ADMIN_URL</td><td><?php echo htmlspecialchars(AUTH_ADMIN_URL); ?></td></tr>
<tr><td>AUTH_REGISTRACE_URL</td><td><?php echo htmlspecialchars(AUTH_REGISTRACE_URL); ?></td></tr>
<tr><td>AUTH_RESET_URL</td><td><?php echo htmlspecialchars(AUTH_RESET_URL); ?></td></tr>
<tr><td>AUTH_REDIRECT_PO_PRIHLASENI</td><td><?php echo htmlspecialchars(AUTH_REDIRECT_PO_PRIHLASENI); ?></td></tr>
<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>
<tr><td>SESSION_NAZEV</td><td><?php echo htmlspecialchars(SESSION_NAZEV); ?></td></tr>
<tr><td>SESSION_EXPIRACE</td><td><?php echo (int) SESSION_EXPIRACE; ?> s (<?php echo round(SESSION_EXPIRACE / 3600, 1); ?> h)</td></tr>
<tr><td>REMEMBER_EXPIRACE</td><td><?php echo (int) REMEMBER_EXPIRACE; ?> s (<?php echo round(REMEMBER_EXPIRACE / 86400, 1); ?> dní)</td></tr>
<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; ?> s (<?php echo round(BRUTE_OKNO / 60, 1); ?> min)</td></tr>
<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>
<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>
<tr><td>AUTH_CSS</td><td><?php echo AUTH_CSS !== '' ? htmlspecialchars(AUTH_CSS) : '(nevyužito)'; ?></td></tr>
</table>
<hr>
<h3>Test odesílání emailů</h3>
<p>Odešle testovací email na adresu <strong><?php echo htmlspecialchars(MAIL_TEST_ADRESA); ?></strong>.</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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.4.2/zxcvbn.js"></script>
<script>
var AUTH_HESLO_MIN_SILA = <?php echo (int) HESLO_MIN_SILA; ?>;
var AUTH_HESLO_MIN_DELKA = <?php echo (int) HESLO_MIN_DELKA; ?>;
</script>
<script src="<?php echo htmlspecialchars(PROJEKT_URL); ?>/auth/js/heslo-sila.js"></script>
</body>
</html>