Přeskočit obsah

11. Roadmap autentizace, autorizace a API perimetru

Pracovní roadmap k 3. 9. 2026. Tento dokument navazuje na 10. Stav autentizace a autorizace vlastního backendu. Slouží jako předávací dokument pro další běh Codexu nebo vývojáře: popisuje cílový stav, proč se změna dělá, a kontrolní seznam kroků, které se budou postupně škrtat.

Zatím nejde o implementační diff. Před úpravami kódu vždy nejdřív ověř aktuální stav repozitáře, protože v pracovním stromu mohou být rozdělané změny.

Cíl

Aplikace má mít bezpečně oddělené tři provozní zóny:

  1. Interní personální část pro administrátory, recepci, terapeuty a finance. Všichni uživatelé z tabulky users, včetně terapeutů, se budou přihlašovat pomocí passkey autentizace.
  2. Klientský portál pro klienty. Klienti budou mít vlastní session nad tabulkou clients, ne session nad tabulkou users. Přihlášení klienta bude e-mailem. SMS se použije při registraci/onboardingu k ověření platnosti telefonního čísla, ne jako druhý faktor každého přihlášení.
  3. Veřejné části dostupné bez přihlášení: veřejné nabídky, start klientského loginu, onboarding/platba/storno přes jednorázové tokeny a další landing stránky.

Základní pravidlo: klientská identita se nikdy nesmí dostat do interního JWT/session světa personálu. Personální /api nesmí přijmout klientskou cookie a veřejné /api/public nesmí omylem otevřít interní data.

Proč se to dělá

Současný backend má jeden typ přihlášené identity: users.id. AuthMiddleware ověří platnou cookie, ale obecně nekontroluje roli. Některé routy řeší oprávnění uvnitř controllerů, některé ne.

Kdyby klientský portál začal vydávat stejnou session jako personál, klient by mohl dostat technicky platnou session pro interní /api. To je špatný bezpečnostní model, i kdyby se část rizika následně lepila kontrolami v jednotlivých controllerech.

Současně má aplikace části, které mají být veřejné, a části, které mají být dostupné jen z intranetu/VPN. To nejde čistě vyřešit, dokud jsou všechny backend endpointy namíchané pod jedním perimetrem.

Cílové rozdělení rout

Potvrzené rozhodnutí k 3. 9. 2026: používáme prefixy /api, /api/public, /api/auth a /api/public/client_auth. Pokud se později zvolí dvě domény nebo dva buildy, prefixy mohou zůstat stejné; změní se jen proxy/deploy pravidla.

/api

Interní API pro personál.

  • Dostupnost: intranet/VPN/reverzní proxy podle nasazení.
  • Autentizace: personální passkey session.
  • Subjekt: user_id.
  • Middleware: personální middleware + explicitní role/oprávnění.
  • Nikdy nepřijímá klientskou session.

/api/public

Veřejné a portálové API pro klienty a terapeuty.

  • Dostupnost: internet.
  • Subjekt podle endpointu:
  • žádný subjekt pro veřejné čtení,
  • client_id pro klientsky autentifikované endpointy,
  • user_id pro terapeutsky autentifikované portálové endpointy, protože terapeuti jsou účty v tabulce users,
  • jednorázový token pro onboarding, platbu, storno a podobné odkazy.
  • Nikdy nevrací interní personální data.
  • Nikdy nepovoluje přístup k libovolnému klientovi podle client_id z requestu.
  • Nikdy nepovoluje přístup k libovolnému terapeutovi podle ID z requestu bez ověření passkey session a role/oprávnění.

/api/auth

Personální autentizace.

  • Passkey register/login challenge a callback.
  • Logout a refresh personální session.
  • Starý password/magic-link login postupně odstranit nebo ponechat jen jako výslovně povolený recovery/admin režim.

/api/public/client_auth

Klientská autentizace.

  • Start loginu e-mailem.
  • Vydání klientské session po ověření e-mailového kódu/linku.
  • Refresh/logout klientské session.
  • Onboarding token validation a onboarding completion.
  • SMS ověření telefonu při registraci/onboardingu.

Fáze 1: mapa endpointů

Stav k 3. 9. 2026: základní perimetr je připravený bez plošného přepisování frontendu. Interní /api zůstává personální surface za staff autentizací. První cílový veřejný klientský vstup je /api/public/client_auth. Dočasná kompatibilní cesta /api/functions/clientAuth zůstává zachovaná, dokud frontend nepřejde na explicitní /api/public volání. Pro Base44-like klientské volání existuje veřejný alias /api/public/functions/clientAuth. Stejný /api/public prefix bude používat i terapeutský portál; liší se až konkrétní autentizační/session middleware a subjekt endpointu.

Endpoint Typ Stav
/api/* staff-auth/intranet interní API, nadále chráněné personálním middleware
/api/auth/* staff-auth cílově passkey callback/refresh/logout pro personál
/api/public/client_auth public/client-auth/token-only připravená veřejná route pro klientskou autentizaci
/api/public/functions/clientAuth public/client-auth kompatibilita veřejný Base44-like alias pro klientské clientAuth akce
/api/public/therapist_auth public/staff-passkey cílová veřejná route pro passkey login terapeutů z tabulky users
/api/functions/clientAuth public kompatibilita dočasný alias kvůli existujícímu frontendu
/api/functions/{name} staff-auth kompatibilita dočasná Base44 kompatibilní vrstva pro interní funkce
/api/clients, /api/orders, /api/invoices, /api/paymentLogs staff-auth zatím interní, role se řeší ve Fázi 6

Označení typů:

  • public: bez přihlášení, ale bez interních dat,
  • client-auth: vyžaduje budoucí klientskou session,
  • staff-passkey: vyžaduje passkey autentizaci účtu z tabulky users, i když je endpoint dostupný přes veřejný /api/public portálový povrch,
  • staff-auth: vyžaduje personální session,
  • token-only: vyžaduje jednorázový token, typicky onboarding/platba/storno.

Cílové session modely

Potvrzené rozhodnutí k 3. 9. 2026: cílové cookies budou explicitně rozlišovat personál a klienty. Dnešní obecné auth_token, auth_refresh_token a auth_session jsou jen přechodný stav pro existující personální JWT autentizaci.

Potvrzené rozhodnutí k 3. 9. 2026: cílově budou session tabulky oddělené: staff_sessions pro personál a client_sessions pro klienty. Dnešní sessions tabulka je migrační zdroj pro existující personální session, ne cílový sdílený model.

Personál

Tabulka: users.

Cookies:

  • staff_auth_token
  • staff_auth_refresh_token
  • staff_auth_session

Cookie paths:

  • access token: /api
  • refresh token: /api/auth
  • session marker: /

JWT claims:

  • sub: ID z users
  • sub_type: staff
  • session_id
  • iat
  • exp

Session tabulka:

  • staff_sessions

Klient

Tabulka: clients.

Cookies:

  • client_auth_token
  • client_auth_refresh_token
  • client_auth_session

Cookie paths:

  • access token: /api/public
  • refresh token: /api/public/client_auth
  • session marker: /

JWT claims:

  • sub: ID z clients
  • sub_type: client
  • session_id
  • iat
  • exp

Session tabulka:

  • client_sessions

Platnost:

  • access token: 30 minut
  • refresh token: 7 dní
  • absolutní platnost session: 30 dní

Potvrzené rozhodnutí k 3. 9. 2026: klientská session použije stejné základní časové limity jako dnešní personální JWT model: 30 minut access token, 7 dní refresh token a 30 dní absolutní session. Důvod je jednodušší migrace a předvídatelné chování napříč aplikací. Citlivé změny jako změna e-mailu, telefonu nebo bezpečnostních údajů musí i tak vyžadovat nové MFA ověření a mohou zneplatnit existující klientské sessions.

Middleware:

  • ClientAuthMiddleware nastavuje jen client_id a client_session_id.
  • Personální controllery nikdy nečtou client_id.
  • Klientské controllery nikdy nečtou user_id pro rozhodování o přístupu.

Klientský login a ověření telefonu

Potvrzené rozhodnutí k 3. 9. 2026: klienti se nebudou běžně přihlašovat dvoufaktorově. Přihlášení klienta bude e-mailem, podobně jako dnešní existující clientAuth flow. SMS se použije při registraci/onboardingu pro ověření, že klient zadává platné telefonní číslo.

Cílový login klienta má být e-mailový challenge-based flow:

  1. Klient zadá e-mail.
  2. Server vytvoří login challenge a odešle 6místné jednorázové heslo e-mailem.
  3. Klient zadá jednorázové heslo.
  4. Server vydá klientskou session.

Důležité vlastnosti:

  • E-mailové jednorázové heslo je 6místné číslo, ne dlouhý link/hash v e-mailu.
  • Jednorázové heslo má krátkou expiraci, typicky 5-10 minut.
  • Jednorázové heslo je v DB uložené jen jako HMAC hash, ne jako plaintext.
  • Hash jednorázového hesla je unikátní, protože podle samotného zadaného kódu se dohledává login challenge a klient.
  • Pokusy jsou limitované.
  • Odpovědi nesmí jednoduše prozrazovat, jestli e-mail existuje.
  • Samotný výběr klienta podle client_id nesmí nahrazovat ověření e-mailové challenge.

Potvrzené rozhodnutí k 3. 9. 2026: SMS provider a základní integrační vzor vezmeme z projektu /home/uhlir/práca/mediasolution/cpmpk.

Referenční implementace:

  • SMS wrapper: /home/uhlir/práca/mediasolution/cpmpk/core/SmsManager/SmsManager.php
  • generátor kódů: /home/uhlir/práca/mediasolution/cpmpk/core/Codegenerator/SmsCodeGenerator.php
  • legacy odeslání: /home/uhlir/práca/mediasolution/cpmpk/app/tags/programs-cards/00-sms/send-sms.php
  • legacy ověření: /home/uhlir/práca/mediasolution/cpmpk/app/tags/programs-cards/00-sms/verify-sms-code.php
  • legacy migrace logu: /home/uhlir/práca/mediasolution/cpmpk/app/migrations/2025-06-27.sql

Provider:

  • endpoint: https://api.smsmngr.com/v2/message
  • konfigurace: SMSMANAGER_API_KEY
  • payload: {"to":[{"phone_number":"+420..."}],"body":"..."}
  • úspěch: přítomnost accepted[0].message_id
  • chyba nízkého kreditu v referenci: rejected[0].code === 78

Pro NEO se nepřenáší legacy tag architektura ani plaintext uložení kódu. Přenést se má jen provider wrapper a ergonomie kódu, ale upravené pro Slim:

  • SmsService v backend/src/Services,
  • konfigurace přes .env, bez zápisu do .env,
  • normalizace telefonu na mezinárodní tvar,
  • bezpečné timeouty a zpracování curl chyb,
  • jednotný návrat {success, provider_message_id, error_code, raw_response},
  • logování bez ukládání plaintext OTP do aplikačních logů.

SMS OTP pro NEO klientskou registraci/onboarding musí zůstat součástí challenge modelu:

  • kód ukládat hashovaně,
  • navázat kód na konkrétní klientskou registrační/onboarding challenge,
  • omezit opakované odeslání, minimálně cooldown 5 minut podle vzoru cpmpk,
  • ověřovací platnost držet 5-10 minut,
  • po úspěšném ověření označit challenge faktor jako splněný.

Onboarding je zvláštní případ:

  • E-mailový onboarding link může být jednorázový token.
  • Pokud klient ještě nemá ověřený telefon, telefon doplní v onboardingu.
  • Před uložením souhlasů a dokončením profilu se ověří SMS OTP na nově zadaný telefon.
  • Po dokončení se onboarding token označí jako použitý.

Passkey pro uživatele z users

Passkey zavést jako primární autentizaci všech účtů v tabulce users: administrátorů, recepce, terapeutů, financí a dalších interních rolí.

Potvrzené rozhodnutí k 3. 9. 2026: nepíšeme lokální WebAuthn implementaci přímo do NEO backendu. Použije se existující centrální auth server /home/uhlir/práca/mediasolution/neo-auth-passkey, který už používá Petrovo CMS v /home/uhlir/práca/mediasolution/cms_git.

Referenční implementace:

  • server: /home/uhlir/práca/mediasolution/neo-auth-passkey
  • CMS klient: /home/uhlir/práca/mediasolution/cms_git/app/Integrations/Auth/AuthClient.php
  • CMS dokumentace: /home/uhlir/práca/mediasolution/cms_git/docs/app/admin/auth-passkey-cms.md
  • WebAuthn knihovna na serveru: web-auth/webauthn-lib ^5.3

NEO aplikace má dělat totéž co CMS:

  • přesměrovat uživatele z tabulky users na centrální auth server,
  • přijmout callback s JWT tokenem,
  • ověřit RS256 JWT přes veřejný klíč auth serveru,
  • spárovat identitu podle e-mailu na lokální users.email,
  • založit vlastní lokální staff session,
  • držet autorizaci a role lokálně v NEO aplikaci.

Auth server dál řeší:

  • ukládání WebAuthn credentialů,
  • registraci passkey přes pozvánku,
  • login challenge,
  • validaci signature, origin, RP ID, challenge, credential ID a sign counter,
  • vlastní rate limiting a expiraci challenge.

NEO bude potřebovat úpravy konfigurace a drobné úpravy centrálního auth serveru podle cílové domény/redirectů, ale základ je už existující auth-passkey projekt.

Recovery:

Potvrzené rozhodnutí k 3. 9. 2026: zatím nebudeme dělat samoobslužný recovery flow pro personál. Praktický model je:

  • každý účet v tabulce users má mít ideálně aspoň dvě passkeys,
  • při ztrátě přístupu owner/admin ověří člověka mimo aplikaci,
  • owner/admin pošle novou passkey pozvánku přes centrální auth server,
  • staré ztracené passkeys se podle potřeby zneplatní na auth serveru,
  • událost musí být auditovaná.

Poznámka: magic link není MFA. Nahrazuje heslo přístupem do e-mailu. Pro účty z tabulky users se nemá používat jako běžný fallback, protože by snižoval bezpečnost passkey modelu.

Autorizace interního API

Interní /api musí mít výchozí stav zakázáno.

Potřebujeme zavést jeden z modelů:

  • role middleware na route skupinách,
  • permission middleware nad schopnostmi,
  • kombinace role middleware + jemnějších kontrol v controlleru.

Minimální role/oprávnění k rozhodnutí:

  • owner/company_admin,
  • recepce nebo admin klientské části,
  • terapeut,
  • finance,
  • globální admin/superadmin, pokud existuje.

První endpointy k uzamčení:

  • /api/clients
  • /api/orders
  • /api/{invoices|paymentLogs}
  • /api/apps/{appId}/entities/{Invoice|PaymentLog}
  • /api/clientCategories
  • /api/notifications
  • /api/users
  • /api/therapists

Veřejný/intranet perimetr

Cílově musí být reverzní proxy schopná rozlišit:

  • veřejné cesty: frontend klientského portálu, landing stránky, /api/public,
  • intranetové cesty: administrační frontend a /api.

Možné varianty:

  1. Dvě domény a dva buildy.
  2. Jeden build, ale proxy dělení podle cesty.

Pragmatická varianta pro tento projekt je zatím druhá možnost: jeden build, ale jasné cesty a proxy pravidla. Dlouhodobě mohou být dvě domény čistší.

Stav k 3. 9. 2026

  • Existuje personální JWT cookie autentizace nad users.
  • Existuje AuthSessionService s cookie auth_token, auth_refresh_token, auth_session.
  • AuthMiddleware ověřuje session, ale obecně nekontroluje roli.
  • docs/10-backend-autorizace.md popisuje rizika současného stavu.
  • V pracovním stromu mohou být rozdělané změny kolem sendNotification, clientAuth, onboarding tokenů a DB schématu. Před další prací vždy zkontroluj git status a aktuální diff.

Roadmap checklist

Fáze 0: Stabilizace rozhodnutí

  • Potvrdit názvy route prefixů: /api, /api/public, případně /api/auth.
  • Potvrdit názvy cookies pro personál a klienty.
  • Rozhodnout, jestli personální session zůstane v sessions, nebo vznikne staff_sessions.
  • Vybrat WebAuthn/passkey PHP knihovnu.
  • Vybrat SMS provider nebo potvrdit existující provider v projektu.
  • Rozhodnout, jak dlouho platí klientská session a refresh token.
  • Rozhodnout, jak bude fungovat recovery pro personál bez passkey.

Fáze 1: API perimetr bez změny chování

  • Sepsat cílovou mapu endpointů: interní /api vs veřejné /api/public.
  • Připravit nové route skupiny bez přesunu logiky.
  • Označit endpointy podle typu: public, client-auth, staff-auth, token-only.
  • Přidat integrační testy, že /api bez staff session vrací 401.
  • Přidat integrační testy, že /api/public nevyžaduje staff session tam, kde nemá.
  • Zachovat dočasný veřejný alias /api/functions/clientAuth, aby současný frontend dál fungoval při postupném přechodu na /api/public/client_auth.

Fáze 2: Oddělená klientská session

  • Přidat tabulku client_sessions.
  • Přidat ClientSessionService.
  • Přidat ClientAuthMiddleware.
  • Přidat klientské cookies s oddělenými názvy a path ideálně /api/public.
  • Přidat refresh/logout pro klienta.
  • Upravit frontend ClientAuthContext, aby nepoužíval lokální důvěru v localStorage jako autentizaci.
  • Ověřit, že klientská cookie nefunguje na interním /api.
  • Ověřit, že personální cookie nefunguje jako klientská identita.

Fáze 3: Klientský e-mail login a SMS ověření telefonu

  • Navrhnout tabulky pro login challenges a OTP pokusy.
  • Ukládat e-mailová jednorázová hesla hashovaně.
  • Implementovat e-mailový login challenge pro klienta.
  • Přidat samostatné registrační akce request_registration_otp a verify_registration_otp vedle login-only request_otp/verify_otp.
  • Implementovat SMS OTP provider pro ověření telefonu při registraci/onboardingu.
  • Přidat rate limiting podle IP a kontaktu.
  • Sjednotit chybové odpovědi proti enumeraci klientů.
  • Vydat klientskou session po úspěšném ověření e-mailového jednorázového hesla.
  • Přidat testy pro ověření přes hashovaný 6místný kód a vydání klientské session.

Fáze 4: Onboarding přes jednorázový token

Stav k 4. 9. 2026: onboarding pozvánky používají jednorázový token v URL. Token se validuje na explicitní veřejné route /api/public/client_auth/onboarding. Dokončení onboardingu na této route i na dočasné kompatibilní clientAuth akci vyžaduje SMS ověřovací kód pro stejný client_id a telefon. Frontend už neukládá právně významné souhlasy přes běžný Client.update.

  • Vytvořit jednorázové onboarding tokeny při odeslání pozvánky.
  • Posílat v e-mailu URL s tokenem, ne jen client_id.
  • Validovat token na /api/public/client_auth/onboarding.
  • Zneplatnit starší tokeny při opětovném odeslání pozvánky.
  • Označit token jako použitý po dokončení onboardingu.
  • Vyžadovat SMS ověření telefonu před uložením souhlasů.
  • Přestat ukládat právně významné souhlasy přes běžný Client.update z frontendu.

Fáze 5: Passkey pro účty z tabulky users

Doplněné rozhodnutí k 9. 9. 2026: základní admin/recovery flow bude postavené na stejném mechanismu jako registrace passkey přes pozvánku. Administrace musí umět odeslat passkey registrační pozvánku při vytvoření uživatele a také později z detailu uživatele tlačítkem pro opětovné odeslání. Při ztrátě všech passkeys admin/owner nejdřív ověří identitu člověka mimo aplikaci, potom pošle novou pozvánku a podle potřeby zneplatní staré passkeys na centrálním auth serveru. Recovery akce musí být auditované.

Doplněné rozhodnutí k 9. 9. 2026: bootstrap úplně prvního administrátora a nouzové recovery bez přihlášeného admina se řeší CLI commandem spuštěným na serveru, ne veřejnou URL. Command pošle passkey pozvánku jen existujícímu aktivnímu admin/owner/company_admin účtu a akci zapíše do auditu jako auth.passkeyBootstrapInvite.

cd /var/www/backend
php bin/send-passkey-invite.php admin@example.com
php bin/send-passkey-invite.php admin@example.com --revoke-existing

Doplněné rozhodnutí k 9. 9. 2026: stav interního účtu řídí i passkey přístup. Při vytvoření aktivního uživatele se odešle passkey pozvánka, při vytvoření neaktivního uživatele ne. Při deaktivaci uživatele se zneplatní jeho passkeys na centrálním auth serveru. Při opětovné aktivaci se odešle nová registrační pozvánka. Při smazání uživatele se před odstraněním lokálního účtu zneplatní jeho passkeys.

  • Použít centrální tabulku passkeys v neo-auth-passkey místo lokální tabulky user_passkeys v NEO.
  • Použít centrální tabulku passkey_challenges v neo-auth-passkey pro WebAuthn challenges.
  • Implementovat registraci passkey přes centrální invite flow.
  • Implementovat passkey login přes centrální auth server.
  • Napojit passkey login na personální session přes /api/auth/passkey/callback.
  • Přidat UI pro správu vlastních passkeys.
  • Přidat správu passkey pozvánek v administraci: odeslání při vytvoření uživatele a opětovné odeslání z detailu uživatele.
  • Použít správu passkey pozvánek jako základní admin/recovery flow pro uživatele, kteří ztratili všechny passkeys; součástí musí být audit a možnost zneplatnit staré passkeys na centrálním auth serveru.
  • Vypnout nebo omezit starý password/magic-link login.
  • Odstranit user enumeration z magic-link endpointu, pokud zůstane.
  • Přihlášení na passkey serveru má vypadat stejně jako portál, tedy použít podobné CSS.
  • Přidat CLI command pro bootstrap prvního administrátora a nouzové recovery bez přihlášeného admina.
  • Navázat passkey pozvánky a zneplatnění passkeys na aktivaci, deaktivaci a smazání interního uživatele.

Fáze 6: Autorizace interních rout

  • Rozšířit AuthMiddleware nebo přidat nový StaffAuthMiddleware.
  • Zavést explicitní požadavky na role/oprávnění na route skupinách.
  • Uzamknout /api/clients.
  • Uzamknout /api/orders.
  • Uzamknout faktury a payment logy.
  • Uzamknout nastavení, uživatele a terapeuty.
  • Přidat testy pro běžného terapeuta, recepci, finance a admina.

Fáze 7: Proxy a nasazení

  • Připravit pravidla pro veřejné cesty.
  • Připravit pravidla pro intranetové cesty.
  • Zkontrolovat cookie Secure, HttpOnly, SameSite a Path.
  • Nastavit produkční APP_URL na veřejnou doménu, ne localhost.
  • Odstranit nebo zamknout /api/test/tokens.
  • Zdokumentovat finální routy pro infrastrukturu.

Fáze 8: Úklid starého Base44 kompatibilního povrchu

  • Zmapovat všechna base44.functions.invoke(...) ve frontendu.
  • Nahradit klientské volání explicitními /api/public endpointy.
  • Nahradit interní volání explicitními /api endpointy.
  • Po přepnutí frontendu odstranit veřejný alias /api/functions/clientAuth.
  • Zrušit nebo výrazně omezit obecný /api/functions/{name} dispatcher.
  • Odstranit tvrdě zadané Base44 app ID z rout, pokud už nebude potřeba.

Definice hotovo

Cíl je splněný, až platí:

  • klient se umí přihlásit přes e-mail a dostane jen klientskou session,
  • registrace/onboarding klienta ověří telefon přes SMS,
  • klientská session neotevře žádné interní /api,
  • všichni uživatelé z tabulky users, včetně terapeutů, se umí přihlásit passkey autentizací,
  • interní routy mají explicitní role/oprávnění,
  • veřejné routy jsou pod /api/public,
  • onboarding a podobné odkazy používají jednorázové tokeny,
  • proxy/infrastruktura umí oddělit veřejný portál od intranetové administrace,
  • testy pokrývají oddělení staff/client identit a nejrizikovější autorizační cesty.