Haven 安全机制全解析:bcrypt、JWT、MFA、限流与磁盘守护的 5 道防线
Haven 安全机制全解析:bcrypt、JWT、MFA、限流与磁盘守护的 5 道防线
Haven 是一款自托管(self-hosted)私有聊天服务器,可部署在自己的机器上作为 Discord 替代品,主打"无云、无遥测、无大科技公司"。但数据放在自己手里,安全设计是否可靠就成了关键问题。本文带你从源码层面快速看懂 Haven 的 5 道核心安全防线:bcrypt 密码哈希、JWT 会话管理、TOTP 双因素认证、多层限流,以及独特的磁盘守护机制,帮助你在部署前心里有底。
1️⃣ 密码存储:bcrypt 哈希,数据库泄露也安全
Haven 用 bcrypt 对用户密码做哈希存储,而不是保存明文——即使整个数据库被偷走,攻击者也拿不回你的密码。几个值得注意的细节:
- 强度分级:注册时密码用 12 轮 cost 因子哈希,兼顾抗破解能力和登录速度;
- 防"用户名探测":当登录的账号不存在时,服务器会拿一个假哈希(dummy hash)执行一次等时长的比对,让"报错速度快慢"无法暴露哪些用户名真实存在;
- 密码策略:密码必须 8–128 位,且不能包含自己的用户名;
- 临时密码机制:管理员重置密码后,用户可用原密码登录来"取消重置",这样其端到端(E2E)私钥的包装密钥不受影响,DM 历史得以保留(见 src/auth.js)。
2️⃣ 会话管理:JWT 令牌 + password_version 撤销
Haven 的登录态基于 JWT(JSON Web Token),令牌由环境变量 JWT_SECRET 签名,未设置该密钥时服务器会直接拒绝启动(src/auth.js)。
无状态令牌有个经典难题:如何"作废"一个被盗的令牌?Haven 的答案是 password_version 机制:
- 每个令牌都携带
pwv(密码版本号)声明; - 修改密码、启用 2FA、注销其他设备时,服务器把账号的
password_version加一; - 所有旧令牌的
pwv与数据库不一致后立即失效,5 秒缓存窗口内完成全局生效(verifyToken)。
管理员还可以配置会话时长(0–365 天,0 表示永不过期);访客账号则只有 12 小时短会话。
3️⃣ 双因素认证:TOTP 动态码 + 一次性备份码
Haven 内置标准 TOTP(Time-based One-Time Password)双因素登录(src/auth.js),兼容 Google Authenticator 等主流验证器应用:
| 设计点 | 说明 |
|---|---|
| 6 位动态码 / 30 秒 | 标准 TOTP 参数,SHA1 算法 |
| 挑战令牌 | 密码通过后先签发 5 分钟有效的 challenge token,输入动态码后才发正式会话 |
| 动态码只许用一次 | 服务器记录已用过的时间步,防止偷看到的 6 位码在一分半钟内重放 |
| 8 个备份码 | 启用时生成 XXXX-XXXX 格式的一次性备用码,数据库里只存 SHA-256 哈希 |
| 错误锁定 | 每账号 15 分钟内错 10 次动态码即暂停,防 6 位码被暴力穷举 |
开启和关闭 2FA 都必须重新输入密码,防止被盗会话被"偷梁换柱"。另外还有账号恢复码:提前生成 8 个一次性恢复码,丢失密码和 2FA 时也能自助重置(src/auth.js)。
4️⃣ 限流:从 IP 到账号的四层闸门
Haven 的限流全部在内存中实现,零额外依赖,且层层递进(src/auth.js):
- IP 级认证限流:同一 IP 每 15 分钟最多 20 次登录/注册请求,超出返回
429; - 账号级密码错误限制:同一账号 15 分钟错 20 次,且同一 IP 错 10 次也会被挡——但"自己用过的 IP"只受较宽松的账号级限制,避免陌生人撞库反而把房主锁在门外;
- 全局注册速度限制(可选):限制服务器每小时新增账号数上限,专门对付"分散到大量 IP"的机器人注册潮,这类攻击恰恰绕得过 IP 级限流;
- CAPTCHA(可选):集成 Cloudflare Turnstile,服务端在线验证令牌真伪,伪造或网络异常一律视为失败。
此外,IP 封禁功能自带"防误伤":服务器会自动拒绝封禁本机、封禁操作者自己、或封禁管理员/同级成员所用地址(src/ipBanGuard.js)。
5️⃣ 磁盘守护:让服务器永远不会"卡死在满盘"
这是 Haven 最有意思的设计之一(src/diskGuard.js)。
问题:SQLite 删除一行数据时本身也要写入磁盘。一旦数据卷被文件塞满,管理员连"删掉占空间的附件"都做不到——删除操作本身写不进去,实例彻底卡死,只能扩容硬盘解围。
解法:永不交出最后一部分空间。
- 默认保留 512 MB(可用
HAVEN_DISK_RESERVE_MB调整),专留给数据库、WAL 和 vacuum 操作; - 上传守卫
guardUploads在文件落盘之前检查空间,一旦剩余空间触及保留线,直接返回507暂停上传,保证"删除"这条路永远走不通; - 检测用
statfs系统调用,5 秒缓存避免突发上传时频繁系统调用; - 平台不支持时"失败开放"(fail open),与没有该功能前行为一致;
- 空间紧张时,控制台每 5 分钟告警一次,同时把状态推给管理端界面,管理员无需盯日志也能发现。
6️⃣ 更多细节:SSRF 防护与出网检查
- 安全出站抓取:链接预览、媒体代理等功能会代服务器去抓用户指定的 URL,src/safeFetch.js 对每一跳重定向做 DNS 解析并核对地址,阻断"让用户链接把服务器指向内网"的 SSRF 攻击;
- SSO 头像下载:校验协议、MIME 类型和文件魔数,下载后还要经过隐私清洗(去除 EXIF 定位信息)才能入库;
- 漏洞上报:发现安全问题请通过 SECURITY.md 中的私有渠道报告,只有最新版本会获得安全修复。
小结:一张表看懂 Haven 的 5 道防线
| 防线 | 机制 | 核心文件 |
|---|---|---|
| 密码存储 | bcrypt 哈希 + 防探测假哈希 | src/auth.js |
| 会话管理 | JWT + password_version 全局撤销 | src/auth.js |
| 双因素 | TOTP + 一次性备份码 + 恢复码 | src/auth.js |
| 限流 | IP / 账号 / 全局注册四层闸门 | src/auth.js |
| 磁盘守护 | 512 MB 保留空间,拒绝"吃掉逃生通道"的上传 | src/diskGuard.js |
自托管的最大吸引力是数据自主,而 Haven 在密码学、会话生命周期和运维容灾上的这套组合拳,正是"把服务器放到自己手里"能让人放心的底气所在。

