Rezervace a kalendářní události¶
Entity: Booking, CalendarEvent (tabulky bookings, calendar_events)
Funkce: clientBooking — akce create_booking, cancel_booking, confirm_payment, get_free_slots…
Prověřeno proti commitu: f1da785 (21. 9. 2026)
Testy: backend/tests/Feature/Audit/RezervaceAuditTest.php
Routy¶
| Routa | Perimetr | Ochrana | Controller |
|---|---|---|---|
GET/POST/PUT/DELETE /api/{bookings\|calendarEvents} |
interní | oprávnění calendar |
EntityCrudController CRUD |
GET /api/public/calendarEvents |
veřejný | žádná v routě, rozhoduje controller | EntityCrudController::publicList |
PUT /api/public/{calendarEvents\|bookings}/{id} |
veřejný | klientská session | EntityCrudController::publicUpdate |
POST /api/public/clientBooking |
veřejný | žádná | FunctionsController::clientBooking |
Veřejný list (listRows, EntityCrudController.php:550) se chová podle cookies:
se staff session vrací všechny události firmy, s klientskou session a filtrem
client_id události klienta, bez session 401.
Kdo to volá z Reactu¶
- administrace:
pages/Calendar.jsx,components/clients/ClientBookingsTab.jsx,components/calendar/PaymentMethodDialog.jsx, interní/api/..., - klientský portál (
/client):pages/ClientDashboard.jsx,components/client/BookingRow.jsx(storno, demo platba), veřejné/api/public/...aclientBooking, - portál terapeuta (
/TherapistPortal):components/therapist/TherapistCalendarView.jsx,TherapistEventDialog.jsx,TherapistExportPanel.jsx, veřejné/api/public/...se staff session.
Nálezy¶
REZ-01 — Kdokoli bez přihlášení zruší libovolnou rezervaci (kritická)¶
Stav: otevřený
Test: REZ-01: anonymní cancel_booking nezruší cizí rezervaci
Ověřený fakt — FunctionsController.php:2574 bookingCancel() načte rezervaci jen podle
booking_id z requestu. Kontrola vlastníka (:2588) proběhne jen pro $userId > 0. Na veřejné
routě routes.php:87 není middleware, user_id je tedy 0 a kontrola se přeskočí.
Test potvrdil, že anonymní požadavek změní stav rezervace na excused.
Dopad — ID rezervací jdou po sobě, takže jde postupně zrušit všechny rezervace.
Stejný vzor (if ($userId > 0)) má i confirm_payment (:2701) a akce na :2871, ty spadají pod platby.
REZ-02 — Kdokoli bez přihlášení založí rezervaci na cizího klienta (kritická)¶
Stav: otevřený
Test: REZ-02: anonymní create_booking nezaloží rezervaci na cizího klienta
Ověřený fakt — FunctionsController.php:2479 bookingCreate() vezme client_id z těla
requestu a nekontroluje klientskou session. Test potvrdil vznik rezervace bez přihlášení.
Dopad — rezervace jménem libovolného klienta, zablokování termínů, notifikace klientovi.
REZ-03 — Klient vidí interní poznámku týmu (vysoká)¶
Stav: otevřený
Test: REZ-03: klient nedostane interní poznámku u vlastní události
Ověřený fakt — veřejný list vrací SELECT * (EntityCrudController.php:614), tedy i sloupec
notes. Definice entity (react_dev/base44/entities/CalendarEvent.jsonc) ho popisuje jako
„Interní poznámka (viditelná pouze pro tým NEO, ne pro klienta)“.
Dopad — klient v odpovědi API vidí, co si o něm tým zapsal. U terapeutického zařízení jde pravděpodobně o citlivé údaje.
REZ-04 — Klient si sám nastaví stav termínu (střední)¶
Stav: otevřený
Test: REZ-04: klient si přes PUT nenastaví libovolný stav události
Ověřený fakt — publicUpdate() (EntityCrudController.php:702) dovolí klientovi zapsat
status, payment_status a payment_method bez kontroly hodnoty. UI to používá
(BookingRow.jsx nastavuje cancelled, resp. paid), ale backend přijme cokoli, třeba completed.
Dopad — storno mimo pravidla lhůt v bookingCancel. Zápis payment_status patří
k nedořešeným platbám.
REZ-05 — Terapeut vidí termíny celé firmy (vysoká)¶
Stav: otevřený
Test: REZ-05: terapeut na veřejné routě nevidí události jiného terapeuta
Ověřený fakt — staffCompanyIdFromSession() (EntityCrudController.php:870) pustí každou
staff roli včetně terapeuta a vrátí všechny události firmy. Filtr therapist_id si určuje
frontend (TherapistCalendarView.jsx), backend ho nevynucuje.
Dopad — dokumentace portálu (pages/therapist-portal.md) slibuje „terapeut pracuje jen
s vlastními daty“. Přes API vidí rezervace, klienty a interní poznámky všech kolegů.
REZ-06 — Terapeut nemůže z portálu označit termín jako proběhlý (nízká, funkční)¶
Stav: otevřený
Test: REZ-06: terapeut označí vlastní termín jako proběhlý z portálu
Ověřený fakt — TherapistEventDialog.jsx volá na /TherapistPortal PUT /api/public/calendarEvents/{id}.
Routa (routes.php:102) vyžaduje klientskou session, terapeut má staff session, takže dostane 401.
Záložní pokus o veřejnou routu dělá base44Client.js jen u čtení.
Dopad — funkce v portálu nefunguje. Oprava nesmí jít cestou „pustit staff session na
publicUpdate“, jinak se otevře REZ-05 i pro zápis.
Mimo rozsah¶
Platby (confirm_payment, zápis payment_status) se kontrolují až po dokončení platebního flow.