Compare commits
16 Commits
1a3b1a369e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e4c10d084 | |||
| cb3e7a2969 | |||
| af6df61128 | |||
| 78be7ec1c2 | |||
| 52bb0208a9 | |||
| f6e5695ef9 | |||
| 876cda8ab4 | |||
| f1c73b41a7 | |||
| 3c9267b1c5 | |||
| 19c92c552d | |||
| cc4cd26a1e | |||
| d4b7b01fe7 | |||
| d26f66f9a8 | |||
| 40289394d2 | |||
| ad0916f25b | |||
| 691ecf6af5 |
+23
-1
@@ -174,10 +174,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$chyba = 'Neplatné ID uživatele.';
|
$chyba = 'Neplatné ID uživatele.';
|
||||||
} elseif ($uzivatel_id === (int) $auth_uzivatel['id']) {
|
} elseif ($uzivatel_id === (int) $auth_uzivatel['id']) {
|
||||||
$chyba = 'Nemůžeš smazat svůj vlastní účet.';
|
$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)) {
|
if (empty($chyba)) {
|
||||||
try {
|
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("
|
$stmt = $pdo->prepare("
|
||||||
DELETE FROM `" . DB_TABULKA_UZIVATELE . "`
|
DELETE FROM `" . DB_TABULKA_UZIVATELE . "`
|
||||||
WHERE `id` = :id
|
WHERE `id` = :id
|
||||||
@@ -284,6 +290,7 @@ try {
|
|||||||
<td><?php echo $u['admin'] ? 'ANO' : 'ne'; ?></td>
|
<td><?php echo $u['admin'] ? 'ANO' : 'ne'; ?></td>
|
||||||
<td><?php echo htmlspecialchars($u['vytvoreno']); ?></td>
|
<td><?php echo htmlspecialchars($u['vytvoreno']); ?></td>
|
||||||
<td>
|
<td>
|
||||||
|
<!-- Formulář pro změnu hesla -->
|
||||||
<form method="POST" action="" style="display:inline;">
|
<form method="POST" action="" style="display:inline;">
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
|
||||||
<input type="hidden" name="akce" value="zmena_hesla">
|
<input type="hidden" name="akce" value="zmena_hesla">
|
||||||
@@ -293,7 +300,14 @@ try {
|
|||||||
<button type="submit">Změnit heslo</button>
|
<button type="submit">Změnit heslo</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<?php if ($u['id'] !== $auth_uzivatel['id']): ?>
|
<?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;"
|
<form method="POST" action="" style="display:inline;"
|
||||||
onsubmit="return confirm('Opravdu smazat uživatele <?php echo htmlspecialchars(addslashes($u['email'])); ?>? Tato akce je nevratná.');">
|
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="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
|
||||||
@@ -301,6 +315,13 @@ try {
|
|||||||
<input type="hidden" name="uzivatel_id" value="<?php echo htmlspecialchars($u['id']); ?>">
|
<input type="hidden" name="uzivatel_id" value="<?php echo htmlspecialchars($u['id']); ?>">
|
||||||
<button type="submit">Smazat</button>
|
<button type="submit">Smazat</button>
|
||||||
</form>
|
</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; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -381,6 +402,7 @@ try {
|
|||||||
<tr><td>PROJEKT_URL</td><td><?php echo htmlspecialchars(PROJEKT_URL); ?></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_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_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_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_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>AUTH_REDIRECT_PO_PRIHLASENI</td><td><?php echo htmlspecialchars(AUTH_REDIRECT_PO_PRIHLASENI); ?></td></tr>
|
||||||
|
|||||||
+326
-228
@@ -1,270 +1,368 @@
|
|||||||
<?php
|
<?php
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// PŘIHLAŠOVACÍ STRÁNKA
|
// HLAVNÍ SOUBOR AUTENTIZACE
|
||||||
|
// ============================================================
|
||||||
|
// Tento soubor vložíš na začátek každé stránky, kterou chceš
|
||||||
|
// chránit přihlášením:
|
||||||
|
//
|
||||||
|
// require_once 'auth/auth.php';
|
||||||
|
//
|
||||||
|
// Po jeho načtení máš k dispozici tyto proměnné:
|
||||||
|
//
|
||||||
|
// $auth_prihlasen ... true / false (vždy nastaveno)
|
||||||
|
// $auth_uzivatel['id'] ... ID přihlášeného uživatele (nebo null)
|
||||||
|
// $auth_uzivatel['email'] ... email přihlášeného uživatele (nebo null)
|
||||||
|
// $auth_uzivatel['admin'] ... true / false (vždy nastaveno)
|
||||||
|
//
|
||||||
|
// $auth_logout_html ... HTML s tlačítkem "Odhlásit se"
|
||||||
|
// a (pro admina) odkazem na admin rozhraní.
|
||||||
|
// Neprázdný pouze pokud je uživatel přihlášen.
|
||||||
|
// Použití: echo $auth_logout_html;
|
||||||
|
//
|
||||||
|
// $auth_login_html ... HTML formulář pro přihlášení (email, heslo,
|
||||||
|
// zapamatovat si mě, CSRF token).
|
||||||
|
// Neprázdný pouze pokud uživatel NENÍ přihlášen.
|
||||||
|
// Data odesílá na AUTH_LOGIN_URL (login.php),
|
||||||
|
// které po přihlášení přesměruje zpět na
|
||||||
|
// aktuální stránku.
|
||||||
|
// Použití: echo $auth_login_html;
|
||||||
|
//
|
||||||
|
// Typické použití – stránka vyžadující přihlášení
|
||||||
|
// (VYZADOVAT_PRIHLASENI = true v config.php):
|
||||||
|
//
|
||||||
|
// require_once 'auth/auth.php';
|
||||||
|
// // Sem se dostane jen přihlášený uživatel.
|
||||||
|
// // Přihlášený email: $auth_uzivatel['email']
|
||||||
|
// // Odhlašovací tlačítko: echo $auth_logout_html;
|
||||||
|
//
|
||||||
|
// Typické použití – stránka s omezeným přístupem
|
||||||
|
// (VYZADOVAT_PRIHLASENI = false v config.php):
|
||||||
|
//
|
||||||
|
// require_once 'auth/auth.php';
|
||||||
|
// // if ($auth_prihlasen): zobraz přihlášený obsah + echo $auth_logout_html;
|
||||||
|
// // else: zobraz veřejný obsah + echo $auth_login_html;
|
||||||
|
//
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 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__ . '/config.php';
|
||||||
require_once __DIR__ . '/db.php';
|
require_once __DIR__ . '/db.php';
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// SPUŠTĚNÍ SESSION
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
// Nastavíme parametry session cookie ještě PŘED session_start().
|
||||||
|
// HttpOnly = JavaScript nemůže cookie číst (ochrana před XSS)
|
||||||
|
// SameSite = cookie se neposílá při požadavcích z jiných webů
|
||||||
|
// (ochrana před CSRF)
|
||||||
|
// Secure = cookie se posílá jen přes HTTPS (pokud web běží na HTTPS)
|
||||||
session_name(SESSION_NAZEV);
|
session_name(SESSION_NAZEV);
|
||||||
session_set_cookie_params([
|
session_set_cookie_params([
|
||||||
'lifetime' => 0,
|
'lifetime' => 0, // 0 = cookie platí do zavření prohlížeče
|
||||||
|
// (trvalost zajišťuje remember me, ne session)
|
||||||
'path' => '/',
|
'path' => '/',
|
||||||
'httponly' => true,
|
'httponly' => true, // JavaScript k cookie nemá přístup
|
||||||
'samesite' => 'Strict',
|
'samesite' => 'Strict', // ochrana před CSRF
|
||||||
// 'secure' => true,
|
// 'secure' => true, // odkomentuj pokud web běží na HTTPS
|
||||||
]);
|
]);
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
// Pokud je uživatel již přihlášen, přesměrujeme ho rovnou dál
|
// ------------------------------------------------------------
|
||||||
if (isset($_SESSION['uzivatel_id'])) {
|
// VÝCHOZÍ STAV – uživatel není přihlášen
|
||||||
header('Location: ' . AUTH_REDIRECT_PO_PRIHLASENI);
|
// ------------------------------------------------------------
|
||||||
|
// Tyto hodnoty jsou nastaveny vždy – při každém načtení stránky.
|
||||||
|
// Teprve níže se případně přepíší na true / skutečné hodnoty.
|
||||||
|
|
||||||
|
$auth_prihlasen = false;
|
||||||
|
$auth_uzivatel = [
|
||||||
|
'id' => null,
|
||||||
|
'email' => null,
|
||||||
|
'admin' => false,
|
||||||
|
];
|
||||||
|
$auth_logout_html = '';
|
||||||
|
$auth_login_html = '';
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// KROK 1: Existuje platná session?
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Session je nejrychlejší způsob ověření – nevyžaduje dotaz do DB.
|
||||||
|
// Session data jsou uložena na serveru, uživatel je nemůže zfalšovat.
|
||||||
|
|
||||||
|
if (isset($_SESSION['uzivatel_id']) && isset($_SESSION['email'])) {
|
||||||
|
|
||||||
|
// Kontrola expirace session (nečinnost)
|
||||||
|
if (isset($_SESSION['posledni_aktivita']) &&
|
||||||
|
(time() - $_SESSION['posledni_aktivita']) > SESSION_EXPIRACE) {
|
||||||
|
|
||||||
|
// Session vypršela – zrušíme ji.
|
||||||
|
// DŮLEŽITÉ: Po session_destroy() není žádná aktivní session.
|
||||||
|
// Krok 2 (remember me) to musí zohlednit – viz níže.
|
||||||
|
$_SESSION = [];
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Session je platná – uživatel je přihlášen
|
||||||
|
$auth_prihlasen = true;
|
||||||
|
$auth_uzivatel = [
|
||||||
|
'id' => $_SESSION['uzivatel_id'],
|
||||||
|
'email' => $_SESSION['email'],
|
||||||
|
// Přetypování na bool – ochrana pro případ, že by v session
|
||||||
|
// byla jiná hodnota než true/false
|
||||||
|
'admin' => (bool) $_SESSION['admin'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Aktualizujeme čas poslední aktivity
|
||||||
|
$_SESSION['posledni_aktivita'] = time();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// KROK 2: Session neexistuje – zkusíme remember me cookie
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Cookie obsahuje pouze selector a token. Nikdy neobsahuje
|
||||||
|
// heslo, ID uživatele ani příznak admin. Vše citlivé je na serveru.
|
||||||
|
|
||||||
|
if (!$auth_prihlasen &&
|
||||||
|
isset($_COOKIE['auth_selector']) &&
|
||||||
|
isset($_COOKIE['auth_token'])) {
|
||||||
|
|
||||||
|
$cookie_selector = $_COOKIE['auth_selector'];
|
||||||
|
$cookie_token = $_COOKIE['auth_token'];
|
||||||
|
|
||||||
|
// Vyhledáme záznam v DB podle selectoru (přesná shoda, rychlé)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
t.id AS token_id,
|
||||||
|
t.uzivatel_id,
|
||||||
|
t.token_hash,
|
||||||
|
t.expiruje,
|
||||||
|
u.email,
|
||||||
|
u.admin
|
||||||
|
FROM `" . DB_TABULKA_TOKENY . "` t
|
||||||
|
JOIN `" . DB_TABULKA_UZIVATELE . "` u ON u.id = t.uzivatel_id
|
||||||
|
WHERE t.selector = :selector
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([':selector' => $cookie_selector]);
|
||||||
|
$zaznam = $stmt->fetch();
|
||||||
|
|
||||||
|
$cookie_ok = false;
|
||||||
|
|
||||||
|
if ($zaznam) {
|
||||||
|
|
||||||
|
// Zkontrolujeme, zda token ještě nevypršel
|
||||||
|
if (time() < strtotime($zaznam['expiruje'])) {
|
||||||
|
|
||||||
|
// Ověříme tajný token pomocí password_verify().
|
||||||
|
// Tato funkce záměrně trvá stejně dlouho bez ohledu
|
||||||
|
// na výsledek – chrání před timing útoky.
|
||||||
|
if (password_verify($cookie_token, $zaznam['token_hash'])) {
|
||||||
|
$cookie_ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cookie_ok) {
|
||||||
|
|
||||||
|
// Cookie je platná – obnovíme session.
|
||||||
|
//
|
||||||
|
// OPRAVA CHYBY: Pokud v kroku 1 vypršela session a byla zničena
|
||||||
|
// přes session_destroy(), není nyní žádná aktivní session.
|
||||||
|
// session_regenerate_id() by selhalo s warningem "no active session",
|
||||||
|
// ten warning by odeslal výstup, a tím by znemožnil setcookie()
|
||||||
|
// a header() níže.
|
||||||
|
//
|
||||||
|
// Řešení: pokud session není aktivní, spustíme ji znovu
|
||||||
|
// před voláním session_regenerate_id().
|
||||||
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regenerujeme session ID (ochrana před session fixation)
|
||||||
|
session_regenerate_id(true);
|
||||||
|
|
||||||
|
$_SESSION['uzivatel_id'] = $zaznam['uzivatel_id'];
|
||||||
|
$_SESSION['email'] = $zaznam['email'];
|
||||||
|
$_SESSION['admin'] = (bool) $zaznam['admin'];
|
||||||
|
$_SESSION['posledni_aktivita'] = time();
|
||||||
|
|
||||||
|
// Vygenerujeme CSRF token pokud ještě neexistuje
|
||||||
|
if (empty($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth_prihlasen = true;
|
||||||
|
$auth_uzivatel = [
|
||||||
|
'id' => $zaznam['uzivatel_id'],
|
||||||
|
'email' => $zaznam['email'],
|
||||||
|
'admin' => (bool) $zaznam['admin'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Prodloužíme platnost tokenu v DB i v cookie
|
||||||
|
$nova_expirace = time() + REMEMBER_EXPIRACE;
|
||||||
|
$nova_expirace_dt = date('Y-m-d H:i:s', $nova_expirace);
|
||||||
|
|
||||||
|
$stmt2 = $pdo->prepare("
|
||||||
|
UPDATE `" . DB_TABULKA_TOKENY . "`
|
||||||
|
SET `expiruje` = :expiruje
|
||||||
|
WHERE `id` = :id
|
||||||
|
");
|
||||||
|
$stmt2->execute([
|
||||||
|
':expiruje' => $nova_expirace_dt,
|
||||||
|
':id' => $zaznam['token_id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Prodloužíme cookie v prohlížeči
|
||||||
|
setcookie('auth_selector', $cookie_selector, [
|
||||||
|
'expires' => $nova_expirace,
|
||||||
|
'path' => '/',
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict',
|
||||||
|
// 'secure' => true,
|
||||||
|
]);
|
||||||
|
setcookie('auth_token', $cookie_token, [
|
||||||
|
'expires' => $nova_expirace,
|
||||||
|
'path' => '/',
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict',
|
||||||
|
// 'secure' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Smažeme staré (expirované) tokeny z DB příležitostně
|
||||||
|
$stmt3 = $pdo->prepare("
|
||||||
|
DELETE FROM `" . DB_TABULKA_TOKENY . "`
|
||||||
|
WHERE `expiruje` < :ted
|
||||||
|
");
|
||||||
|
$stmt3->execute([':ted' => date('Y-m-d H:i:s')]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Cookie je neplatná nebo expirovaná – smažeme ji
|
||||||
|
if ($zaznam) {
|
||||||
|
$stmt4 = $pdo->prepare("
|
||||||
|
DELETE FROM `" . DB_TABULKA_TOKENY . "`
|
||||||
|
WHERE `id` = :id
|
||||||
|
");
|
||||||
|
$stmt4->execute([':id' => $zaznam['token_id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setcookie('auth_selector', '', [
|
||||||
|
'expires' => time() - 3600,
|
||||||
|
'path' => '/',
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict',
|
||||||
|
]);
|
||||||
|
setcookie('auth_token', '', [
|
||||||
|
'expires' => time() - 3600,
|
||||||
|
'path' => '/',
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// KROK 3: Rozhodnutí – přesměrovat nebo pokračovat?
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
if (!$auth_prihlasen && VYZADOVAT_PRIHLASENI) {
|
||||||
|
|
||||||
|
// Uložíme URL aktuální stránky pro přesměrování po přihlášení
|
||||||
|
$aktualni_url = $_SERVER['REQUEST_URI'] ?? '';
|
||||||
|
if (!empty($aktualni_url)) {
|
||||||
|
$_SESSION['redirect_po_prihlaseni'] = $aktualni_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: ' . AUTH_LOGIN_URL);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// CSRF TOKEN
|
// CSRF TOKEN – zajistíme, že vždy existuje
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
if (empty($_SESSION['csrf_token'])) {
|
if (empty($_SESSION['csrf_token'])) {
|
||||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
$csrf_token = $_SESSION['csrf_token'];
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// ZPRACOVÁNÍ FORMULÁŘE
|
// PŘIPRAVENÉ HTML PROMĚNNÉ
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
$chyba = '';
|
// -- Odhlašovací formulář ------------------------------------
|
||||||
$email_hodnota = '';
|
// Neprázdný pouze pokud je uživatel přihlášen.
|
||||||
|
// Pro admina obsahuje navíc odkaz na administrační rozhraní.
|
||||||
|
// Použití na stránce: echo $auth_logout_html;
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($auth_prihlasen) {
|
||||||
|
|
||||||
// -- Ověření CSRF tokenu ----------------------------------
|
$auth_logout_html = '';
|
||||||
$csrf_z_formulare = $_POST['csrf_token'] ?? '';
|
|
||||||
if (!hash_equals($csrf_token, $csrf_z_formulare)) {
|
// Odkaz na admin rozhraní – pouze pro admina
|
||||||
$chyba = 'Neplatný požadavek. Zkuste stránku obnovit a přihlásit se znovu.';
|
if ($auth_uzivatel['admin']) {
|
||||||
|
$auth_logout_html .=
|
||||||
|
'<a href="' . htmlspecialchars(AUTH_ADMIN_URL) . '">Administrace</a> | ';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($chyba)) {
|
// Přihlášený uživatel a tlačítko odhlášení
|
||||||
|
$auth_logout_html .=
|
||||||
$email = trim($_POST['email'] ?? '');
|
htmlspecialchars($auth_uzivatel['email'])
|
||||||
$heslo = $_POST['heslo'] ?? '';
|
. ' | '
|
||||||
$zapamatovat = isset($_POST['zapamatovat']);
|
. '<form method="POST" action="' . htmlspecialchars(AUTH_LOGOUT_URL) . '"'
|
||||||
|
. ' style="display:inline;">'
|
||||||
$email_hodnota = htmlspecialchars($email);
|
. '<input type="hidden" name="csrf_token" value="'
|
||||||
|
. htmlspecialchars($_SESSION['csrf_token']) . '">'
|
||||||
if (empty($email) || empty($heslo)) {
|
. '<button type="submit">Odhlásit se</button>'
|
||||||
$chyba = 'Zadejte email a heslo.';
|
. '</form>';
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// -- Přihlašovací formulář -----------------------------------
|
||||||
// HTML VÝSTUP
|
// Neprázdný pouze pokud uživatel NENÍ přihlášen.
|
||||||
// ------------------------------------------------------------
|
// Odesílá data na login.php, které zpracuje přihlášení
|
||||||
?>
|
// a přesměruje uživatele zpět na původní stránku.
|
||||||
<!DOCTYPE html>
|
// Použití na stránce: echo $auth_login_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>
|
if (!$auth_prihlasen) {
|
||||||
<h2><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h2>
|
|
||||||
|
|
||||||
<?php if (!empty($chyba)): ?>
|
// Aktuální URL předáme login.php jako parametr,
|
||||||
<p><strong>Chyba: <?php echo htmlspecialchars($chyba); ?></strong></p>
|
// aby nás po úspěšném přihlášení přesměroval zpět sem
|
||||||
<?php endif; ?>
|
$aktualni_url = $_SERVER['REQUEST_URI'] ?? '';
|
||||||
|
|
||||||
<form method="POST" action="">
|
$auth_login_html =
|
||||||
|
'<form method="POST" action="' . htmlspecialchars(AUTH_LOGIN_URL) . '">'
|
||||||
|
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
|
// CSRF token – ochrana před podvrženými požadavky
|
||||||
|
. '<input type="hidden" name="csrf_token" value="'
|
||||||
|
. htmlspecialchars($_SESSION['csrf_token']) . '">'
|
||||||
|
|
||||||
<p>
|
// Aktuální URL – login.php nás po přihlášení přesměruje zpět
|
||||||
<label for="email">Email:</label><br>
|
. '<input type="hidden" name="redirect_url" value="'
|
||||||
<input
|
. htmlspecialchars($aktualni_url) . '">'
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
value="<?php echo $email_hodnota; ?>"
|
|
||||||
required
|
|
||||||
autocomplete="email"
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
. '<p>'
|
||||||
<label for="heslo">Heslo:</label><br>
|
. '<label for="auth-email">Email:</label><br>'
|
||||||
<input
|
. '<input type="email" id="auth-email" name="email"'
|
||||||
type="password"
|
. ' required autocomplete="email">'
|
||||||
id="heslo"
|
. '</p>'
|
||||||
name="heslo"
|
|
||||||
required
|
|
||||||
autocomplete="current-password"
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
. '<p>'
|
||||||
<label>
|
. '<label for="auth-heslo">Heslo:</label><br>'
|
||||||
<input type="checkbox" name="zapamatovat" value="1">
|
. '<input type="password" id="auth-heslo" name="heslo"'
|
||||||
Zapamatovat si mě
|
. ' required autocomplete="current-password">'
|
||||||
</label>
|
. '</p>'
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
. '<p>'
|
||||||
<button type="submit">Přihlásit se</button>
|
. '<label>'
|
||||||
</p>
|
. '<input type="checkbox" name="zapamatovat" value="1">'
|
||||||
|
. ' Zapamatovat si mě'
|
||||||
|
. '</label>'
|
||||||
|
. '</p>'
|
||||||
|
|
||||||
</form>
|
. '<p>'
|
||||||
|
. '<button type="submit">Přihlásit se</button>'
|
||||||
|
. '</p>'
|
||||||
|
|
||||||
<?php if (REGISTRACE_OTEVRENA): ?>
|
. '</form>';
|
||||||
<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>
|
|
||||||
+5
-4
@@ -41,7 +41,7 @@ define('DB_TABULKA_SLUZBA', 'users');
|
|||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
// Název projektu – zobrazuje se na přihlašovací stránce apod.
|
// Název projektu – zobrazuje se na přihlašovací stránce apod.
|
||||||
define('PROJEKT_NAZEV', 'auth demo hoho');
|
define('PROJEKT_NAZEV', 'auth demo hoho ho');
|
||||||
|
|
||||||
// Základní URL projektu (bez lomítka na konci)
|
// Základní URL projektu (bez lomítka na konci)
|
||||||
// Používá se např. v odkazech v emailech
|
// Používá se např. v odkazech v emailech
|
||||||
@@ -50,6 +50,7 @@ define('PROJEKT_URL', 'https://authdemo.svach.eu');
|
|||||||
// Cesty ke stránkám auth systému (relativní od kořene webu)
|
// Cesty ke stránkám auth systému (relativní od kořene webu)
|
||||||
define('AUTH_LOGIN_URL', '/auth/login.php');
|
define('AUTH_LOGIN_URL', '/auth/login.php');
|
||||||
define('AUTH_LOGOUT_URL', '/auth/logout.php');
|
define('AUTH_LOGOUT_URL', '/auth/logout.php');
|
||||||
|
define('AUTH_ADMIN_URL', '/auth/admin.php');
|
||||||
define('AUTH_REGISTRACE_URL', '/auth/registrace.php');
|
define('AUTH_REGISTRACE_URL', '/auth/registrace.php');
|
||||||
define('AUTH_RESET_URL', '/auth/reset_hesla.php');
|
define('AUTH_RESET_URL', '/auth/reset_hesla.php');
|
||||||
define('AUTH_REDIRECT_PO_PRIHLASENI', '/index.php');
|
define('AUTH_REDIRECT_PO_PRIHLASENI', '/index.php');
|
||||||
@@ -73,7 +74,7 @@ define('REGISTRACE_OTEVRENA', true);
|
|||||||
// false = nepřihlášený uživatel obsah VIDÍ (auth.php jen nastaví
|
// false = nepřihlášený uživatel obsah VIDÍ (auth.php jen nastaví
|
||||||
// proměnné $auth_prihlasen a $auth_uzivatel),
|
// proměnné $auth_prihlasen a $auth_uzivatel),
|
||||||
// stránka sama rozhodne, co mu ukáže
|
// stránka sama rozhodne, co mu ukáže
|
||||||
define('VYZADOVAT_PRIHLASENI', true);
|
define('VYZADOVAT_PRIHLASENI', false);
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
@@ -130,7 +131,7 @@ define('HESLO_MIN_DELKA', 8);
|
|||||||
define('MAIL_ODESILATEL', 'admin@svach.eu');
|
define('MAIL_ODESILATEL', 'admin@svach.eu');
|
||||||
|
|
||||||
// Jméno odesílatele (zobrazí se v emailovém klientu)
|
// Jméno odesílatele (zobrazí se v emailovém klientu)
|
||||||
define('MAIL_ODESILATEL_JMENO', 'authdemátor');
|
define('MAIL_ODESILATEL_JMENO', 'Název mého projektu');
|
||||||
|
|
||||||
// Testovací emailová adresa (pro tlačítko "test emailu" v admin.php)
|
// Testovací emailová adresa (pro tlačítko "test emailu" v admin.php)
|
||||||
define('MAIL_TEST_ADRESA', 'admin@svach.eu');
|
define('MAIL_TEST_ADRESA', 'admin@svach.eu');
|
||||||
@@ -143,4 +144,4 @@ define('MAIL_TEST_ADRESA', 'admin@svach.eu');
|
|||||||
// Cesta k vlastnímu CSS souboru pro stránky auth systému.
|
// Cesta k vlastnímu CSS souboru pro stránky auth systému.
|
||||||
// Pokud nechceš vlastní styl, nastav na prázdný řetězec: ''
|
// Pokud nechceš vlastní styl, nastav na prázdný řetězec: ''
|
||||||
// Příklad: '/css/auth-styl.css'
|
// Příklad: '/css/auth-styl.css'
|
||||||
define('AUTH_CSS', '');
|
define('AUTH_CSS', '/css/auth-styl.css');
|
||||||
@@ -0,0 +1,641 @@
|
|||||||
|
/* ============================================================
|
||||||
|
AUTH SYSTÉM – STYL
|
||||||
|
============================================================
|
||||||
|
Umístění: libovolné (např. /css/auth-styl.css)
|
||||||
|
Aktivace: v auth/config.php nastavit
|
||||||
|
define('AUTH_CSS', '/css/auth-styl.css');
|
||||||
|
|
||||||
|
Funguje bez jakýchkoliv změn v PHP souborech.
|
||||||
|
Styluje čistě pomocí HTML elementů – žádné třídy nejsou
|
||||||
|
potřeba.
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
GOOGLE FONTS
|
||||||
|
Montserrat 300/500 – tři velikosti (viz níže)
|
||||||
|
IBM Plex Serif 200 – jedna velikost pro veškerý text
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;500&family=IBM+Plex+Serif:ital,wght@0,200;1,200&display=swap');
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
TYPOGRAFICKÁ STUPNICE MONTSERRAT
|
||||||
|
============================================================
|
||||||
|
Velikost A – 2.6rem ... hlavní nadpis stránky (h1), jednou, weight 300
|
||||||
|
Velikost B – 1.05rem ... nadpisy sekcí (h2, h3), weight 300
|
||||||
|
Velikost C – 0.78rem ... nejmenší (th weight 500, button/label weight 300)
|
||||||
|
= shodná s IBM Plex Serif optickou velikostí
|
||||||
|
IBM Plex Serif 200 ... 0.875rem pro veškerý ostatní text
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
PROMĚNNÉ
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
:root {
|
||||||
|
/* Pozadí */
|
||||||
|
--bg-hlavni: #141414; /* tělo stránky = barva mezer mezi bloky */
|
||||||
|
--bg-obsah: #222222; /* světlejší bloky obsahu */
|
||||||
|
--bg-prvek: #2a2a2a; /* inputy, tabulky, textarea */
|
||||||
|
--bg-prvek-focus: #313131; /* input při focusu */
|
||||||
|
|
||||||
|
/* Písmo */
|
||||||
|
--text-hlavni: #d0d0d0;
|
||||||
|
--text-jemny: #7a7a7a;
|
||||||
|
--text-nadpis: #eeeeee;
|
||||||
|
|
||||||
|
/* Akcent – tmavě červená */
|
||||||
|
--akcent: #5c1010; /* pozadí tlačítek, th */
|
||||||
|
--akcent-hover: #7a1515; /* hover tlačítek */
|
||||||
|
--akcent-svetly: #c0392b; /* text, h1, h3, odkazy */
|
||||||
|
--akcent-okraj: #4a0d0d; /* okraje akcentových prvků */
|
||||||
|
|
||||||
|
/* Ocelová šedá */
|
||||||
|
--ocel: #3a4455; /* okraje inputů, tabulek */
|
||||||
|
--ocel-svetly: #6a7a90; /* labely, h2, pomocný text */
|
||||||
|
|
||||||
|
/* Stavy */
|
||||||
|
--chyba-text: #f08080;
|
||||||
|
--uspech-text: #68d391;
|
||||||
|
|
||||||
|
/* Rozměry */
|
||||||
|
--pruh-max: 620px;
|
||||||
|
--sekce-mezera: 1.6rem; /* výška tmavé mezery mezi bloky */
|
||||||
|
--sekce-padding: 1.4rem; /* vnitřní odsazení bloků */
|
||||||
|
--polomer-blok: 6px; /* zaoblení rohů bloků */
|
||||||
|
--polomer-prvek: 4px; /* zaoblení inputů, tlačítek */
|
||||||
|
|
||||||
|
/* Stíny */
|
||||||
|
--stin-blok: 0 2px 12px rgba(0, 0, 0, 0.45);
|
||||||
|
--stin-input: inset 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
--stin-input-focus: 0 0 0 2px rgba(192, 57, 43, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
ZÁKLAD
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-hlavni);
|
||||||
|
color: var(--text-hlavni);
|
||||||
|
font-family: 'IBM Plex Serif', Georgia, serif;
|
||||||
|
font-size: 0.875rem; /* IBM Plex Serif – trochu větší než dřív */
|
||||||
|
font-weight: 200;
|
||||||
|
line-height: 1.75;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
animation: fadein 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
OBSAHOVÝ PRUH
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
body > * {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--pruh-max);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
BLOKY OBSAHU – světlejší pozadí, zaoblení, stín
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
body > h1,
|
||||||
|
body > h2,
|
||||||
|
body > h3,
|
||||||
|
body > p,
|
||||||
|
body > form,
|
||||||
|
body > table,
|
||||||
|
body > section,
|
||||||
|
body > ol,
|
||||||
|
body > ul,
|
||||||
|
body > textarea {
|
||||||
|
background-color: var(--bg-obsah);
|
||||||
|
padding-left: var(--sekce-padding);
|
||||||
|
padding-right: var(--sekce-padding);
|
||||||
|
padding-top: var(--sekce-padding);
|
||||||
|
padding-bottom: var(--sekce-padding);
|
||||||
|
border-radius: var(--polomer-blok);
|
||||||
|
box-shadow: var(--stin-blok);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabulka – padding jen nahoře/dole, šířku řídí buňky */
|
||||||
|
body > table {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
overflow: hidden; /* zaoblení se projeví i na th/td */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mezery – každý blok dostane mezeru nahoře */
|
||||||
|
body > h1,
|
||||||
|
body > h2,
|
||||||
|
body > h3,
|
||||||
|
body > p,
|
||||||
|
body > form,
|
||||||
|
body > table,
|
||||||
|
body > section,
|
||||||
|
body > ol,
|
||||||
|
body > ul,
|
||||||
|
body > textarea {
|
||||||
|
margin-top: var(--sekce-mezera);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hr = čistá tmavá mezera, žádná čára */
|
||||||
|
body > hr {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
height: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* h2 těsně za h1 – stejný blok, bez mezery nahoře */
|
||||||
|
body > h1 + h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* h1 když za ním následuje h2 – zaoblení jen nahoře, bez čáry dole */
|
||||||
|
body > h1:has(+ h2) {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
padding-bottom: 0.3rem;
|
||||||
|
box-shadow: var(--stin-blok); /* stejný stín jako ostatní bloky, bez čáry */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* h3 blok má o trochu světlejší pozadí než ostatní bloky */
|
||||||
|
body > h3 {
|
||||||
|
background-color: #292929;
|
||||||
|
}
|
||||||
|
body > *:last-child {
|
||||||
|
margin-bottom: var(--sekce-mezera);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Elementy uvnitř formuláře a sekcí – bez extra pozadí a stínu */
|
||||||
|
form > *,
|
||||||
|
section > * {
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
TYPOGRAFIE – MONTSERRAT
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/* === VELIKOST A: hlavní nadpis stránky, jednou === */
|
||||||
|
h1 {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--akcent-svetly);
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === VELIKOST B: nadpisy sekcí === */
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #9ab0c8; /* světlejší modrošedá – čitelná na tmavém i světlém */
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: var(--akcent-svetly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === VELIKOST C: th, button, label, indikátory ===
|
||||||
|
(shodná s IBM Plex Serif = 0.78rem, aby text vypadal stejně velký) */
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.875rem; /* IBM Plex Serif velikost */
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--text-nadpis);
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
p > strong:first-child {
|
||||||
|
color: var(--chyba-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
color: var(--text-jemny);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
ODKAZY
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
a {
|
||||||
|
color: var(--akcent-svetly);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid var(--akcent-okraj);
|
||||||
|
transition: color 0.15s, border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover,
|
||||||
|
a:focus {
|
||||||
|
color: #e74c3c;
|
||||||
|
border-bottom-color: #e74c3c;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
FORMULÁŘE
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--pruh-max);
|
||||||
|
}
|
||||||
|
|
||||||
|
form p {
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.9rem;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === VELIKOST C: label === */
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ocel-svetly);
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
line-height: 1; /* pevná výška řádku – zabrání dědění 1.75 z body */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline label pro checkbox – jiný styl */
|
||||||
|
label:has(input[type="checkbox"]) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-family: 'IBM Plex Serif', Georgia, serif;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
color: var(--text-hlavni);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
INPUTY
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"] {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bg-prvek);
|
||||||
|
color: var(--text-hlavni);
|
||||||
|
border: 1px solid var(--ocel);
|
||||||
|
border-radius: var(--polomer-prvek);
|
||||||
|
padding: 0.45rem 0.7rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: 'IBM Plex Serif', Georgia, serif;
|
||||||
|
font-weight: 200;
|
||||||
|
box-shadow: var(--stin-input);
|
||||||
|
transition: border-color 0.15s, background-color 0.15s, box-shadow 0.15s;
|
||||||
|
outline: none;
|
||||||
|
margin-top: 0.15rem; /* minimální optický odstup od labelu */
|
||||||
|
margin-bottom: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus,
|
||||||
|
input[type="email"]:focus,
|
||||||
|
input[type="password"]:focus {
|
||||||
|
background-color: var(--bg-prvek-focus);
|
||||||
|
border-color: var(--akcent-svetly);
|
||||||
|
box-shadow: var(--stin-input), var(--stin-input-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]::placeholder,
|
||||||
|
input[type="email"]::placeholder,
|
||||||
|
input[type="password"]::placeholder {
|
||||||
|
color: var(--ocel-svetly);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline inputy v tabulce admin.php */
|
||||||
|
table input[type="password"] {
|
||||||
|
width: auto;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 0.85rem;
|
||||||
|
height: 0.85rem;
|
||||||
|
accent-color: var(--akcent-svetly);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
TEXTAREA
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
textarea {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bg-prvek);
|
||||||
|
color: #98c898; /* terminálová zelená pro SQL */
|
||||||
|
border: 1px solid var(--ocel);
|
||||||
|
border-radius: var(--polomer-prvek);
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: var(--stin-input);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus {
|
||||||
|
border-color: var(--akcent-svetly);
|
||||||
|
box-shadow: var(--stin-input), var(--stin-input-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
TLAČÍTKA – VELIKOST C (0.78rem Montserrat)
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
button[type="submit"],
|
||||||
|
button[type="button"] {
|
||||||
|
background-color: var(--akcent);
|
||||||
|
color: #e8e8e8;
|
||||||
|
border: 1px solid var(--akcent-okraj);
|
||||||
|
border-radius: var(--polomer-prvek);
|
||||||
|
padding: 0.48rem 1.1rem;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"]:hover,
|
||||||
|
button[type="button"]:hover {
|
||||||
|
background-color: var(--akcent-hover);
|
||||||
|
border-color: var(--akcent-hover);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"]:focus,
|
||||||
|
button[type="button"]:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(192, 57, 43, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
color: var(--text-jemny);
|
||||||
|
border-color: #2a2a2a;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled:hover {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
border-color: #2a2a2a;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline formuláře – odhlášení, smazání v tabulce */
|
||||||
|
form[style*="display:inline"],
|
||||||
|
form[style*="display: inline"] {
|
||||||
|
display: inline;
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form[style*="display:inline"] button,
|
||||||
|
form[style*="display: inline"] button {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0.2rem 0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
min-width: 7rem; /* stejná šířka pro Smazat i Změnit heslo */
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
INDIKÁTOR SÍLY HESLA – VELIKOST C
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
#heslo-sila,
|
||||||
|
#novy-heslo-sila {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--text-jemny);
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tlacitko-duvod,
|
||||||
|
#novy-tlacitko-duvod {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--text-jemny);
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
TABULKY
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === VELIKOST C: záhlaví tabulky – weight 500 pro lepší čitelnost === */
|
||||||
|
table th {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: var(--akcent);
|
||||||
|
color: #e8e8e8;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px solid var(--akcent-okraj);
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
background-color: var(--bg-prvek);
|
||||||
|
color: var(--text-hlavni);
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
border: 1px solid var(--ocel);
|
||||||
|
vertical-align: middle;
|
||||||
|
font-family: 'IBM Plex Serif', Georgia, serif;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(even) td {
|
||||||
|
background-color: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:hover td {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
transition: background-color 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td em {
|
||||||
|
color: var(--text-jemny);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
KÓDOVÉ ELEMENTY
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
code {
|
||||||
|
background-color: var(--bg-prvek);
|
||||||
|
color: #98c898;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 0.1em 0.35em;
|
||||||
|
border-radius: var(--polomer-prvek);
|
||||||
|
border: 1px solid var(--ocel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
SEZNAMY
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
ol, ul {
|
||||||
|
padding-left: 1.4rem;
|
||||||
|
font-family: 'IBM Plex Serif', Georgia, serif;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol li, ul li {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
RESPONSIVNÍ DESIGN
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
:root {
|
||||||
|
--pruh-max: 100%;
|
||||||
|
--sekce-padding: 1rem;
|
||||||
|
--sekce-mezera: 1rem;
|
||||||
|
--polomer-blok: 0px; /* na mobilu bez zaoblení – bloky jdou od kraje */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td form[style*="display:inline"],
|
||||||
|
table td form[style*="display: inline"] {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td form[style*="display:inline"] button,
|
||||||
|
table td form[style*="display: inline"] button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* iOS: font-size ≥ 16px zabrání zoomu při focusu */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"] {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table input[type="password"] {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"],
|
||||||
|
button[type="button"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------
|
||||||
|
PŘECHOD PŘI NAČTENÍ
|
||||||
|
------------------------------------------------------------ */
|
||||||
|
@keyframes fadein {
|
||||||
|
from { opacity: 0; transform: translateY(4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
@@ -1,21 +1,175 @@
|
|||||||
<?php require_once 'auth/auth.php'; ?>
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// UKÁZKOVÁ STRÁNKA S CHRÁNĚNÝM OBSAHEM
|
||||||
|
// ============================================================
|
||||||
|
// Demonstruje použití auth systému s VYZADOVAT_PRIHLASENI = false.
|
||||||
|
// Stránka je dostupná všem, ale zobrazuje různý obsah podle
|
||||||
|
// toho, zda je uživatel přihlášen nebo ne.
|
||||||
|
//
|
||||||
|
// Předpoklad: v auth/config.php je nastaveno:
|
||||||
|
// define('VYZADOVAT_PRIHLASENI', false);
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
require_once 'auth/auth.php';
|
||||||
|
|
||||||
|
// $auth_prihlasen, $auth_uzivatel, $auth_logout_html, $auth_login_html
|
||||||
|
// jsou nyní k dispozici díky auth.php
|
||||||
|
|
||||||
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="cs">
|
<html lang="cs">
|
||||||
<head><meta charset="UTF-8"><title>Ukázka</title></head>
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Ukázková stránka – <?php echo htmlspecialchars(PROJEKT_NAZEV); ?></title>
|
||||||
|
<?php if (AUTH_CSS !== ''): ?>
|
||||||
|
<link rel="stylesheet" href="<?php echo htmlspecialchars(AUTH_CSS); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<?php if ($auth_prihlasen): ?>
|
<!-- ============================================================
|
||||||
|
HLAVIČKA – zobrazena vždy
|
||||||
|
============================================================ -->
|
||||||
|
<header>
|
||||||
|
<hr>
|
||||||
|
<h1><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h1>
|
||||||
|
<!-- Sem můžeš vložit obrázkové logo, např.: -->
|
||||||
|
<!-- <img src="/img/logo.png" alt="Logo"> -->
|
||||||
|
<hr>
|
||||||
|
</header>
|
||||||
|
|
||||||
<p>Přihlášen: <strong><?php echo htmlspecialchars($auth_uzivatel['email']); ?></strong></p>
|
|
||||||
<?php echo $auth_logout_html; ?>
|
|
||||||
<p>Toto vidí pouze přihlášený uživatel.</p>
|
|
||||||
|
|
||||||
<?php else: ?>
|
<!-- ============================================================
|
||||||
|
PŘIHLAŠOVACÍ BADGE
|
||||||
|
Nepřihlášeným: formulář (email, heslo, zapamatovat, odeslat)
|
||||||
|
+ odkaz na registraci (pokud povolena)
|
||||||
|
+ odkaz na zapomenuté heslo
|
||||||
|
Přihlášeným: email uživatele + tlačítko odhlášení
|
||||||
|
+ odkaz na administraci (pouze pro admina)
|
||||||
|
============================================================ -->
|
||||||
|
<section>
|
||||||
|
<h2>Přihlášení</h2>
|
||||||
|
|
||||||
<p>Pro zobrazení obsahu se přihlas:</p>
|
<?php if ($auth_prihlasen): ?>
|
||||||
<?php echo $auth_login_html; ?>
|
|
||||||
|
|
||||||
<?php endif; ?>
|
<?php
|
||||||
|
// Pro přihlášené zobrazíme $auth_logout_html, který obsahuje:
|
||||||
|
// - odkaz na administraci (pouze pro adminy)
|
||||||
|
// - email přihlášeného uživatele
|
||||||
|
// - tlačítko odhlášení
|
||||||
|
// Vše je sestaveno v auth.php
|
||||||
|
echo $auth_logout_html;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Pro nepřihlášené zobrazíme $auth_login_html, který obsahuje:
|
||||||
|
// - pole pro email a heslo
|
||||||
|
// - checkbox "zapamatovat si mě"
|
||||||
|
// - tlačítko odeslat
|
||||||
|
// - skrytý CSRF token a redirect URL
|
||||||
|
// Formulář odesílá data na login.php, které nás po
|
||||||
|
// přihlášení přesměruje zpět sem
|
||||||
|
echo $auth_login_html;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?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); ?>">Zapomenuté heslo</a></p>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- ============================================================
|
||||||
|
NAVIGACE – dvě varianty podle stavu přihlášení
|
||||||
|
============================================================ -->
|
||||||
|
<nav>
|
||||||
|
|
||||||
|
<?php if ($auth_prihlasen): ?>
|
||||||
|
|
||||||
|
<p>Navigace pro přihlášené uživatele:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#">Můj profil</a></li>
|
||||||
|
<li><a href="#">Moje nastavení</a></li>
|
||||||
|
<li><a href="#">Chráněná sekce A</a></li>
|
||||||
|
<li><a href="#">Chráněná sekce B</a></li>
|
||||||
|
<?php if ($auth_uzivatel['admin']): ?>
|
||||||
|
<li><a href="<?php echo htmlspecialchars(AUTH_ADMIN_URL); ?>">Administrace</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<p>Navigace pro nepřihlášené návštěvníky:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#">Úvod</a></li>
|
||||||
|
<li><a href="#">O projektu</a></li>
|
||||||
|
<li><a href="#">Veřejná sekce</a></li>
|
||||||
|
<?php if (REGISTRACE_OTEVRENA): ?>
|
||||||
|
<li><a href="<?php echo htmlspecialchars(AUTH_REGISTRACE_URL); ?>">Registrace</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- ============================================================
|
||||||
|
OBSAH STRÁNKY – stránka sama rozhoduje co zobrazit
|
||||||
|
============================================================ -->
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<?php if ($auth_prihlasen): ?>
|
||||||
|
|
||||||
|
<!-- Obsah pro přihlášené uživatele -->
|
||||||
|
<h2>Vítej, <?php echo htmlspecialchars($auth_uzivatel['email']); ?>!</h2>
|
||||||
|
|
||||||
|
<p>Toto je chráněný obsah, který vidí pouze přihlášení uživatelé.</p>
|
||||||
|
|
||||||
|
<p>Tvoje údaje z auth systému:
|
||||||
|
<ul>
|
||||||
|
<li>ID: <?php echo htmlspecialchars($auth_uzivatel['id']); ?></li>
|
||||||
|
<li>Email: <?php echo htmlspecialchars($auth_uzivatel['email']); ?></li>
|
||||||
|
<li>Admin: <?php echo $auth_uzivatel['admin'] ? 'ANO' : 'ne'; ?></li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php if ($auth_uzivatel['admin']): ?>
|
||||||
|
<p>Vidíš toto, protože jsi admin. <a href="<?php echo htmlspecialchars(AUTH_ADMIN_URL); ?>">Přejít do administrace.</a></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<!-- Obsah pro nepřihlášené návštěvníky -->
|
||||||
|
<h2>Vítej na ukázkové stránce</h2>
|
||||||
|
|
||||||
|
<p>Toto je veřejný obsah, který vidí každý návštěvník.</p>
|
||||||
|
|
||||||
|
<p>Pro zobrazení chráněného obsahu se přihlas pomocí formuláře výše.</p>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- ============================================================
|
||||||
|
PATIČKA – zobrazena vždy
|
||||||
|
============================================================ -->
|
||||||
|
<footer>
|
||||||
|
<p><?php echo htmlspecialchars(PROJEKT_NAZEV); ?>
|
||||||
|
– <a href="<?php echo htmlspecialchars(PROJEKT_URL); ?>"><?php echo htmlspecialchars(PROJEKT_URL); ?></a></p>
|
||||||
|
<hr>
|
||||||
|
</footer>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# Systém přihlašování MSPPPPaM – dokumentace pro implementaci
|
||||||
|
|
||||||
|
Tento dokument popisuje hotový systém přihlašování v PHP a MySQL,
|
||||||
|
který chci použít v novém projektu. Systém je připravený, neupravuj
|
||||||
|
jeho soubory – pouze ho používej.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Umístění souborů
|
||||||
|
|
||||||
|
Všechny soubory systému jsou ve složce `auth/` v kořenovém adresáři projektu.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Seznam souborů a jejich funkce
|
||||||
|
|
||||||
|
| Soubor | Popis |
|
||||||
|
|---|---|
|
||||||
|
| `auth/config.php` | Veškerá konfigurace systému (DB přihlašovací údaje, URL, limity, přepínače). Upravuje se při nasazení. |
|
||||||
|
| `auth/db.php` | Připojení k MySQL přes PDO. Výsledkem je proměnná `$pdo`. |
|
||||||
|
| `auth/auth.php` | Hlavní soubor – vkládá se na začátek chráněných stránek. Ověří přihlášení a nastaví proměnné (viz níže). |
|
||||||
|
| `auth/login.php` | Přihlašovací stránka s formulářem. Obsahuje ochranu proti brute force a CSRF. |
|
||||||
|
| `auth/logout.php` | Odhlášení – zruší session i remember me cookie. Přesměruje na login. |
|
||||||
|
| `auth/registrace.php` | Registrace nového uživatele (pokud je povolena v config.php). |
|
||||||
|
| `auth/reset_hesla.php` | Formulář pro zaslání odkazu na obnovu hesla emailem. |
|
||||||
|
| `auth/nove_heslo.php` | Stránka z odkazu v emailu – umožní zadat nové heslo. |
|
||||||
|
| `auth/admin.php` | Administrační rozhraní – správa uživatelů, zobrazení konfigurace, test emailu. Přístupné pouze adminům. |
|
||||||
|
| `auth/mail.php` | Pomocná funkce `odesli_mail()` pro odesílání emailů přes PHP `mail()`. |
|
||||||
|
| `auth/install.php` | Jednorázový instalační skript – vytvoří tabulky v DB a prvního admina. Po instalaci zablokován souborem `install.lock`. |
|
||||||
|
| `auth/js/heslo-sila.js` | JavaScript pro kontrolu síly hesla pomocí knihovny zxcvbn (Dropbox). Používají ho stránky s formuláři pro zadání hesla. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jak použít systém na chráněné stránce
|
||||||
|
|
||||||
|
Na začátek každého PHP souboru, jehož obsah chci chránit, vložím:
|
||||||
|
|
||||||
|
```php
|
||||||
|
require_once 'auth/auth.php';
|
||||||
|
```
|
||||||
|
|
||||||
|
Po načtení jsou k dispozici tyto proměnné:
|
||||||
|
|
||||||
|
| Proměnná | Typ | Popis |
|
||||||
|
|---|---|---|
|
||||||
|
| `$auth_prihlasen` | `bool` | `true` pokud je uživatel přihlášen, jinak `false`. Nastaveno vždy. |
|
||||||
|
| `$auth_uzivatel['id']` | `int\|null` | ID přihlášeného uživatele, nebo `null` pokud není přihlášen. |
|
||||||
|
| `$auth_uzivatel['email']` | `string\|null` | Email přihlášeného uživatele, nebo `null`. |
|
||||||
|
| `$auth_uzivatel['admin']` | `bool` | `true` pokud je přihlášený uživatel admin, jinak `false`. Nastaveno vždy. |
|
||||||
|
| `$auth_logout_html` | `string` | Připravený HTML kód s emailem uživatele, tlačítkem odhlášení a (pro adminy) odkazem na administraci. Neprázdný pouze pokud je uživatel přihlášen. Použití: `echo $auth_logout_html;` |
|
||||||
|
| `$auth_login_html` | `string` | Připravený HTML formulář pro přihlášení (email, heslo, zapamatovat si mě, CSRF token). Neprázdný pouze pokud uživatel NENÍ přihlášen. Odesílá data na `login.php`, které po přihlášení přesměruje zpět na aktuální stránku. Použití: `echo $auth_login_html;` |
|
||||||
|
|
||||||
|
### Dva režimy fungování (nastavení v `auth/config.php`)
|
||||||
|
|
||||||
|
**`VYZADOVAT_PRIHLASENI = true`** (výchozí):
|
||||||
|
Nepřihlášený uživatel je automaticky přesměrován na přihlašovací stránku.
|
||||||
|
Na chráněnou stránku se dostane až po přihlášení.
|
||||||
|
|
||||||
|
**`VYZADOVAT_PRIHLASENI = false`**:
|
||||||
|
Stránka se zobrazí všem. Pomocí `$auth_prihlasen` si stránka sama
|
||||||
|
rozhodne, co nepřihlášenému uživateli ukáže.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Databázové tabulky
|
||||||
|
|
||||||
|
Systém přihlašování vytváří a spravuje tyto tabulky:
|
||||||
|
|
||||||
|
### `auth_users` – uživatelé přihlašovacího systému
|
||||||
|
|
||||||
|
| Sloupec | Typ | Popis |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | `INT UNSIGNED` | Primární klíč, auto increment |
|
||||||
|
| `email` | `VARCHAR(255)` | Email uživatele, unikátní |
|
||||||
|
| `heslo` | `VARCHAR(255)` | Bcrypt hash hesla |
|
||||||
|
| `admin` | `TINYINT(1)` | 0 = běžný uživatel, 1 = admin |
|
||||||
|
| `vytvoreno` | `DATETIME` | Datum a čas registrace |
|
||||||
|
|
||||||
|
### `auth_remember_tokens` – tokeny pro "zapamatovat si mě"
|
||||||
|
|
||||||
|
Spravuje systém přihlašování automaticky. Není potřeba upravovat.
|
||||||
|
|
||||||
|
### `auth_brute_force` – záznamy neúspěšných přihlášení
|
||||||
|
|
||||||
|
Spravuje systém přihlašování automaticky. Není potřeba upravovat.
|
||||||
|
|
||||||
|
### `auth_password_resets` – tokeny pro obnovu hesla
|
||||||
|
|
||||||
|
Spravuje systém přihlašování automaticky. Není potřeba upravovat.
|
||||||
|
|
||||||
|
### `users` – uživatelé konkrétní služby
|
||||||
|
|
||||||
|
Tato tabulka propojuje systém přihlašování s tvým projektem.
|
||||||
|
Systém přihlašování ji vytvoří při instalaci se základní strukturou:
|
||||||
|
|
||||||
|
| Sloupec | Typ | Popis |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | `INT UNSIGNED` | Primární klíč, auto increment |
|
||||||
|
| `uzivatel_id` | `INT UNSIGNED` | Cizí klíč na `auth_users.id` (ON DELETE CASCADE) |
|
||||||
|
|
||||||
|
**Tuto tabulku rozšíříš o vlastní sloupce** podle potřeb projektu
|
||||||
|
(jméno, telefon, nastavení, apod.). Systém přihlašování ji nebude
|
||||||
|
nijak měnit – pouze při registraci nového uživatele do ní vloží
|
||||||
|
prázdný řádek s `uzivatel_id`.
|
||||||
|
|
||||||
|
Propojení s uživatelem přihlašovacího systému:
|
||||||
|
```sql
|
||||||
|
SELECT u.*, au.email
|
||||||
|
FROM users u
|
||||||
|
JOIN auth_users au ON au.id = u.uzivatel_id
|
||||||
|
WHERE u.uzivatel_id = :id
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Konfigurace (auth/config.php)
|
||||||
|
|
||||||
|
Při nasazení do projektu je potřeba vyplnit:
|
||||||
|
|
||||||
|
- `DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASS` – přihlašovací údaje k MySQL
|
||||||
|
- `PROJEKT_NAZEV` – název projektu (zobrazuje se na přihlašovací stránce)
|
||||||
|
- `PROJEKT_URL` – základní URL webu (používá se v odkazech v emailech)
|
||||||
|
- `MAIL_ODESILATEL` – emailová adresa odesílatele (musí být autorizována SPF záznamem domény)
|
||||||
|
- `MAIL_TEST_ADRESA` – adresa pro testovací email v admin rozhraní
|
||||||
|
- `REGISTRACE_OTEVRENA` – `true` = veřejná registrace, `false` = pouze admin zakládá uživatele
|
||||||
|
- `VYZADOVAT_PRIHLASENI` – `true` = přesměrovat nepřihlášené, `false` = stránka rozhoduje sama
|
||||||
|
|
||||||
|
Cesty ke stránkám auth systému (pokud je složka `auth/` jinde než v kořeni webu):
|
||||||
|
- `AUTH_LOGIN_URL`, `AUTH_LOGOUT_URL`, `AUTH_ADMIN_URL`
|
||||||
|
- `AUTH_REGISTRACE_URL`, `AUTH_RESET_URL`
|
||||||
|
- `AUTH_REDIRECT_PO_PRIHLASENI` – kam přesměrovat po přihlášení
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bezpečnostní vlastnosti systému
|
||||||
|
|
||||||
|
Pro přehled – systém řeší:
|
||||||
|
- Hesla hashována bcryptem (`password_hash` s `PASSWORD_DEFAULT`)
|
||||||
|
- Ochrana před SQL injection – výhradně PDO prepared statements
|
||||||
|
- Ochrana před CSRF – každý formulář obsahuje token ověřovaný na serveru
|
||||||
|
- Ochrana před brute force – blokování po opakovaných neúspěšných pokusech
|
||||||
|
- Ochrana před session fixation – `session_regenerate_id()` při přihlášení
|
||||||
|
- Ochrana před XSS – `htmlspecialchars()` na všech výstupech
|
||||||
|
- Remember me – bezpečný selector+token mechanismus (heslo ani ID není v cookie)
|
||||||
|
- Síla hesla – kontrola přes zxcvbn (JavaScript) + minimální délka (PHP)
|
||||||
|
- Celá DB v `utf8mb4` – plná podpora diakritiky a emoji
|
||||||
Reference in New Issue
Block a user