数友社区 · 帖子

ShuyoNote 的身份与隐私模型

这是 ShuyoNote 项目文档里的《身份与隐私模型》全文:本地优先的软件里, 「你是谁」和「你在哪台设备上打开它」为什么必须解耦。

本文整理「一人多服务器 × 多空间」与「个人无服务器也要私密」这两条线背后的身份 / 鉴权 / 加密设计决策。关联: 设计哲学(本地优先、数据可搬)/ 定位 / 路线图(加密里程碑)。 落地节奏见 身份与隐私 子路线图。 服务端相关:shuyonote-sync-server(账号/角色鉴权、密钥签发)见其 docs/architecture.md 与 docs/team-edition-*.md。

0. 一句话模型

打开应用不需要身份(本地优先);「身份」只用于「同步/远程」——每工作空间绑定一个同步身份(密钥或账户);本地私密靠「口令派生密钥」的落盘加密。


1. 背景场景

小明同时是:

  • 个人用户:自己部署了一台同步服务器(无账户体系)。
  • 公司员工:公司购买了团队版,给了一个账户。
  • 兴趣圈成员:朋友的团队版,另一个账户。

→ 一个人 = 3+ 台服务器 × 每台多个空间(3·n 个),各目标独立账号/密钥。

同时,若小明不部署服务器(纯本地),又不想让别人随便看笔记 → 需要「本地没有服务器情况下的私密」。

这两条线的一个公共问题:密钥 / 身份从哪来、怎么用、一个客户端怎么同时持有多个。


2. 核心原则:本地优先 → 身份与「打开应用」解耦

  • 应用是 local-first:数据在你本机,打开应用无需任何身份(可选加本地口令/系统锁)。
  • 所以不做「以某身份登录才能打开」——那违背本地优先的「数据即文件、随时可读」定位。
  • 身份只服务「同步 / 远程鉴权」,以及(可选)「本地静置加密」的解锁。

3. 身份模型:两种形态

  • 密钥(个人/自建,无账户):你自己的同步服务器 | 一个秘密密钥(自建服务器签发给你;个人空间可配 E2E 密钥对) | 持钥即拥有该服务器全部空间;服务器支持作废/轮换
  • 账户(团队版):公司 / 朋友的团队服务器 | email/password → 会话 token(Bearer) | 按 space_members 角色:viewer < editor < admin < owner

关键差异:

  • 密钥 = 拥有权:服务端不建用户表,只向「机主/租户」发钥,简单、极简。
  • 账户 = 授权:多用户、按空间分角色、TLS 会话、可邀请/移除成员。

4. 一个客户端,多个身份

必须区分两种「多身份」:

含义 A:多个「同步身份」(推荐,已实现)

一个客户端内不同工作空间各连不同服务器 + 各自凭据:

本地空间「个人」 → 密钥   → 小明自己的服务器
本地空间「公司」 → 账户token → 公司团队服务器(成员角色控权)
本地空间「圈子」 → 账户token → 朋友团队服务器(成员角色控权)
  • 实现即 per-workspace sync_profiles(meta.db.sync_profiles(ws_id, server_url, token, space_id, seq…))。
  • 不需要「切换身份打开应用」:打开时无身份,同步时按空间取对应身份。
  • 各目标凭据、变更序列、附件分桶相互独立(公司的数据只进公司服务器)。

含义 B:多个「本地用户」

同一台机器多人使用(家庭/共用电脑),各自本地数据 + 加密隔离——类似浏览器 profile / OS 多账号。

  • 与「本地优先单用户」定位有张力;建议后置。
  • 若要,形态 = 每身份一份 vault + 独立口令/密钥,属独立大特性。

✅ 结论:不做「多身份打开应用」;「多身份」= 每空间绑定一个同步身份,一个客户端天然持多身份。

4.2 多账户切换界面(UI)

在 per-workspace sync_profiles 之上,「切换账户」的界面 = 切换工作空间(身份随空间走),不做「全局当前登录账号」。

打开 App(无需身份)
 ├─ 空间「个人」 → 同步身份[个人密钥] → 小明服务器
 ├─ 空间「公司A」→ 同步身份[公司账户] → 公司服务器(成员角色)
 └─ 空间「圈子」 → 同步身份[圈子账户] → 朋友服务器

UI 组合(推荐):

  1. 侧边栏「空间切换器」(主切换面):每个空间行显示同步身份小标签(品牌色圆点 + 服务器简称),如 公司项目 🔵公司 / 兴趣圈子 🟡圈子 / 个人笔记 🟢个人·密钥。点空间 → 切到该账户对应的数据 + 同步目标。
  2. 当前同步目标 pill:某空间行/页头显示「正在以 公司@server 同步」,避免误以为是别的账户。
  3. SyncPanel(每空间一行,已做):给空间填服务器 + 账户 token + 空间 id(同服务器多个空间 = 绑定不同 space_id)。
  4. 「账户中心」面板(透明度,非切换):侧边栏头部「身份/账户」入口,按服务器分组列出所有同步身份及其挂载的本地空间;可登录/绑密钥、把某空间挂到某账户下、同步该组 / 全部。只做看板 + 管理,不做「当前账号」全局态。

为什么不做「全局多账户登录态」:

  • 本地优先:打开无需登录;强加「当前账号」全局态会让「没网的本地空间」也被账号逻辑卡住,违背定位。
  • 数据本就按空间物理隔离;「账户」作为空间的同步绑定已覆盖多账户/多服务器/多空间。
  • 若要「一个 App 多本地用户(含各自登录态)」= 本地多用户档案(browser/OS 多账号),属独立大特性,与本地优先定位有张力,建议后置(见含义 B)。

5. 本地私密(无服务器)

5.1 两种加密必须分清

  • 同步加密(E2E):数据传输到同步服务器时加密(服务端仅存密文) | ✅ 已实现(security.rs + crypto.rs:Argon2id 派生 + XChaCha20-Poly1305;encrypt_payload/decrypt_payload)
  • 本地静置加密(at-rest):数据存在本机磁盘时加密 | ✅ 已实现(v1.64.16:SQLCipher 加密空间库 + 附件加密 + 启动锁定/解锁门控,默认关;见内部加密实现笔记)

注:本地静置加密已落地为 SQLCipher 方案(§5.4 方案 2):加密配置存 meta(明文)、密钥仅存会话内存(SESSION_KEY 不落盘)、启动默认锁定、锁定态不读空间;「口令解锁/锁定」既是会话锁,也是磁盘加密的解锁门。

5.2 本地静置加密的密钥:不是「获取」,是「创建 + 派生」

个人版无服务器 → 没有服务器可签发密钥。正确模型:

  1. 你自己设一个口令(≥8 位,仅你知道)。
  2. 应用用 Argon2id(口令, 随机盐) → 32 字节密钥(每空间独立盐/密钥)。
  3. 数据落盘用 XChaCha20-Poly1305 以该密钥加密。
  4. 打开时输入口令 → 重派生密钥 → 解密;口令错/缺失 → 密文不可读。

核心:密钥不存在于服务器、也无需「获取」——它是你记住的口令经派生而来。别人拿到数据库文件但没有口令 → 解不开。忘记口令 = 数据不可恢复(务必记牢/备份)。

5.3 与「同步鉴权密钥」是两回事

  • 本地静置密钥:口令派生,加密本机数据(私密)。
  • 同步鉴权密钥:连服务器 / 拥有权的密钥(上一节),或账号 token(授权)。
  • 两者正交:本地口令 管「本机谁能读」;服务器密钥/账户 管「这台设备能连哪个服务器、哪个空间」。

5.4 落地路径(本地 at-rest 加密,三条路)

  1. 文件级(推荐折中):用派生密钥把「工作空间 DB 文件 + 附件目录」整体加解密;应用打开→输入口令→解密到会话;关闭/锁定→再加密。改动集中在存储层,对上层透明。
  2. SQLCipher:SQLite 官方加密扩展,成熟标准;需引入 rusqlite 的 SQLCipher 变体(libsqlite3-sys vendor 编译),对现有 SQL 零改动但新增构建依赖。
  3. 逐块/逐字段:只加密敏感列(content_json/content_text/附件);粒度细但读写与索引复杂、易漏。

5.5 用好它的提醒

  • 口令即密钥,别无副本 → 必须记牢/备份;否则数据永久丢失。
  • 建议可选开启(默认关),别把「本地优先=数据可读」做成「默认锁死」。
  • 与 sync_profiles 的同步身份分层清晰:本地口令管读、同步身份管连。

6. 安全要点

  • 密钥身份:持钥即拥有 → 自建服务器要支持作废/轮换;客户端别把团队凭据当个人密钥发给别的服务器。
  • 账户身份:token 有 TTL;角色控权;require_space 已按角色挡(服务端)。
  • E2E:个人空间客户端持钥加密;组织空间服务端可见明文(为协同/检索/AI 的取舍,已定)。
  • 多身份同客户端:凭据隔离 + 每身份 E2E 钥匙独立(别让公司空间读到个人空间钥匙);凭据建议进 OS 钥匙串。

7. 现状与路线

  • 多空间 / 每空间独立库(物理隔离):✅
  • per-workspace sync_profiles(多服务器×多空间):✅(客户端 src-tauri/src/sync.rs + SyncPanel)
  • 同步 E2E(口令派生 + XChaCha20):✅(security.rs/crypto.rs)
  • 服务器账号/角色鉴权(团队版):✅(sync-server S3/S4/S5)
  • 本地静置加密(at-rest):✅ 已实现(SQLCipher 空间库 + 附件加密 + 锁定门控,默认关)
  • 服务器「密钥签发/作废」接口(个人版无账户):⬜ 待建(sync-server)
  • SyncPanel「登录拿 token / 列出空间绑定」:✅ 已实现(U3:登录 + 空间下拉绑定)
  • UI:空间切换器带身份标签 + 当前同步目标 pill:✅ 已实现(U1/U2)
  • UI:账户中心面板(看板式管理同步身份):✅ 已实现(U4:按服务器分组列身份 + 挂载空间)
  • 本地「多用户档案」(含义 B):🚫 后置

8. 结论

  1. 打开应用无需身份;「身份」= 每空间的同步凭据(密钥或账户)。
  2. 多服务器×多空间 = per-workspace sync_profiles:一个客户端持多个身份,各自同步到各自服务器。
  3. 个人无服务器也要私密 = 本地 at-rest 加密:口令 → Argon2id → 密钥 → 静置加密;密钥由你创建,不需从哪「获取」。
  4. 密钥=拥有权(个人)、账户=授权(团队)、口令=本地读权——三者各司其职,互不混淆。
📥 存为 ShuyoNote 笔记

评论 0

还没有回复,来说两句吧。

板块:数据主权

← 返回首页