14. TODO: striktní API perimetr¶
Stav k 24. 9. 2026, commit
83be60c. Úkoly pro backend, které je potřeba dokončit před spuštěním. Navazuje na 11. Roadmap autentizace a 12. Technologický dluh.Oddíl A jsou konkrétní díry, které jdou opravit hned a samostatně. Oddíly B–D jsou změna struktury
routes.php, po které už podobná díra nevznikne omylem.
Proč¶
Práva jsou v backend/src/routes.php vyjmenovaná u každé skupiny, to je v pořádku.
Výchozí zákaz to ale není: Slim sám nic nezakazuje a routa bez ->add($auth(...))
je otevřená. Zákaz dnes drží jen tím, že na ->add nikdo nezapomene. Oddíl A ukazuje,
že se to už stalo.
Cíl: routa pod /api je chráněná tím, že je pod /api, ne tím, že má správný řádek
navíc. Veřejná routa musí být veřejná výslovně a dohledatelně.
A. Otevřené díry — opravit hned¶
A1. Zaměstnanec si sám nastaví roli admin (kritické)¶
Ověřený fakt — PUT /api/user/{id} → UserController::update(), route má jen
$auth($authenticated).
Uživatel, který není admin, smí upravit vlastní účet (authUser.id === id z URL).
Z těla požadavku se ale bere i role a is_active a kontroluje se jen, že je role ze
seznamu povolených — ne, kdo ji mění. Terapeut nebo recepce si tak jedním požadavkem
{"role": "admin"} nastaví admina. AuthMiddleware::getAppRole() pak z role = admin
udělá plného admina se všemi oprávněními.
- Pro ne-admina ignorovat nebo odmítnout
roleais_active(a zvážit iemail, podle kterého se páruje passkey login). - Test: terapeut →
PUT /api/user/{vlastní id}srole=admin→ role se nezmění. - Zjistit, jestli frontend tuhle routu vůbec volá. Ve
react_dev/srcjsem volání nenašel; pokud se nepoužívá, smazat.
A2. POST /api/user bez přihlášení založí firmu a admina (střední)¶
Ověřený fakt — skupina /api/user, $group->post('', UserController::create)
nemá žádný middleware. create() založí novou firmu a v ní uživatele s rolí admin
/ company_admin. Passkey callback páruje účet jen podle e-mailu
(WHERE u.email = ? AND is_active = 1) a kontroluje iss i aud tokenu.
Ověřený fakt — NEO má vlastní passkey server: .env na mswork má
AUTH_URL=https://auth.neocentrum.cz, AUTH_JWT_ISSUER=auth.neocentrum.cz,
AUTH_JWT_AUDIENCE=neo-cal-app. Identita ze sdíleného auth.mscms.cz (Petrovo CMS)
v NEO neprojde. Pozvánky na auth.neocentrum.cz posílá jen admin
(/api/auth/passkey/invite) nebo CLI.
Odvozeno — heslo je prázdné a přihlášení heslem je vypnuté
(STAFF_PASSWORD_LOGIN_ENABLED), takže založeným účtem se nikdo nepřihlásí. Zůstává
zakládání firem a adminů komukoli a obsazení e-mailu budoucího zaměstnance předem
(email je unikátní) — pak mu nepůjde vytvořit účet.
- Smazat. Je to zbytek z Fachmana; zakládání uživatelů dělá
POST /api/users(createCompanyUser, za$admins).
A3. POST /api/register bez přihlášení založí firmu a admina (vysoká)¶
Ověřený fakt — AuthController::register(), bez middleware a bez přepínače.
Totéž co A2, jen s heslem. Přihlášení heslem je dnes vypnuté, ale stačí ho zapnout
v .env (třeba kvůli ladění) a registrace je otevřená komukoli.
- Smazat, nebo dát za stejný přepínač jako login heslem. NEO je jedna firma, samoobslužná registrace firem nedává smysl.
A4. Testovací a zbytkové routy (střední)¶
Ověřený fakt — bez middleware:
| Routa | Co dělá |
|---|---|
POST /api/sendmail |
pošle mail na natvrdo zadanou gmailovou adresu; opakovaným voláním se dá zahltit schránka a vyčerpat kvóta mailového poskytovatele |
GET /api/test-ecomail |
vypíše seznamy z Ecomailu přes print_r |
GET /api/hello |
testovací |
POST /api/analytics/track/batch |
prázdný stub z Base44 |
- Všechny čtyři smazat (případně stub analytics nechat, pokud ho frontend volá,
ale pod
/api/public). -
MailControllermá natvrdoSECRET_JWT_KEY_CHANGE_ME— s routou smazat i jeho.
A5. Zbytky z Fachmana v perimetru (nízká, ale k rozhodnutí)¶
Ověřený fakt — veřejné bez middleware: /api/inquiry/submitInquiry,
/api/fnct/idokladCallback, /api/fnct/idokladAuthorize, /api/images/*,
/api/billing/webhook (Stripe). Za $admins: /api/companies, /api/referral,
/api/subscription, /api/billing/*.
- U každé rozhodnout: používá ji NEO? Když ne, smazat. Když ano, přesunout podle oddílu B.
B. Cílová struktura: tři skupiny¶
| Skupina | Kdo volá | Middleware skupiny | Dostupnost |
|---|---|---|---|
/api/auth |
nepřihlášený personál | žádný, jen přihlašovací routy | intranet/VPN |
/api |
personál | staff session vždy ($auth($authenticated) na skupině), podskupiny přidávají oprávnění |
intranet/VPN |
/api/public |
internet: klient, terapeut, token, webhook | povinný režim přístupu u každé routy (oddíl C) | internet |
Ve Slimu to znamená jeden $app->group('/api', ...)->add($auth($authenticated)),
do kterého se přesunou všechny dnešní staff skupiny. Oprávnění ($clientsAccess,
$admins …) zůstávají na podskupinách jako dnes. Zapomenuté ->add pak znamená
nanejvýš chybějící oprávnění, ne otevřenou routu.
Pozor: skupina /api/auth a /api/public se musí registrovat mimo skupinu /api,
i když mají stejný prefix URL. Ve Slimu platí middleware skupiny jen pro routy
registrované uvnitř ní, ne podle prefixu URL.
Co se musí přesunout¶
Do /api/auth (bez session): /api/login, /api/login/request,
/api/loginTokens/{token}, /api/auth/refresh, /api/auth/logout,
/api/auth/passkey/login, /api/auth/passkey/callback, /api/invitations/{token},
/api/invitations/{token}/complete. Část z nich už prefix /api/auth má, ostatní
změní URL — upravit i frontend.
Do /api/public (volá je internet): vše z A5, co zůstane — webhooky, callbacky,
obrázky, submitInquiry. URL webhooků a callbacků je nastavená u poskytovatele
(Stripe, iDoklad, Comgate) — po přesunu ji tam změnit, jinak přestanou chodit platby.
Do /api (staff): /api/getUser, /api/updateMe, /api/user-info, /api/me,
/api/auth/passkey/manage, /api/auth/passkey/invite, /api/upload,
/api/functions/* a všechny $app->group(...) od /api/users níž.
K rozhodnutí: /api/cron/functions/reminderDispatcher. Od commitu 83be60c
existuje backend/bin/run-reminders.php. Odvozeno: pokud cron běží přes CLI, HTTP
routa je zbytečná a má zmizet.
C. /api/public: režim přístupu povinně u každé routy¶
/api/public neznamená „bez přihlášení“, ale „dostupné z internetu“. Podle roadmapy 11
tam chodí:
public— bez identity (veřejný rezervační portál, číselníky),client— klientská session (ClientAuthMiddleware),staff— passkey session personálu (terapeutský portál),token— jednorázový token v URL (onboarding, platba, storno, doklad),webhook— volá poskytovatel, ověřuje se podpisem.
Jeden middleware na celou skupinu tu nestačí. Přísnost znamená, že každá routa musí režim výslovně deklarovat a routa bez deklarace neprojde. Dvě cesty, stačí jedna:
- Middleware skupiny
/api/public, který čte deklaraci routy (např. argument->setArgument('access', 'client')) a routu bez ní odmítne 500 + log. - Test z oddílu D.
Ověřený fakt — dnešní stav /api/public bez middleware, kde kontrola musí být
uvnitř controlleru (a kde jsou otevřené nálezy auditu REZ/SET):
POST /api/public/{clientBooking|comgatePayment}PUT /api/public/{calendarEvents|bookings}/{id}(publicUpdate)GET /api/public/{18 entit}(publicList)GET /api/public/therapists,GET /api/public/clientCategoriesPOST /api/public/client_auth,/onboarding,/refresh,/logout
U každé z nich doplnit do tabulky níže, jaký má režim a čím se ověřuje.
| Routa | Režim | Čím se ověřuje |
|---|---|---|
| … |
D. Pojistka: test perimetru¶
Aktuální stav: existuje backend/tests/Feature/ApiPerimeterTest.php. Ten ověřuje
vybrané chování perimetru integračně (např. interní /api/clients bez session vrací 401,
client session neplatí na interních routách, /api/test/tokens je za admin auth, několik
terapeutických/public rout funguje pro přihlášeného terapeuta). Je to užitečná sada, ale
není to plošná pojistka proti nově přidané špatně zařazené routě.
Ještě chybí samostatný route-collector test v backend/tests/Feature/Audit/, který projde
všechny registrované routy Slim aplikace ($app->getRouteCollector()->getRoutes()) a
spadne, když:
- routa pod
/api(mimo/api/autha/api/public) nemá staffAuthMiddleware, - routa pod
/api/publicnemá deklarovaný režim přístupu, - routa není pod žádným z prefixů
/api/auth,/api,/api/public, - routa pod
/api/authnení na explicitním seznamu přihlašovacích rout.
Tenhle test hlídá, aby nová routa nevznikla omylem otevřená. Jestli daná role smí na daný endpoint, nehlídá — to je audit po zdrojích (Přehled auditu).
- Částečný behaviorální test perimetru:
backend/tests/Feature/ApiPerimeterTest.php. - Dopsat plošný route-collector test. Nejdřív ho pustit proti dnešním routám — musí najít všechno z oddílu A.
Checklist¶
- A1 — ne-admin nemění
role/is_active, test - A2 — smazat
POST /api/user - A3 — smazat nebo zamknout
POST /api/register - A4 — smazat
sendmail,test-ecomail,hello,analytics/track/batch,MailController - A5 — rozhodnout o zbytcích z Fachmana
- B — skupina
/apise staff session na skupině - B — skupina
/api/auth, přesun přihlašovacích rout, úprava frontendu - B — přesun webhooků/callbacků pod
/api/public, změna URL u poskytovatelů - B — rozhodnout o
/api/cron/functions - C — deklarace režimu u každé
/api/publicrouty, vyplněná tabulka - D — plošný route-collector test perimetru, zelený
- Aktualizovat pravidla proxy (Deployment a routing) na tři prefixy
Definice hotovo¶
- Žádná routa pod
/api(mimo/api/auth) neprojde bez staff session, a to díky skupině, ne díky řádku u routy. - Každá routa pod
/api/publicmá deklarovaný režim a tabulka v oddílu C je vyplněná. - Test perimetru je zelený a běží v CI / před nasazením.
- Na produkci nejsou testovací ani nepoužívané Fachman routy.