19. Bezpečnost (2FA, IP allowlist, role, activity log)

Bezpečnost MyInvoice stojí na 4 vrstvách:

  1. Autentizace — bcrypt hesla + peppered + brute-force ochrana + CAPTCHA
  2. 2FA (TOTP) — volitelné druhé ověření přes mobilní aplikaci
  3. Síťová izolace — IP allowlist (volitelný, doporučeno v produkci)
  4. Autorizace — role-based access (admin / accountant / readonly)
  5. Audit — activity log všech mutací

19.1 Hesla

VrstvaDetail
Algoritmusbcrypt cost 12
PepperSůl z cfg.php → app.pepper (32B base64), neukládá se v DB
Min. délka12 znaků
Max. délkaBez limitu — passphrase je doporučená (20+ znaků)
Kontrola sílyIndikátor v UI (slabé / střední / silné)
Reset heslaOdkaz na 1 hodinu, e-mailem

💡 Passphrase je bezpečnější než krátké složité heslo. „korelace medvědí dýně přístav 2026" má 49 znaků a je odolnější vůči brute-force než „Hu1@n!22".

19.2 Dvoufaktorové ověření (TOTP)

TOTP = time-based one-time password (RFC 6238). Nejznámější standard pro 2FA.

19.2.1 Aktivace

Můj profil → 2FA → Aktivovat.

Aktivace 2FA
Aktivace 2FA
  1. Aplikace ukáže QR kód + textový secret key.
  2. V mobilu otevři autentikátor (Google Authenticator, Authy, Microsoft Authenticator, 1Password, Bitwarden) → Přidat účet → Sken QR kódu.
  3. Aplikace začne generovat 6-cifrené kódy každých 30 sekund.
  4. Zadej aktuální kód do MyInvoice → Potvrdit aktivaci.

⚠️ MyInvoice nepoužívá záložní jednorázové kódy (recovery codes). Při ztrátě autentikátoru použij CLI rescue: php api/bin/reset-2fa.php <email> — viz § 19.2.3.

19.2.2 Přihlášení s 2FA

Po zadání e-mailu + hesla aplikace vyzve k 6-cifernému kódu z autentikátoru.

2FA výzva
2FA výzva

Pokud autentikátor nemáš po ruce, nezbývá než provést rescue reset (následující sekce).

19.2.3 Ztráta telefonu / deaktivace

Aplikace nemá UI pro deaktivaci 2FA — doporučený postup je CLI rescue tool:

php api/bin/reset-2fa.php tvuj@email.cz

Skript nastaví totp_enabled = 0 a totp_secret = NULL pro zadaný účet. Pak se přihlásíš jen s heslem a 2FA si můžeš znovu aktivovat na novém telefonu (Můj profil → 2FA → Aktivovat).

Pokud nemáš shell přístup ke kontejneru/serveru, použij SQL fallback:

UPDATE users
SET totp_enabled = 0, totp_secret = NULL
WHERE email = 'tvuj@email.cz';

⚠️ Pro produkční nasazení doporučujeme mít k DB přístup přes admin (phpMyAdmin / Adminer / mysql CLI) připravený předem. Při ztrátě telefonu by jinak nikdo nešel do aplikace.

19.2.4 Vynucení 2FA pro všechny uživatele

Pokud chceš, aby každý uživatel po přihlášení musel mít aktivní TOTP, nastav v cfg.php (nebo cfg.local.php):

'auth' => [
    'require_totp' => true,
],

Stejné lze přepnout přes ENV (Docker / PaaS):

MYINVOICE_AUTH_REQUIRE_TOTP=true

Chování:

💡 Volbu lze zapnout i v instalačních skriptech: - CLI: php api/bin/setup.php se ptá *„Vynutit 2FA?"* a v případě souhlasu zapíše auth.require_totp = true do cfg.local.php. - Web wizard (/setup): checkbox v kroku „Admin účet" má stejný efekt; po dokončení je admin rovnou přesměrován na /setup-totp.

⚠️ Vyžaduje validní app.secret_encryption_key (32B base64). Při špatné konfiguraci by uživatelé skončili v silent-500 — health endpoint vrací warning, viz § 99 Řešení problémů.

19.3 Brute-force ochrana

Pokusy běhemAkce
5 selhání / 5 minutCAPTCHA (Cloudflare Turnstile)
10 selhání / 15 minutLockout 15 minut (per IP)
30 selhání / 1 hodinuLockout 24 hodin + e-mail uživateli o pokusech

Implementace: Redis pokud běží, jinak MariaDB MEMORY engine fallback.

19.4 IP allowlist (volitelné)

V cfg.php → ip_allowlist.allow můžeš omezit přístup jen na vybrané IP / CIDR rozsahy.

'ip_allowlist' => [
    'enabled' => true,
    'mode' => 'block',           // 'block' = ne-allowlisted IP dostane 403
    'allow' => [
        '127.0.0.1',
        '203.0.113.42',          // tvoje kancelářská WAN (IPv4)
        '2001:db8:1234::/48',    // IPv6 prefix
    ],
],

Doporučení v produkci:

🛈 IP allowlist je v cfg.php (file-based config) → změna vyžaduje SSH / deploy. Není v UI schválně — v případě omylu by ses zablokoval a nemohl si ho přes UI sundat.

19.5 RBAC (role-based access)

Tři role:

RoleUIAPIVystavováníEditace vystavenéKonfiguraceUživatelé
admin✅ (force)
accountant
readonly✅ (read)✅ (read)

Per endpoint je v API kódu definovaná minimální role. UI se podle aktuální role uživatele schovává tlačítka, na která nemá nárok.

19.6 CSRF + Origin check

Každý mutating request (POST / PUT / PATCH / DELETE) musí mít:

  1. Origin header se shodující s app.url v cfg.php
  2. X-CSRF-Token header se shodující s tokenem v session

Bez nich → 403 csrf_failed / origin_mismatch. UI to obsluhuje automaticky (token v Pinia store, header v axios interceptoru).

19.7 Activity log

Každá mutace (vytvoření / změna / vystavení / smazání) se loguje. Záznamy obsahují:

Viz 18. Nastavení → § 15.6 pro UI.

19.7.1 Co log NEUKLÁDÁ

19.8 DKIM podpis e-mailů

Pro deliverabilitu (aby gmail / o365 / seznam tvé maily nepoznačily jako spam) doporučujeme aktivovat DKIM:

  1. Vygeneruj RSA klíč: openssl genrsa -out private/dkim/myinvoice.pem 2048
  2. Public key → DNS TXT záznam myinvoice._domainkey.tvoje-domena.cz
  3. V cfg.php → smtp.dkim.enabled => true
  4. Restart služby

Detaily v README.md v rootu repa.

19.9 Bezpečnostní audit

V source/07-security-audit.md najdeš výsledky interního auditu — všechny identifikované findings (P1/P2/P3) jsou vyřešené nebo odůvodněně vynechané.

19.10 Tipy