Přeskočit obsah

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/... a clientBooking,
  • 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.