Skip to main content

Auth & user flow

EXEPERT uses Better Auth for identity and sessions. Supabase remains the data plane: Postgres, RLS, Realtime, RPCs, and Edge Functions. The 3D simulation is still available offline and no guest identity is created merely by opening the app.

The main modules are:

  • api/_lib/auth.ts: Better Auth, Google, verified email/password, CAPTCHA, rate limits, and the native OAuth 2.1 provider.
  • api/_lib/identity.ts and api/_lib/bridge.ts: stable principal resolution, merge/legacy claims, and five-minute Supabase JWT issuance.
  • src/auth/session.ts: browser member/guest actions and compatibility mode.
  • src/auth/bridge.ts and src/data/client.ts: in-memory bridge-token caching and injection into Supabase requests.
  • src/auth/gate.ts and src/ui/signin-view.ts: accessible sign-in modal and signed-in account popover.
  • desktop/exepert-desktop/src/chat.rs: hosted PKCE sign-in and token refresh.
  • desktop/exepert-desktop/src/auth_vault.rs: Windows Credential Manager storage for the native refresh token.

Identity boundary

The principal UUID is the durable data owner. A Better Auth user UUID is the replaceable login identity. Existing Supabase users are seeded as principals with the same UUID, so current RLS policies can continue comparing owner fields with auth.uid().

Only the server can read or write authn and app_private. Browser clients retain the public Supabase URL and publishable key but never receive database credentials, the Better Auth secret, email credentials, or signing private key.

Browser states

StateSessionData behaviorAccount surface
Signed outNoneSimulation stays local; cloud action requests identityCentered sign-in modal
GuestBetter Auth anonymous sessionPrincipal owns temporary cloud workUpgrade notice in the modal
MemberGoogle or verified email sessionPrincipal owns synchronized workCompact account popover

The browser stores the Better Auth session in an HttpOnly, same-site cookie. It requests a Supabase bridge token only when the data client needs one, keeps that token in memory, and refreshes before expiry. It does not persist the bridge JWT in local storage.

Sign-in modal

The avatar in #authSlot opens a centered modal while signed out or using a guest. It offers Google first, then email sign-in, account creation, password reset, and the explicit guest action. Escape and the scrim close it, Tab is trapped within it, initial focus moves inside, and closing restores focus to the opener. On a narrow viewport the panel becomes a safe-area-aware sheet.

After member sign-in, the same avatar opens a compact non-modal popover with the resolved account and sign-out action. Outside click closes only that popover.

Guest upgrade and legacy claim

Before a guest signs into a member account, the server creates a random, single-use, 15-minute merge ticket and stores only its SHA-256 hash. The locked database procedure either keeps the guest principal UUID or moves all known owned rows to the existing member principal. Colliding project names receive a deterministic Guest import suffix; rows are not deleted.

During the guarded rollout, the browser can also prove an existing Supabase session to claim its legacy principal. Unclaimed users and guests remain in the database indefinitely. Edge Functions temporarily accept either an explicitly verified Better Auth bridge token or a legacy Supabase session.

Native desktop flow

The native client is public and has no client secret. Loopback ports are selected at runtime, PKCE S256 is mandatory, and the authorization response's state and RFC 9207 issuer must match. The access and bridge tokens remain in memory. Only the rotating refresh token is persisted, under the EXEPERT target in Windows Credential Manager; it is revoked and deleted on sign-out.

Rollback mode

AUTH_RUNTIME_MODE=legacy keeps the browser on Supabase Auth while the new API and migration are deployed. VITE_AUTH_RUNTIME_MODE is only the offline fallback for the public config endpoint. The dual Edge verifier and preserved legacy users make switching the mode back non-destructive. See the Better Auth rollout runbook before changing the production flags.