这是 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 组合(推荐):
- 侧边栏「空间切换器」(主切换面):每个空间行显示同步身份小标签(品牌色圆点 + 服务器简称),如
公司项目 🔵公司/兴趣圈子 🟡圈子/个人笔记 🟢个人·密钥。点空间 → 切到该账户对应的数据 + 同步目标。 - 当前同步目标 pill:某空间行/页头显示「正在以 公司@server 同步」,避免误以为是别的账户。
- SyncPanel(每空间一行,已做):给空间填服务器 + 账户 token + 空间 id(同服务器多个空间 = 绑定不同
space_id)。 - 「账户中心」面板(透明度,非切换):侧边栏头部「身份/账户」入口,按服务器分组列出所有同步身份及其挂载的本地空间;可登录/绑密钥、把某空间挂到某账户下、同步该组 / 全部。只做看板 + 管理,不做「当前账号」全局态。
为什么不做「全局多账户登录态」:
- 本地优先:打开无需登录;强加「当前账号」全局态会让「没网的本地空间」也被账号逻辑卡住,违背定位。
- 数据本就按空间物理隔离;「账户」作为空间的同步绑定已覆盖多账户/多服务器/多空间。
- 若要「一个 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 本地静置加密的密钥:不是「获取」,是「创建 + 派生」
个人版无服务器 → 没有服务器可签发密钥。正确模型:
- 你自己设一个口令(≥8 位,仅你知道)。
- 应用用 Argon2id(口令, 随机盐) → 32 字节密钥(每空间独立盐/密钥)。
- 数据落盘用 XChaCha20-Poly1305 以该密钥加密。
- 打开时输入口令 → 重派生密钥 → 解密;口令错/缺失 → 密文不可读。
核心:密钥不存在于服务器、也无需「获取」——它是你记住的口令经派生而来。别人拿到数据库文件但没有口令 → 解不开。忘记口令 = 数据不可恢复(务必记牢/备份)。
5.3 与「同步鉴权密钥」是两回事
- 本地静置密钥:口令派生,加密本机数据(私密)。
- 同步鉴权密钥:连服务器 / 拥有权的密钥(上一节),或账号 token(授权)。
- 两者正交:
本地口令管「本机谁能读」;服务器密钥/账户管「这台设备能连哪个服务器、哪个空间」。
5.4 落地路径(本地 at-rest 加密,三条路)
- 文件级(推荐折中):用派生密钥把「工作空间 DB 文件 + 附件目录」整体加解密;应用打开→输入口令→解密到会话;关闭/锁定→再加密。改动集中在存储层,对上层透明。
- SQLCipher:SQLite 官方加密扩展,成熟标准;需引入 rusqlite 的 SQLCipher 变体(
libsqlite3-sysvendor 编译),对现有 SQL 零改动但新增构建依赖。 - 逐块/逐字段:只加密敏感列(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. 结论
- 打开应用无需身份;「身份」= 每空间的同步凭据(密钥或账户)。
- 多服务器×多空间 = per-workspace
sync_profiles:一个客户端持多个身份,各自同步到各自服务器。 - 个人无服务器也要私密 = 本地 at-rest 加密:口令 → Argon2id → 密钥 → 静置加密;密钥由你创建,不需从哪「获取」。
- 密钥=拥有权(个人)、账户=授权(团队)、口令=本地读权——三者各司其职,互不混淆。
评论 0
还没有回复,来说两句吧。