Skip to content

Authentication

Perfxcel uses Supabase Auth for the admin dashboard. The public-facing site (perfxcel.com) has no authentication — all public routes are open.

Two-Tier Auth Model

Tier Surface Auth Required Mechanism
Public perfxcel.com No Open access; rate-limited
Admin admin.perfxcel.com Yes Supabase JWT + perfxcel tenant check

Admin Login Flow

sequenceDiagram
    participant A as admin.perfxcel.com
    participant SB as Supabase Auth
    participant API as api.perfxcel.com

    A->>SB: supabase.auth.signInWithPassword(email, password)
    SB-->>A: Session (JWT)
    A->>API: GET /api/v1/metrics (Bearer JWT) — liveness check
    API->>SB: supabase.auth.getUser(token)
    SB-->>API: { user }
    API->>API: requirePerfxcelTenant — DB lookup to verify perfxcel membership
    API-->>A: 200 OK
    A->>A: setSession — show dashboard

On session load, perfxcel-admin calls GET /api/v1/metrics as a liveness check. If this call fails (e.g. revoked session), the admin is signed out immediately.

requireAuth Middleware

Validates the Authorization: Bearer <token> header using supabase.auth.getUser(token) and attaches req.user.

  • Returns 401 for missing or malformed headers.
  • Returns 403 for invalid or expired tokens.
  • Dev bypass: when SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY are unset, auth is skipped with a warning log. Never remove this guard.

requirePerfxcelTenant Middleware

After requireAuth, this middleware performs a DB lookup to confirm the authenticated user is a member of the perfxcel tenant.

  • Reads the user's membership from the public schema via a separate portalSupabase client.
  • Sets req.tenantId on success.
  • Throws AuthorizationError if the tenant or membership is not found.
sequenceDiagram
    participant M as requirePerfxcelTenant
    participant PS as portalSupabase (public schema)

    M->>PS: SELECT tenants.id FROM tenants JOIN user_tenant_roles WHERE user_id = req.user.id AND slug = 'perfxcel'
    PS-->>M: { tenantId }
    M->>M: req.tenantId = tenantId
    M->>M: next()

SSO Support

The admin dashboard supports SSO via Supabase Auth providers (configured at the Supabase project level). No additional configuration is required in the application code.

Session Management

Session state is managed in App.tsx via:

supabase.auth.getSession(); // initial load
supabase.auth.onAuthStateChange(); // ongoing session changes

There is no token refresh logic in the admin app — Supabase handles refresh automatically.

Bot Protection (Public Routes)

Interest registration (POST /api/v1/courses/:id/interest), training plan requests, and contact form submissions are protected by Cloudflare Turnstile:

  1. The public site renders a Turnstile widget (@marsidev/react-turnstile).
  2. On form submit the token is included in the request body as turnstileToken.
  3. The API verifies the token against https://challenges.cloudflare.com/turnstile/v0/siteverify before inserting any record.
  4. Allowed hostnames are controlled by VITE_PERFXCEL_TURNSTILE_HOSTNAMES (comma-separated).