[FÁZE-1][config] Přidán konfigurační soubor systému přihlašování

[FÁZE-1][db] Přidáno připojení k databázi přes PDO
[FÁZE-1][install] Přidán instalační skript s tvorbou tabulek a prvního admina
This commit is contained in:
stepan
2026-03-16 23:08:28 +01:00
parent b24b0720f6
commit c4ec313eeb
3 changed files with 518 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
<?php
// ============================================================
// KONFIGURACE SYSTÉMU PŘIHLAŠOVÁNÍ
// ============================================================
// Tento soubor upravíš při nasazení do každého projektu.
// NIKDY jej nenahrávej na veřejné místo s reálnými hesly!
// ============================================================
// ------------------------------------------------------------
// DATABÁZE
// ------------------------------------------------------------
// Adresa databázového serveru (většinou localhost)
define('DB_HOST', 'localhost');
// Název databáze
define('DB_NAME', 'nazev_databaze');
// Uživatelské jméno pro přístup k databázi
define('DB_USER', 'uzivatel_databaze');
// Heslo pro přístup k databázi
define('DB_PASS', 'heslo_databaze');
// Název tabulky uživatelů přihlašovacího systému
// (neměň po první instalaci!)
define('DB_TABULKA_UZIVATELE', 'uzivatele');
// Název tabulky remember me tokenů
define('DB_TABULKA_TOKENY', 'remember_tokeny');
// Název tabulky pro brute force záznamy
define('DB_TABULKA_BRUTE', 'brute_force');
// Název tabulky pro tokeny obnovy hesla
define('DB_TABULKA_RESET', 'reset_hesla');
// Název tabulky uživatelů konkrétní služby (tvoje vlastní tabulka)
// Při registraci se do ní automaticky přidá prázdný řádek.
// Pokud tuto funkci nechceš, nastav na prázdný řetězec: ''
define('DB_TABULKA_SLUZBA', 'sluzba_uzivatele');
// ------------------------------------------------------------
// NÁZEV A URL PROJEKTU
// ------------------------------------------------------------
// Název projektu zobrazuje se na přihlašovací stránce apod.
define('PROJEKT_NAZEV', 'Název mého projektu');
// Základní URL projektu (bez lomítka na konci)
// Používá se např. v odkazech v emailech
define('PROJEKT_URL', 'https://example.com');
// Cesta k přihlašovací stránce (relativní od kořene projektu)
define('AUTH_LOGIN_URL', '/auth/login.php');
// Cesta k hlavní stránce po přihlášení
define('AUTH_REDIRECT_PO_PRIHLASENI', '/index.php');
// ------------------------------------------------------------
// REGISTRACE
// ------------------------------------------------------------
// true = registrace je otevřená pro kohokoliv (zobrazí se odkaz na registraci)
// false = registraci může provádět pouze admin přes admin.php
define('REGISTRACE_OTEVRENA', true);
// ------------------------------------------------------------
// PŘÍSTUP NEPŘIHLÁŠENÝCH UŽIVATELŮ
// ------------------------------------------------------------
// true = nepřihlášený uživatel je VŽDY přesměrován na login,
// obsah chráněné stránky se mu vůbec nezobrazí
// 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);
// ------------------------------------------------------------
// SESSION
// ------------------------------------------------------------
// Název session cookie (změň, pokud máš na serveru více projektů)
define('SESSION_NAZEV', 'auth_session');
// Maximální délka nečinnosti session v sekundách (výchozí: 2 hodiny)
define('SESSION_EXPIRACE', 60 * 60 * 2);
// ------------------------------------------------------------
// REMEMBER ME (zapamatovat si mě)
// ------------------------------------------------------------
// Délka platnosti "zapamatovat si mě" cookie v sekundách (výchozí: 30 dní)
define('REMEMBER_EXPIRACE', 60 * 60 * 24 * 30);
// ------------------------------------------------------------
// BRUTE FORCE OCHRANA
// ------------------------------------------------------------
// Maximální počet neúspěšných pokusů před zablokováním
define('BRUTE_MAX_POKUSU', 10);
// Časové okno v sekundách, ve kterém se pokusy počítají (výchozí: 15 minut)
define('BRUTE_OKNO', 60 * 15);
// ------------------------------------------------------------
// HESLO MINIMÁLNÍ SÍLA
// ------------------------------------------------------------
// Minimální skóre zxcvbn (0 = nejslabší, 4 = nejsilnější)
// Doporučená hodnota: 2 (rozumně silné heslo)
define('HESLO_MIN_SILA', 2);
// Minimální délka hesla v znacích
define('HESLO_MIN_DELKA', 8);
// ------------------------------------------------------------
// EMAIL
// ------------------------------------------------------------
// Emailová adresa, ze které se odesílají zprávy systému
define('MAIL_ODESILATEL', 'noreply@example.com');
// Jméno odesílatele (zobrazí se v emailovém klientu)
define('MAIL_ODESILATEL_JMENO', 'Název mého projektu');
// Testovací emailová adresa (pro tlačítko "test emailu" v admin.php)
define('MAIL_TEST_ADRESA', 'tvuj@email.com');
// ------------------------------------------------------------
// STYLOVÁNÍ
// ------------------------------------------------------------
// 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', '');
+48
View File
@@ -0,0 +1,48 @@
<?php
// ============================================================
// PŘIPOJENÍ K DATABÁZI
// ============================================================
// Tento soubor vytvoří připojení k MySQL databázi přes PDO.
// PDO je modernější a bezpečnější způsob práce s databází než
// starší mysqli (které bylo použito v původním projektu).
//
// Výsledkem je proměnná $pdo, kterou pak používají ostatní
// soubory pro dotazy na databázi.
// ============================================================
// Pokud config.php ještě není načtený, načteme ho
if (!defined('DB_HOST')) {
require_once __DIR__ . '/config.php';
}
// DSN = Data Source Name řetězec popisující připojení k DB
// utf8mb4 = plná podpora Unicode (diakritika, emoji, ...)
// utf8mb4_unicode_ci = správné řazení a porovnávání znaků
$dsn = 'mysql:host=' . DB_HOST
. ';dbname=' . DB_NAME
. ';charset=utf8mb4';
// Nastavení chování PDO
$moznosti = [
// Při chybě vyhoď výjimku (exception) místo tichého selhání
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// Výsledky vrací jako asociativní pole (např. $radek['email'])
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// Vypne emulovné prepared statements používáme pravé,
// které chrání před SQL injection na úrovni databáze
PDO::ATTR_EMULATE_PREPARES => false,
];
// Pokus o připojení k databázi
try {
$pdo = new PDO($dsn, DB_USER, DB_PASS, $moznosti);
} catch (PDOException $e) {
// Při chybě připojení zobrazíme obecnou chybovou hlášku.
// Detailní chybu NEZOBRAZUJEME uživateli mohla by prozradit
// citlivé informace o struktuře serveru.
// Chybu ale zalogujeme do PHP error logu pro administrátora.
error_log('Chyba připojení k databázi: ' . $e->getMessage());
die('Nepodařilo se připojit k databázi. Zkuste to prosím později.');
}
+323
View File
@@ -0,0 +1,323 @@
<?php
// ============================================================
// INSTALAČNÍ SKRIPT
// ============================================================
// Tento skript spusť JEDNOU po nahrání souborů na server.
// Vytvoří všechny potřebné tabulky v databázi a prvního
// administrátora.
//
// Po úspěšné instalaci se vytvoří soubor install.lock
// a tento skript již nepůjde spustit znovu.
//
// BEZPEČNOST: Po instalaci můžeš install.php smazat,
// nebo ho nechat install.lock ho zablokuje.
// ============================================================
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/db.php';
// ------------------------------------------------------------
// KONTROLA: Proběhla už instalace?
// ------------------------------------------------------------
$lock_soubor = __DIR__ . '/install.lock';
if (file_exists($lock_soubor)) {
die('<p>Instalace již proběhla. Pokud chceš instalaci opakovat, smaž soubor <code>auth/install.lock</code>. Pozor tím přijdeš o všechna data!</p>');
}
// ------------------------------------------------------------
// ZPRACOVÁNÍ FORMULÁŘE (pokud byl odeslán)
// ------------------------------------------------------------
$chyba = '';
$uspech = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Načtení hodnot z formuláře
$email = trim($_POST['email'] ?? '');
$heslo = $_POST['heslo'] ?? '';
$heslo2 = $_POST['heslo2'] ?? '';
// Základní validace emailu
if (empty($email)) {
$chyba = 'Email nesmí být prázdný.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$chyba = 'Email není platná emailová adresa.';
}
// Validace hesla
if (empty($chyba) && empty($heslo)) {
$chyba = 'Heslo nesmí být prázdné.';
} elseif (empty($chyba) && mb_strlen($heslo) < HESLO_MIN_DELKA) {
$chyba = 'Heslo musí mít alespoň ' . HESLO_MIN_DELKA . ' znaků.';
} elseif (empty($chyba) && $heslo !== $heslo2) {
$chyba = 'Hesla se neshodují.';
}
// Pokud není chyba, spustíme instalaci
if (empty($chyba)) {
try {
// ------------------------------------------------
// VYTVOŘENÍ TABULEK
// ------------------------------------------------
// Tabulka uživatelů přihlašovacího systému
// utf8mb4 = plná podpora Unicode (diakritika, emoji)
$pdo->exec("
CREATE TABLE IF NOT EXISTS `" . DB_TABULKA_UZIVATELE . "` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(255) NOT NULL,
`heslo` VARCHAR(255) NOT NULL,
`admin` TINYINT(1) NOT NULL DEFAULT 0,
`vytvoreno` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Tabulka remember me tokenů
// selector = veřejný identifikátor pro vyhledání záznamu v DB
// token_hash = bcrypt hash tajného tokenu (samotný token je jen v cookie)
$pdo->exec("
CREATE TABLE IF NOT EXISTS `" . DB_TABULKA_TOKENY . "` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uzivatel_id` INT UNSIGNED NOT NULL,
`selector` VARCHAR(32) NOT NULL,
`token_hash` VARCHAR(255) NOT NULL,
`expiruje` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `selector` (`selector`),
FOREIGN KEY (`uzivatel_id`) REFERENCES `" . DB_TABULKA_UZIVATELE . "` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Tabulka brute force záznamů
// Ukládají se neúspěšné pokusy o přihlášení
$pdo->exec("
CREATE TABLE IF NOT EXISTS `" . DB_TABULKA_BRUTE . "` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`ip_adresa` VARCHAR(45) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`cas` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `ip_adresa` (`ip_adresa`),
KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Tabulka tokenů pro obnovu hesla
// Token je platný jen omezenou dobu (viz config.php)
$pdo->exec("
CREATE TABLE IF NOT EXISTS `" . DB_TABULKA_RESET . "` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uzivatel_id` INT UNSIGNED NOT NULL,
`token_hash` VARCHAR(255) NOT NULL,
`expiruje` DATETIME NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`uzivatel_id`) REFERENCES `" . DB_TABULKA_UZIVATELE . "` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Tabulka uživatelů konkrétní služby (pokud je nastavena)
// Tato tabulka slouží pro data specifická pro tvůj projekt
// (jméno, nastavení, kontaktní údaje apod.)
if (DB_TABULKA_SLUZBA !== '') {
$pdo->exec("
CREATE TABLE IF NOT EXISTS `" . DB_TABULKA_SLUZBA . "` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uzivatel_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uzivatel_id` (`uzivatel_id`),
FOREIGN KEY (`uzivatel_id`) REFERENCES `" . DB_TABULKA_UZIVATELE . "` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
}
// ------------------------------------------------
// VYTVOŘENÍ PRVNÍHO ADMIN UŽIVATELE
// ------------------------------------------------
$heslo_hash = password_hash($heslo, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("
INSERT INTO `" . DB_TABULKA_UZIVATELE . "`
(`email`, `heslo`, `admin`)
VALUES
(:email, :heslo, 1)
");
$stmt->execute([
':email' => $email,
':heslo' => $heslo_hash,
]);
$novy_uzivatel_id = $pdo->lastInsertId();
// Pokud existuje tabulka služby, vytvoříme prázdný řádek
if (DB_TABULKA_SLUZBA !== '') {
$stmt2 = $pdo->prepare("
INSERT INTO `" . DB_TABULKA_SLUZBA . "`
(`uzivatel_id`)
VALUES
(:uzivatel_id)
");
$stmt2->execute([':uzivatel_id' => $novy_uzivatel_id]);
}
// ------------------------------------------------
// VYTVOŘENÍ ZÁMKU zabránění opakované instalaci
// ------------------------------------------------
file_put_contents($lock_soubor, 'Instalace proběhla: ' . date('Y-m-d H:i:s'));
$uspech = true;
} catch (PDOException $e) {
// Chybu zalogujeme, uživateli zobrazíme obecnou hlášku
error_log('Chyba při instalaci: ' . $e->getMessage());
$chyba = 'Při instalaci došlo k chybě databáze. Zkontroluj nastavení v config.php a PHP error log.';
} catch (Exception $e) {
error_log('Chyba při instalaci: ' . $e->getMessage());
$chyba = 'Při instalaci došlo k neočekávané chybě.';
}
}
}
// ------------------------------------------------------------
// HTML VÝSTUP
// ------------------------------------------------------------
// Poznámka: žádné stylování vše je záměrně prosté,
// aby žádný prvek nebyl skrytý nebo překrytý.
// ------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<title>Instalace <?php echo htmlspecialchars(PROJEKT_NAZEV); ?></title>
</head>
<body>
<h1>Instalace systému přihlašování</h1>
<h2><?php echo htmlspecialchars(PROJEKT_NAZEV); ?></h2>
<?php if ($uspech): ?>
<p><strong>Instalace proběhla úspěšně!</strong></p>
<p>Tabulky byly vytvořeny a první administrátor byl založen.</p>
<p>Byl vytvořen soubor <code>auth/install.lock</code> instalaci již nelze spustit znovu.</p>
<p><a href="<?php echo htmlspecialchars(AUTH_LOGIN_URL); ?>">Přejít na přihlášení</a></p>
<?php else: ?>
<p>Tento skript vytvoří potřebné tabulky v databázi a založí prvního administrátora.</p>
<p>Spusť ho pouze jednou. Po instalaci bude zablokován souborem <code>install.lock</code>.</p>
<?php if (!empty($chyba)): ?>
<p><strong>Chyba: <?php echo htmlspecialchars($chyba); ?></strong></p>
<?php endif; ?>
<form method="POST" action="">
<p>
<label for="email">Email administrátora:</label><br>
<input
type="email"
id="email"
name="email"
value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>"
required
>
</p>
<p>
<label for="heslo">Heslo administrátora:</label><br>
<input
type="password"
id="heslo"
name="heslo"
required
>
<br>
<!-- Indikátor síly hesla vyplní ho zxcvbn přes JavaScript -->
<span id="heslo-sila">Síla hesla: zadej heslo</span>
</p>
<p>
<label for="heslo2">Heslo znovu (pro ověření):</label><br>
<input
type="password"
id="heslo2"
name="heslo2"
required
>
</p>
<p>
<!-- Tlačítko je ve výchozím stavu zakázané.
JavaScript ho povolí, až heslo dosáhne požadované síly. -->
<button type="submit" id="tlacitko-odeslat" disabled>
Spustit instalaci
</button>
<span id="tlacitko-duvod"> (čekám na dostatečně silné heslo)</span>
</p>
</form>
<?php endif; ?>
<!-- ============================================================
zxcvbn knihovna pro hodnocení síly hesla od Dropboxu
Načítáme z CDN. Pokud chceš offline použití, stáhni soubor
a změň src na lokální cestu.
============================================================ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.4.2/zxcvbn.js"></script>
<script>
// Minimální požadovaná síla hesla (přebíráme z PHP konfigurace)
const MIN_SILA = <?php echo (int) HESLO_MIN_SILA; ?>;
// Popisky síly hesla (0 = nejslabší, 4 = nejsilnější)
const POPISKY_SILY = [
'Velmi slabé',
'Slabé',
'Průměrné',
'Silné',
'Velmi silné'
];
const inputHeslo = document.getElementById('heslo');
const spanSila = document.getElementById('heslo-sila');
const tlacitko = document.getElementById('tlacitko-odeslat');
const spanDuvod = document.getElementById('tlacitko-duvod');
inputHeslo.addEventListener('input', function () {
const hodnota = this.value;
if (hodnota.length === 0) {
spanSila.textContent = 'Síla hesla: zadej heslo';
tlacitko.disabled = true;
spanDuvod.textContent = ' (čekám na dostatečně silné heslo)';
return;
}
// Spuštění zxcvbn hodnocení
const vysledek = zxcvbn(hodnota);
const skore = vysledek.score; // 04
spanSila.textContent = 'Síla hesla: ' + POPISKY_SILY[skore] + ' (' + skore + '/4)';
if (skore >= MIN_SILA) {
tlacitko.disabled = false;
spanDuvod.textContent = '';
} else {
tlacitko.disabled = true;
spanDuvod.textContent = ' (heslo je příliš slabé, potřebuji alespoň ' + MIN_SILA + '/4)';
}
});
</script>
</body>
</html>