认证

由功能开关控制的邮箱认证,配合安全的桌面会话存储。

认证通过 VITE_AUTH_ENABLED Flag-gated,默认值是 true。模板实现了 Supabase 邮箱密码登录、带六位验证码的注册、带 recovery code 的忘记/重置密码、 已登录 Account 页面、profile 编辑和 avatar 上传。

当前 renderer 即使在 VITE_AUTH_ENABLED=false 时,也会在挂载前校验 VITE_SUPABASE_URLVITE_SUPABASE_PUBLISHABLE_KEY。本地开发桌面外壳时 可以使用真实值或占位值;真实 auth/data 流程需要 Supabase 项目。

运行时开关

Gate默认值效果
VITE_AUTH_ENABLEDtrue显示 auth 与 account surfaces。
Supabase env validation当前必需URL 缺失、URL 无效或 publishable key 缺失时,app/renderer.tsx 渲染 SupabaseConfigErrorScreen
Supabase schema手动配置Account 与示例数据界面需要 profilestodosavatars

关闭 auth surfaces 后,应用是一个没有登录 UI 的本地桌面外壳。计费仍由 VITE_BILLING_ENABLED 单独控制。

Auth flows

Auth routes 位于 app/routes/auth/AuthCard 提供居中外壳,表单使用 react-hook-formapp/lib/auth/auth-schemas.ts 中的 Zod schemas。

流程RouteSupabase 调用
登录/loginsupabase.auth.signInWithPassword
注册/registersupabase.auth.signUp,带 full_name metadata
验证注册/register verify stepsupabase.auth.verifyOtp({ type: 'signup' })
重新发送注册验证码/register verify stepsupabase.auth.resend({ type: 'signup' })
忘记密码/forgot-passwordsupabase.auth.resetPasswordForEmail
重置密码/reset-passwordverifyOtp({ type: 'recovery' })updateUser({ password }),然后 signOut()
退出登录Sidebar / Account通过 auth store 调用 supabase.auth.signOut()

模板当前没有接入 OAuth provider。添加 Google、GitHub 或 Apple 时,需要配置 Supabase provider 并补充桌面 deep-link routing;如果新增 callback route,要把它 放进类型化 app-intent allow-list。

Session state

app/stores/auth-store.ts 是 renderer 中的 auth source of truth:

  • statusunknownauthenticatedunauthenticated
  • Supabase Auth 的 usersession
  • 来自 public.profilesprofile
  • profile refresh 与 sign-out actions

启动时它调用 supabase.auth.getSession(),然后订阅 onAuthStateChangeSIGNED_ININITIAL_SESSIONTOKEN_REFRESHEDUSER_UPDATED 会触发 profile refresh。SIGNED_OUT 会清空 user、session 和 profile。

安全桌面存储

app/lib/supabase/client.ts 中的 Supabase client 使用 secure-session-storage.ts 提供的自定义 storage adapter:

Supabase auth storage key
  -> app/lib/supabase/secure-session-storage.ts
  -> window.conveyor.secure
  -> lib/main/secure-storage.ts
  -> Electron safeStorage encrypted file under userData/secrets.bin

所有 Supabase sessions 都存入固定 secure slot supabase-session。启动时会移除旧 localStorage session keys,避免 token 留在浏览器存储里。

当 secure storage 无法解密或读取值时,adapter 返回 null,Supabase 会把应用视为 已退出。写入时如果系统不可用加密,main process 返回 ENCRYPTION_UNAVAILABLE, auth write 会失败,而不是静默把 token 明文保存。

Account 与 avatar

Account 页面是 app/routes/AccountPage.tsx,背后复用 app/routes/settings/sections/AccountSection.tsx

界面Source后端要求
Profile loadapp/stores/auth-store.tsapp/services/profile.ts存在 profiles.id = auth.uid() 行。
Profile updateupdateProfile(userId, patch)RLS 只允许用户更新自己的行。
Avatar uploadapp/services/avatar.ts私有 avatars bucket,5 MB 限制,PNG/JPEG/WebP,路径为 <user-id>/avatar.<ext>
Avatar displayuseAvatarUrlcreateSignedUrlSigned URL TTL 是 7 天,并使用 profile.updated_at 做 cache key。

Avatar 和 profile 是 Example surfaces。它们展示了桌面文件选择、Storage 上传、signed URL 读取和 profile RLS 的模式,但发布前应按你的产品字段改造。

当前邮箱流程基于 OTP,而不是 link callback。Supabase 发送验证码,用户在桌面应用中 输入。client 也设置了 detectSessionInUrl: false

如果之后切换到链接式邮件模板或 OAuth,应在 Supabase Auth 允许 soar-electron://,但不要假设今天 /login/reset-password deep links 已经 可用。当前 app-intent allow-list 接受 /settings/account/billing 等 shell routes。

验证认证

  • 删除或破坏 VITE_SUPABASE_URL,确认应用挂载前出现 SupabaseConfigErrorScreen
  • 用邮箱密码注册,输入六位验证码,确认应用回到 Home。
  • 确认创建了用户 id 对应的 profiles 行。
  • 退出、重新登录、重启应用,并确认 session 通过 secure storage 持久化。
  • 跑忘记/重置密码流程,确认更新密码后会退出登录。
  • 在 Account 上传并移除 avatar。

相关页面

On this page