Compare commits
18 Commits
eba0fee983
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e4c10d084 | |||
| cb3e7a2969 | |||
| af6df61128 | |||
| 78be7ec1c2 | |||
| 52bb0208a9 | |||
| f6e5695ef9 | |||
| 876cda8ab4 | |||
| f1c73b41a7 | |||
| 3c9267b1c5 | |||
| 19c92c552d | |||
| cc4cd26a1e | |||
| d4b7b01fe7 | |||
| d26f66f9a8 | |||
| 40289394d2 | |||
| ad0916f25b | |||
| 691ecf6af5 | |||
| 1a3b1a369e | |||
| 79c2a13266 |
+23
-1
@@ -174,10 +174,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$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
|
||||
@@ -284,6 +290,7 @@ try {
|
||||
<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">
|
||||
@@ -293,7 +300,14 @@ try {
|
||||
<button type="submit">Změnit heslo</button>
|
||||
</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;"
|
||||
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); ?>">
|
||||
@@ -301,6 +315,13 @@ try {
|
||||
<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>
|
||||
@@ -381,6 +402,7 @@ try {
|
||||
<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>
|
||||
|
||||
+326
-228
@@ -1,270 +1,368 @@
|
||||
<?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__ . '/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_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'lifetime' => 0, // 0 = cookie platí do zavření prohlížeče
|
||||
// (trvalost zajišťuje remember me, ne session)
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
// 'secure' => true,
|
||||
'httponly' => true, // JavaScript k cookie nemá přístup
|
||||
'samesite' => 'Strict', // ochrana před CSRF
|
||||
// 'secure' => true, // odkomentuj pokud web běží na HTTPS
|
||||
]);
|
||||
session_start();
|
||||
|
||||
// Pokud je uživatel již přihlášen, přesměrujeme ho rovnou dál
|
||||
if (isset($_SESSION['uzivatel_id'])) {
|
||||
header('Location: ' . AUTH_REDIRECT_PO_PRIHLASENI);
|
||||
// ------------------------------------------------------------
|
||||
// VÝCHOZÍ STAV – uživatel není přihlášen
|
||||
// ------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// CSRF TOKEN
|
||||
// CSRF TOKEN – zajistíme, že vždy existuje
|
||||
// ------------------------------------------------------------
|
||||
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
$csrf_token = $_SESSION['csrf_token'];
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// ZPRACOVÁNÍ FORMULÁŘE
|
||||
// PŘIPRAVENÉ HTML PROMĚNNÉ
|
||||
// ------------------------------------------------------------
|
||||
|
||||
$chyba = '';
|
||||
$email_hodnota = '';
|
||||
// -- Odhlašovací formulář ------------------------------------
|
||||
// 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 ----------------------------------
|
||||
$csrf_z_formulare = $_POST['csrf_token'] ?? '';
|
||||
if (!hash_equals($csrf_token, $csrf_z_formulare)) {
|
||||
$chyba = 'Neplatný požadavek. Zkuste stránku obnovit a přihlásit se znovu.';
|
||||
$auth_logout_html = '';
|
||||
|
||||
// Odkaz na admin rozhraní – pouze pro admina
|
||||
if ($auth_uzivatel['admin']) {
|
||||
$auth_logout_html .=
|
||||
'<a href="' . htmlspecialchars(AUTH_ADMIN_URL) . '">Administrace</a> | ';
|
||||
}
|
||||
|
||||
if (empty($chyba)) {
|
||||
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$heslo = $_POST['heslo'] ?? '';
|
||||
$zapamatovat = isset($_POST['zapamatovat']);
|
||||
|
||||
$email_hodnota = htmlspecialchars($email);
|
||||
|
||||
if (empty($email) || empty($heslo)) {
|
||||
$chyba = 'Zadejte email a heslo.';
|
||||
}
|
||||
}
|
||||
|
||||
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řihlášený uživatel a tlačítko odhlášení
|
||||
$auth_logout_html .=
|
||||
htmlspecialchars($auth_uzivatel['email'])
|
||||
. ' | '
|
||||
. '<form method="POST" action="' . htmlspecialchars(AUTH_LOGOUT_URL) . '"'
|
||||
. ' style="display:inline;">'
|
||||
. '<input type="hidden" name="csrf_token" value="'
|
||||
. htmlspecialchars($_SESSION['csrf_token']) . '">'
|
||||
. '<button type="submit">Odhlásit se</button>'
|
||||
. '</form>';
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// HTML VÝSTUP
|
||||
// ------------------------------------------------------------
|
||||
?>
|
||||
<!DOCTYPE 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>
|
||||
// -- Přihlašovací formulář -----------------------------------
|
||||
// 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.
|
||||
// Použití na stránce: echo $auth_login_html;
|
||||
|
||||
<h1>Přihlášení</h1>
|
||||
<h2><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h2>
|
||||
if (!$auth_prihlasen) {
|
||||
|
||||
<?php if (!empty($chyba)): ?>
|
||||
<p><strong>Chyba: <?php echo htmlspecialchars($chyba); ?></strong></p>
|
||||
<?php endif; ?>
|
||||
// Aktuální URL předáme login.php jako parametr,
|
||||
// aby nás po úspěšném přihlášení přesměroval zpět sem
|
||||
$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>
|
||||
<label for="email">Email:</label><br>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value="<?php echo $email_hodnota; ?>"
|
||||
required
|
||||
autocomplete="email"
|
||||
>
|
||||
</p>
|
||||
// Aktuální URL – login.php nás po přihlášení přesměruje zpět
|
||||
. '<input type="hidden" name="redirect_url" value="'
|
||||
. htmlspecialchars($aktualni_url) . '">'
|
||||
|
||||
<p>
|
||||
<label for="heslo">Heslo:</label><br>
|
||||
<input
|
||||
type="password"
|
||||
id="heslo"
|
||||
name="heslo"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
>
|
||||
</p>
|
||||
. '<p>'
|
||||
. '<label for="auth-email">Email:</label><br>'
|
||||
. '<input type="email" id="auth-email" name="email"'
|
||||
. ' required autocomplete="email">'
|
||||
. '</p>'
|
||||
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="zapamatovat" value="1">
|
||||
Zapamatovat si mě
|
||||
</label>
|
||||
</p>
|
||||
. '<p>'
|
||||
. '<label for="auth-heslo">Heslo:</label><br>'
|
||||
. '<input type="password" id="auth-heslo" name="heslo"'
|
||||
. ' required autocomplete="current-password">'
|
||||
. '</p>'
|
||||
|
||||
<p>
|
||||
<button type="submit">Přihlásit se</button>
|
||||
</p>
|
||||
. '<p>'
|
||||
. '<label>'
|
||||
. '<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): ?>
|
||||
<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>
|
||||
. '</form>';
|
||||
}
|
||||
+5
-4
@@ -41,7 +41,7 @@ define('DB_TABULKA_SLUZBA', 'users');
|
||||
// ------------------------------------------------------------
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
define('AUTH_LOGIN_URL', '/auth/login.php');
|
||||
define('AUTH_LOGOUT_URL', '/auth/logout.php');
|
||||
define('AUTH_ADMIN_URL', '/auth/admin.php');
|
||||
define('AUTH_REGISTRACE_URL', '/auth/registrace.php');
|
||||
define('AUTH_RESET_URL', '/auth/reset_hesla.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í
|
||||
// proměnné $auth_prihlasen a $auth_uzivatel),
|
||||
// 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');
|
||||
|
||||
// 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)
|
||||
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.
|
||||
// Pokud nechceš vlastní styl, nastav na prázdný řetězec: ''
|
||||
// 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,12 +1,175 @@
|
||||
<?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>
|
||||
<html lang="cs">
|
||||
<head><meta charset="UTF-8"><title>Chráněná stránka</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>
|
||||
<p>Přihlášen: <?php echo htmlspecialchars($auth_uzivatel['email']); ?></p>
|
||||
<?php echo $auth_logout_html; ?>
|
||||
<p>Zde je chráněný obsah.</p>
|
||||
|
||||
<!-- ============================================================
|
||||
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Ř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>
|
||||
|
||||
<?php if ($auth_prihlasen): ?>
|
||||
|
||||
<?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>
|
||||
</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