Skip to content

用户、空间与认证授权

本页说明Molore的账号体系与访问边界:用户如何注册登录、令牌如何签发与吊销、个人空间与团队空间如何隔离数据、游客演示模式与管理后台的边界,以及会员功能的门控口径。理解这一层是理解检索、打标、图谱等所有上层模块「数据从谁的库里取」的前提。

核心概念

概念说明
本地账户本地注册永久免费,邮箱 + 密码开户,密码以 bcrypt 哈希落库
JWT登录凭证,签发时携带 token_version 声明,用于服务端吊销
token_version用户表上的版本号,改密/登出时递增,旧 token 立即失效
个人空间默认作用域,内容按 user_id + tenant_id IS NULL 归属
团队空间多人共享工作区,内容按 tenant_id = T 归属,全员共享口径
游客演示模式未登录 GET 请求自动注入只读演示账号(demo@wenmo.local
admin独立于普通用户体系的管理员通道,单独密钥签发
会员门控Pro 功能判定走 plan features + feature_flags.membership_enabled 总开关

注册与登录

  • 本地注册永久免费:注册即开户(POST /api/v1/auth/registerapp/api/v1/endpoints/auth.py),赠送免费层容量护栏,条数不设限。
  • 密码复杂度:8~128 位且须含大小写字母与数字,由 PASSWORD_COMPLEXITY_RE 校验(app/core/security.py)。
  • 密码哈希:bcrypt(get_password_hash,rounds=12)。bcrypt 只认前 72 字节——哈希与校验两侧同口径按 UTF-8 截断 72 字节(app/core/security.py:48-61)。两侧必须对称截断,否则超长密码会永远「密码不正确」,这是登录/注销/改密全链路的既有教训。
  • 登录成功签发 access token + refresh token 一对(_create_token_pair),refresh token 不可当 access token 用(token_use 声明区分)。

令牌吊销:token_version

JWT 本身无状态,吊销靠版本号实现(app/core/security.py:116):

  • 签发时把用户当前 token_version 写入 token 声明;
  • 每次请求 get_current_user 比对 token 声明与用户表当前值,不一致即 401「Token has been revoked」;
  • 改密(change-password)与登出(logout)都会递增 token_version,此后所有旧 access/refresh token 一并失效(app/api/v1/endpoints/auth.py:307-374)。

安全事件(登录失败、无效 token、吊销重放等)统一走 app.security logger,只记事件类型 + IP + 账号标识,不记密码与 token 本体。

云端与本地是两套库、两个密码

桌面端本地库与云端库是两个独立数据库、两套独立密码

  • 改密只改当前所在的那一侧,不会同步到另一侧
  • 云登录自动建本地账户时,本地密码取刚验证过的云端密码,此后两库各自演化;
  • 两侧的 token_version 同样各自独立。

个人空间与团队空间

租户上下文由 app/core/tenant_scope.py 收口,全模块共用同一口径:

  • get_active_tenant:返回当前激活租户;成员被移除、租户停用或 tenant_id 残留时一律回落个人空间。
  • scope_condition:团队上下文 → model.tenant_id == T;个人空间 → user_id == 本人 AND tenant_id IS NULL。笔记、剪藏、知识单元、文档、文件夹、标签全部按此条件过滤。
  • 团队空间内内容全员共享,不做部门/分组隔离;成员被移出后,其在空间内创建的内容保留在团队中。
  • 会话始终是创建者私有:团队空间建的会话打上空间戳,但 user_id 条件不随空间放宽(app/models/chat.py)。
  • 图谱构建状态表 graph_build_state 使用复合主键 (user_id, tenant_id),个人空间以空串 '' 占位(迁移 migrate_graph_build_state_space_pkapp/core/db_migrations.py),保证每个空间各一行构建状态。

席位行锁

团队拉人有席位上限(默认 10 席)。并发拉人通过对租户行加锁读防超卖(app/api/v1/endpoints/tenants.py:229):

db.query(Tenant).filter(Tenant.id == tenant_id).with_for_update().first()

PostgreSQL 下这是 SELECT ... FOR UPDATE;SQLite 下 with_for_update 为空操作,由单连接串行提交兜底。席位占满时拉人返回 403。

游客演示模式

app/core/guest_demo.py 实现「打开即所见」的演示:

  • 未携带凭证的 GET 请求命中 /api/v1/ 前缀时,中间件自动注入演示账号(demo@wenmo.local)的长效 token;
  • 写操作(POST/PUT/DELETE/PATCH)不注入,仍需真实登录——演示账号只读(仅工作区切换 activate/deactivate 两个幂等端点例外,供演示团队空间);
  • 演示 token 限 30 天且携带 token_version,改密/登出后旧缓存 token 被拒,重启自愈;
  • 演示账号只放可公开数据;自托管不播种演示账号时该功能自然关闭。

admin 独立于普通用户

管理后台通道与用户通道完全分离(app/core/security.py):

  • admin token 用独立的 ADMIN_SECRET_KEY 签发,payload type=admin;普通用户 token 是 type=user,两者互不通用;
  • 铸造方式:create_access_token({"sub": admin_id}, is_admin=True)
  • 管理端点挂在 app/api/admin/ 下,与普通用户 API(app/api/v1/)分属两套路由。

会员门控

Pro 功能(BYOK、云端备份同步、模型管家等)的权限判定有两个层次:

  • 功能位判定BillingService.check_feature_access(user_id, feature)app/services/billing_service.py:250)按订阅 plan 的 features 逐项判定,续费/折扣/权益全走此口径。
  • 总开关feature_flags.membership_enabled(默认开,缺键即开)。管理员关闭期间所有会员功能全员放行。桌面端经 desktop_gate.membership_enforced / membership_enforced_for 判定(app/core/desktop_gate.py):本地开关 + 云端开关缓存双重判定,云端开关随 /users/me 回源缓存进 cloud_bindings 表。

断网语义是 fail-closed:断网沿用旧缓存;从未拉到过云端开关(缓存为 NULL)时维持门禁(app/core/desktop_gate.py:271-274),信息不足不擅自放行。

下一步

基于 AGPL-3.0 协议发布。