Safe to demo, not yet safe to grow.

The booking flow works and the calendar code is sound. Two problems should be fixed before the next release: any signed-in customer can read and cancel every other customer’s bookings, and the key that bypasses the database’s access rules ships in the browser.

Three more should follow soon after: double bookings, an uncapped model call and a booking page with no tests. None of it needs a rewrite.

Five findings, ranked by severity.

  1. C1criticalAUTH AND DATA ACCESS

    Row-level security is off on the bookings table.

    FILE
    supabase/migrations/20260611_bookings.sql
    RISK
    The app’s public key can read and change every row, so any signed-in customer can see other customers’ names, phone numbers and appointments, and cancel them.
    FIX
    Turn row-level security on, with policies that let customers see, create and change only their own bookings and let staff see their own salon’s. A test signs in as a second customer and expects nothing back. The change is below.
  2. C2criticalSECRETS

    The service-role key ships in the browser bundle.

    FILE
    src/integrations/supabase/admin.ts
    RISK
    That key skips row-level security entirely. Anyone who opens the site’s JavaScript can copy it and act as the database administrator.
    FIX
    Rotate the key today. Move the two admin calls into an Edge Function that reads the key from its environment, and search the git history for older copies.
  3. W1warnLOGIC BUGS

    Two customers can book the same slot.

    FILE
    supabase/functions/create-booking/index.ts
    RISK
    The function checks that a slot is free, then inserts, in two separate queries. Two requests in the same moment both succeed.
    FIX
    A unique constraint on the salon, the chair and the start time, so the database refuses the second booking, and a clear message in the app offering the next free slot.
  4. W2warnAI-API SPEND

    The reminder writer retries with no cap.

    FILE
    supabase/functions/send-reminder/index.ts
    RISK
    Each reminder is written by a model API call inside a retry loop with no timeout and no limit. A slow API turns one reminder into an open-ended bill.
    FIX
    A timeout on the call, at most two retries with backoff, and a daily budget on the key. A fixed template covers the reminder when the model is unavailable.
  5. W3warnTESTS AND STRUCTURE

    Nothing tests booking, rescheduling or cancelling.

    FILE
    src/pages/Book.tsx
    RISK
    The calendar, the form and the confirmation live in one 900-line component, and every change is checked by clicking through it.
    FIX
    End-to-end tests for book, reschedule and cancel first, then split the page into three components. The tests are what make the fixes above safe to ship.

The fix for C1, as a change.

A new migration: row-level security on, and one policy for each thing a customer or a member of staff needs to do. Anything no policy allows stays closed.

supabase/migrations/20260923_bookings_rls.sql0 lines removed, 22 lines added.row-level security
  1. added alter table bookings
  2. added enable row level security;
  3. added
  4. added create policy read_own
  5. added on bookings for select
  6. added using (customer_id = auth.uid());
  7. added
  8. added create policy book_own
  9. added on bookings for insert
  10. added with check (customer_id = auth.uid());
  11. added
  12. added create policy change_own
  13. added on bookings for update
  14. added using (customer_id = auth.uid())
  15. added with check (customer_id = auth.uid());
  16. added
  17. added create policy staff_read_salon
  18. added on bookings for select
  19. added using (salon_id in (
  20. added select salon_id from staff
  21. added where user_id = auth.uid()
  22. added ));

Two parts that are sound as they are.

  1. ok

    Sign-in.

    FILE
    src/integrations/supabase/client.ts
    WHY
    Supabase Auth with email links, set up correctly, sessions refreshed. Nothing to change.
  2. ok

    The calendar components.

    FILE
    src/components/calendar/
    WHY
    Small, typed and reused across three pages. The refactor in W3 keeps them as they are.

The order to fix them in, starting today.

  1. TODAY

    Rotate the service-role key and turn row-level security on (C2, C1).
  2. THIS WEEK

    The unique slot constraint and the cap on reminder retries (W1, W2).
  3. THEN

    Tests on the booking flow, and only then the split of the booking page (W3).

The same report, for your repository.

You grant read-only access to one repository, and the written review arrives within 48 hours of access.