Chyba #169
otevřenýdev.noveknihy.local: login padá na chybějícím klíči slepého indexu, dev DB zaostává za kódem
Popis
h2. Příznak
@POST http://dev.noveknihy.local/login.php?action=process@ vracel HTTP 200 s fatální chybou v těle:
Fatal error: Uncaught RuntimeException: BlindIndex: key file not readable: /home/f/git/noveknihy/catalog/includes/local/index_keys.php in /home/f/git/noveknihy/catalog/includes/classes/Noveknihy/BlindIndex.php:100 #2 .../includes/modules/content/login/cm_login_form.php(42): Noveknihy\BlindIndex::whereEmail()
Pozor na reprodukci: bez platného @formid@ se @Form::validate_action_is('process')@ neprojde a @cm_login_form::login()@ se vůbec nezavolá, takže se chyba neprojeví. Je potřeba vzít token z @GET /login.php@ se stejnou cookie @ceid@.
h2. Příčina
Není to chyba v kódu. Pracovní kopie @/home/f/git/noveknihy@ (větev @functionalHTML@) byla napřed proti databázi @noveknihy_dev@. Ověřeno:
- @/home/f/git/noveknihy/catalog/includes/local/index_keys.php@ neexistoval
- sloupec @customers.customers_email_bidx@ neexistoval, @customers_email_address@ byl plaintext @varchar(255)@ s čitelnými adresami (10 zákazníků)
- tabulky @role_keys@, @role_members@ neexistovaly
- @php vendor/bin/phinx status -c phinx-adapter.php@ hlásil 16 migrací @down@ — od @20261016150000 AKeyIsWrappedForEveryReader@ po @20261026100000 AGuestBuysWithoutBeingSignedIn@, tedy celou vrstvu šifrování osobních údajů
@cm_login_form::login()@ volá @BlindIndex::whereEmail()@ bezpodmínečně. Dokumentace @/home/f/git/noveknihy/doc/cs/sifrovani-osobnich-udaju.md:745@ to říká přímo: "Dokud @--seal@ u zákazníků neproběhl, nikdo se nepřihlásí."
h2. Druhý zasažený subsystém
Stejná příčina shazovala i agentní API: @/home/f/git/noveknihy/catalog/includes/classes/agent_auth.php:38@ selectuje sloupec @pii_key_token@, který vzniká až migrací @20261026040000 AnAgentCarriesTheCustomersKeyInItsToken@. Jakýkoli předložený token tedy skončil fatální chybou místo JSON odpovědi.
h2. Co už je udělané (4. 11. 2026 večer)
Záloha databáze před zásahem: @/home/f/zalohy-noveknihy/noveknihy_dev-pred-sifrovanim-2026-11-04-2352.sql.gz@ (62 MB, práva 0600). Integrita gzipu, přítomnost klíčových tabulek i úplnost dumpu ověřeny. Záměrně mimo repozitář — @/home/f/git/noveknihy/db/data/@ není v @.gitignore@ a dump obsahuje zákaznická data.¶
Všech 16 migrací spuštěno, @phinx status@ nehlásí nic @down@. Vzniklo @customers_email_bidx@, @pii_key_token@, @role_keys@, @role_members@. Migrace mimochodem zvedly minimální délku hesla z 5 na 8 a zapnuly brzdy @ar_login.php@ a @ar_admin_login.php@.¶
Vygenerován a zapsán indexový klíč @/home/f/git/noveknihy/catalog/includes/local/index_keys.php@ (32 bajtů, verze 1, práva 0600). Před generováním ověřeno, že žádný starší nikde neleží — ty v @/home/nkcrypto/keys/@ a @/home/nkcryptoadmin/keys/@ patří crypto simulaci na jiné databázi @noveknihy_crypto@ (uživatelé @nkc_shop@ / @nkc_admin@), a záloha @/home/f/noveknihy-zaloha-klicu-NA-PAPIR.txt@ je ke značkovacímu klíči e-knih a platebním klíčům GP webpay. Ověřeno také, že web ten adresář nevydává: @includes/local/.htaccess@ má @Require all denied@ a všechny zkoušené cesty vrací 403.¶
Fatální chyba v loginu je tím pryč — stránka vrací korektní "E-mailová adresa nebo heslo nenalezeno." Agentní API vrací korektní JSON 401 místo stack trace.¶
h2. Co zbývá
Obřad s klíči podle @/home/f/git/noveknihy/doc/cs/sifrovani-osobnich-udaju.md:684-745@. Vyžaduje heslo administrátora, které zadá člověk, protože se pod ním zabalí privátní klíč role. Připraven skript @/home/f/obrad-dev-klice.sh@ (práva 0700, heslo čte bez echa, takže neskončí v historii shellu):
- administrátor @devkeys@
- @crypto-keys.php init@ — záchranný klíč + role @root@ a @secretariat@
- @crypto-keys.php shop-key@ — role @shop@; bez její veřejné půlky se nezapečetí nic a nikdo se nezaregistruje ani nenakoupí (@RoleKeys::publicKeyOf()@ vyhodí @no role 'shop'@). Soubor @shop_private.php@ vzniknout nesmí, privátní půlka zůstane zapečetěná na záchranný klíč v @role_keys.wrapped_private@. Pořadí je dané: @RoleKeys::createRole()@ na @RoleKeys.php:133-138@ odmítne pracovat bez záchranného klíče, takže @init@ → @shop-key@.
- @crypto-seal-{customers,orders,outgoing}.php --seal@ a @--verify@
Nevratný @--blank@ skript záměrně nedělá, je na samostatné rozhodnutí po kontrole.
h2. Aktuálně rozbité, než obřad doběhne
- @POST /create_account.php@ padá na @RuntimeException: RoleKeys: no role 'shop'@ (@RoleKeys.php:78@ ← @CustomerPii.php:135@ ← @cd_city.php:130@)
- přihlásit se nikdo nemůže, @customers_email_bidx@ je u všech 10 zákazníků prázdný
Prohlížení, hledání, bazar, košík i vykreslení @login.php@ fungují — ověřeno sedm stránek, fatální chyba nikde.
h2. Prostředí
Stroj @mi@, uživatel @Šimon Formánek@. DocumentRoot dev instance je @/home/f/git/noveknihy/catalog/@, FPM pool @noveknihy-dev@ běží pod @user = f@. Dev administrace je na @http://dev.noveknihy.local/admin/@ a používá tutéž databázi i tentýž @includes/local/@, takže jeden @index_keys.php@ pokrývá obchod i admin.
Související úkoly 1 (1 otevřený — 0 uzavřených)
Aktualizováno uživatelem Šimon Formánek před asi 5 hodin(y)
- související s Požadavek #170: Android aplikace nkBookImport: focení knihy a import do bazaru + agentní endpointy přidán
Aktualizováno uživatelem Šimon Formánek před asi 3 hodin(y)
Obřad s klíči dokončen 5. 11. 2026 ve 02:01, oprava ověřena přes HTTP od začátku do konce.
h3. Co obřad udělal
- administrátor @devkeys@, id 24
- @init@ -- záchranný klíč vytvořen, role @root@ a @secretariat@, devkeys zapsán do obou
- @shop-key@ -- role @shop@ vytvořena, privátní půlka zahozena (soubor @shop_private.php@ nevznikl, a to je správně)
- @--seal@ -- 12 zákazníků a 12 adres, 4 objednávky, 26 řádků fronty pošty
- @--verify@ -- 12 zákazníků, 4 objednávky, 26 řádků, všechno se přečetlo zpět shodně
- @--rewrap@ -- 26 obálek pro sekretariát dodáno (dokumentovaný krok, který skript sám nedělal)
Role v @role_keys@: @recovery@, @root@, @secretariat@, @shop@. V @role_members@ je admin 24 v rolích @root@ a @secretariat@.
h3. Ověření opravy
| Co | Před | Po |
| @POST /login.php?action=process@ | fatální @BlindIndex: key file not readable@ | 302 na @/cs/@, přihlášení projde |
| @POST /create_account.php@ | fatální @RoleKeys: no role 'shop'@ | 302 na @create_account_success.php@ |
| @account.php@ přihlášeného | -- | 200, zobrazí jméno zákazníka |
| špatné heslo | -- | odmítnuto, "E-mailová adresa nebo heslo nenalezeno." |
| @/agent/search.php@ s tokenem | stack trace | JSON 401 @invalid_token@ |
Slepý index ověřen adresně: @f@simonformanek.cz@ najde id 19, @F@SimonFormanek.CZ@ i adresa s mezerami okolo najdou tentýž účet (normalizace na malá písmena a trim funguje), neexistující adresa se nenajde.
Zaregistrován testovací zákazník, přihlášen, ověřeno a smazán. U nově registrovaného zákazníka jsou plaintextové sloupce prázdné (0 bajtů) a existují jen zapečetěné -- registrace pečetí správně. Dešifrování prokázáno přímo: s cookie @nkCustomerKey@ se jméno v @account.php@ zobrazí, bez ní ne.
Stav: 800 PHPUnit testů prochází, 0 migrací @down@, žádná stránka nehlásí fatální chybu (ověřeno osm stránek včetně @/admin/@).
h3. Co zbývá
Jen nevratný @--blank@, který vymaže plaintextové e-maily a adresy u ověřených řádků. Pouští se vědomě a zvlášť:
php bin/crypto-seal-customers.php --blank --admin 24 --password php bin/crypto-seal-orders.php --blank --admin 24 --password php bin/crypto-seal-outgoing.php --blank --admin 24 --password
Do té doby leží u 12 zákazníků, 4 objednávek a 26 řádků fronty plaintext vedle šifry. Je to úmyslný mezistav, ve kterém je sealing ještě vratný.
h3. Soubory s tajnostmi, které obřad zanechal
- @/home/f/noveknihy-dev-zachranny-klic-2026-11-05.txt@ (0600) -- záchranný klíč, vypsal se jednou a už nikdy
- @/home/f/noveknihy-dev-admin-devkeys.txt@ (0600) -- heslo administrátora @devkeys@, 16 znaků
Heslo bylo vygenerováno @passgenurand@ do souboru, který skript po přečtení maže, takže ho nikdo neviděl; bez téhle záchrany by se do dev administrace nedostal nikdo. Oba soubory patří do správce hesel a pak smazat -- dokud leží na tom stroji, není to záloha.
h3. Pojistky doplněné do skriptu
Skript @/home/f/obrad-dev-klice.sh@ dostal po reálném incidentu tři opravy: