Authentication

Flag-gated email auth, OTP flows, and OS-keychain session storage.

Authentication is enabled by default through VITE_AUTH_ENABLED. The template ships Supabase email/password sign-in, sign-up verification, password recovery, an Account page, profile editing, and private avatar uploads.

Gates and startup behavior

ConditionResult
VITE_AUTH_ENABLED=true (default)Sign-in and account UI are available. A valid Supabase URL and publishable key are required.
Auth on + URL missing/invalid or key missingmain.tsx renders SupabaseConfigErrorScreen before App is imported.
VITE_AUTH_ENABLED=falseThe auth surfaces and store activity are absent; the app is a local desktop app. The Supabase client becomes an import-safe proxy and throws only if code accidentally tries to use it.

This makes local-only builds possible without inventing placeholder backend behavior. Billing has a separate VITE_BILLING_ENABLED gate.

Email flows

Routes in src/routes/auth/ share the centered AuthCard shell. Forms use React Hook Form and the Zod schemas in src/lib/auth/auth-schemas.ts:

FlowRouteImplementation
Sign in/loginsignInWithPassword
Create account/registersignUp with full_name metadata
Confirm accountRegister’s verify stepSix-digit verifyOtp with type: 'signup'; resend has a 30-second cooldown
Start recovery/forgot-passwordresetPasswordForEmail
Set new password/reset-passwordSix-digit recovery verifyOtp, then updateUser, then signOut

Passwords must be at least eight characters and include a letter and a number. Configure Supabase’s signup and recovery email templates to show {{ .Token }}. These are code-entry flows, not magic-link redirects; the client intentionally sets detectSessionInUrl: false.

There are no OAuth providers wired into this template. Adding one requires both Supabase provider setup and a safe deep-link callback: register the rebranded scheme in Supabase and add the callback to the app-intent route allow-list. See Supabase Setup and Deep Links.

Session state and the keychain

src/stores/auth-store.ts owns the renderer’s unknown, authenticated, and unauthenticated states plus user, session, and profile. At startup it gets the existing session, then listens for Supabase auth events. Sign-in, initial session, token refresh, and user updates refresh the profile; sign-out clears it.

The session never persists in ordinary browser storage:

supabase-js storage adapter
  → src/lib/tauri/secure.ts
  → generated secure commands
  → src-tauri/src/commands/secure.rs
  → OS keychain (Rust keyring crate)

Supabase’s variable storage key is mapped to one fixed keychain slot, supabase-session. Legacy localStorage session entries are removed at startup. The native SecureKey enum is a whitelist — access-token, refresh-token, supabase-session, license-key, and showcase-secret — so the webview cannot probe arbitrary keychain entry names.

FailureRead behaviorWrite/delete behavior
ENCRYPTION_UNAVAILABLEReturns null; Supabase treats the user as signed out.Rejects the operation rather than saving plaintext.
STORAGE_FAILUREReturns null; the app stays usable but has no recovered session.Rejects with the native error.

On unsigned macOS development builds, a keychain access prompt is expected. Allow it to test persistent login; do not replace this adapter with localStorage just to avoid the prompt.

Account, profile, and avatar

src/routes/AccountPage.tsx renders the Account settings section. It reads and updates the profiles row through src/services/profile.ts and uses src/services/avatar.ts for the avatar workflow.

DetailCurrent behavior
Profile fieldsfull_name, bio, phone, location, and avatar_url
Image validationPNG, JPEG, or WebP; non-empty and no larger than 5 MB
Upload pathavatars/<user-id>/avatar.<ext>
Bucket visibilityPrivate; RLS restricts every operation to the owner folder
Display URLA signed URL with a seven-day TTL, cache-busted by profiles.updated_at

Profile and avatar are example surfaces, but the pattern is production useful: validation in the renderer, owner-scoped Storage RLS, a stored path rather than a public URL, and short-lived signed reads. Apply the required SQL from Supabase Setup before enabling them.

Verify authentication

  • Remove the URL or publishable key with auth enabled and confirm the configuration screen appears instead of a broken app shell.
  • Register, enter the six-digit code, and confirm a profiles row exists.
  • Sign out, sign in, restart the app, and confirm the keychain restores the session.
  • Complete forgot/reset password and confirm the flow ends at sign-in.
  • Upload and remove an avatar from Account.

On this page