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
| Condition | Result |
|---|---|
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 missing | main.tsx renders SupabaseConfigErrorScreen before App is imported. |
VITE_AUTH_ENABLED=false | The 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:
| Flow | Route | Implementation |
|---|---|---|
| Sign in | /login | signInWithPassword |
| Create account | /register | signUp with full_name metadata |
| Confirm account | Register’s verify step | Six-digit verifyOtp with type: 'signup'; resend has a 30-second cooldown |
| Start recovery | /forgot-password | resetPasswordForEmail |
| Set new password | /reset-password | Six-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.
| Failure | Read behavior | Write/delete behavior |
|---|---|---|
ENCRYPTION_UNAVAILABLE | Returns null; Supabase treats the user as signed out. | Rejects the operation rather than saving plaintext. |
STORAGE_FAILURE | Returns 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.
| Detail | Current behavior |
|---|---|
| Profile fields | full_name, bio, phone, location, and avatar_url |
| Image validation | PNG, JPEG, or WebP; non-empty and no larger than 5 MB |
| Upload path | avatars/<user-id>/avatar.<ext> |
| Bucket visibility | Private; RLS restricts every operation to the owner folder |
| Display URL | A 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
profilesrow 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.
