The result in one sentence
A booking SaaS for Swiss salons went from a prototype to a multi-tenant platform in five languages, and a security audit closed the flaws that let anyone on the internet become a platform administrator.
- Client: Termins.Online, a booking SaaS for salons and service businesses in Switzerland
- My role: product, frontend, Supabase backend, platform migration, security audit and fixes
- Timeline: March to July 2026
- Key result: 4 critical and 4 high tenant-isolation flaws fixed before launch
- Move to an owned backend: I took the schema and authentication to Supabase, with Postgres, RLS and Deno edge functions, to have real control over data, rules and costs.
- Two tenant paths, clearly separated: in the authenticated app, the business comes from the user's profile; in public booking, it comes from the salon's subdomain. Each path has its own rules.
- Audit before launch: before opening to customers, I ran a full OWASP-based audit, tracing data flow from the frontend to the functions and database policies, and listing only what was actually exploitable.
- 4 critical and 4 high flaws fixed, including full platform takeover from a single anonymous request
- Tenant isolation guaranteed by the database, not by the interface
- A platform in five languages, with local timezone, currency and payments
- Public booking per subdomain, with no marketplace standing between the salon and its customers
- Lower-risk items documented with a fix plan instead of forgotten
The context
In Switzerland, salons, barbershops and studios live on full calendars and customers who show up. The market wants a local product: prices in francs, deposits paid with TWINT, the Zurich timezone and service in German, French and Italian. Every business needed its own booking page, without depending on a marketplace.
The problem
The product was born fast, as a prototype, to validate the market. That delivered screens and flows in weeks, but the codebase had to move off a closed platform onto its own backend and, above all, hold data from many businesses in the same database without one seeing another. A prototype works in the demo; tenant isolation is what decides whether it can go to production.
The approach
The solution
A booking page per salon. Each business has its own subdomain, with services, staff, available slots and TWINT deposits. The customer books, gets a confirmation and a reminder by email and can cancel from the portal.Under the hood
React 18 with Vite, TanStack Query and shadcn/ui on the frontend, a custom i18n layer in German, French, Italian, Portuguese and English, dates always stored in UTC and formatted in each business's timezone. Supabase with Postgres, RLS, dozens of migrations and 18 Deno edge functions for public bookings, invites, campaigns, reminders and reports, with email through Resend.
The audit found the root cause behind almost everything: authorization trusted the client. Public signup copied the user's role from metadata sent by the browser, so a single request was enough to become a platform administrator. The profile update policy had no WITH CHECK, so a user could point their own account at another business and gain full access to it. Public functions accepted IDs without checking who owned them, allowing anyone to cancel appointments and read customer data from other salons. Fixes landed in the database and the functions: a fixed role at signup with privileged provisioning only on the server, WITH CHECK on policies, members restricted to staff, ownership checks in every public function and an open email relay closed.
Results
What I take from this project
Speed in the first version does not answer the question "who can write to my database?". That answer is still engineering work, and it has to come before the first customer, not after the first incident.

