← Back to projects
Product & SaaS

Termins.Online

A multi-tenant booking SaaS for Swiss salons and service businesses, in five languages, with public booking pages per subdomain, TWINT deposits and plan-based feature gating. Includes a full security audit that closed four critical tenant-isolation flaws.

Termins.Online
Type
Product & SaaS
Stack
SaaSReactSupabaseSecurity

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

        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

        • 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.

            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. Public booking page for a salon The business dashboard. Calendar, availability, staff invited by email, services by category, customers, email campaigns, PDF reports and reminder settings. Features and limits (staff, services, appointments) are unlocked by each business's plan, with per-customer overrides. Business calendar and dashboard Platform administration. A panel for the SaaS owner to create businesses, adjust plans and provide support, with a log of admin actions.

            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

            • 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

                    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.

Facing a similar challenge?

Tell me what you need to build, whether it is a product, an automation, or an AI system, and I will show you the shortest path to production.

More projects