安全包:审计日志、两步验证、代登录
企业买家评估 SaaS 的三道必答题:出了事能不能追溯(审计日志)、账号能不能加固 (两步验证)、排障能不能留痕(代登录)。本页讲这三件事在模板里的实现与取舍。
审计日志:精选允许清单,不是 firehose
audit_log 只记安全敏感事件——20 余类,全部登记在
src/features/audit/audit.shared.ts 的 AUDIT_ACTIONS 目录里:
| 分组 | 动作 |
|---|---|
| 账号安全(用户级) | auth.sign_in / password_changed / password_reset / 2fa_enabled / 2fa_disabled / account_deleted |
| 站点管理 | admin.impersonate_start/stop / user_banned / user_unbanned |
| 组织生命周期 | org.created / deleted / member_added / member_removed / member_role_changed / invitation_sent / invitation_canceled |
| 组织凭证与计费 | apikey.created / revoked / orgbilling.subscription_updated |
| 终身买断席位 | seat.invited / invite_canceled / accepted |
为什么不全量记录?全量日志属于可观测性(Sentry / Workers Logs),审计流水的价值在于 每一行都值得人看——精选清单让"审计页有一条新记录"本身就是信号。
新增一类事件,三步
- 把动作名加进
AUDIT_ACTIONS(点分命名:<域>.<事件>)。 - 两本字典(
i18n/dictionaries/en.ts+zh.ts)各加一条audit.act.<动作名,点转下划线>标签——有 node 测试强制,漏一边直接红。 - 在发生变更的地方调
recordAudit(db, { action, actorId, ... })。
两条硬设计
recordAudit永不 throw(内部 try/catch +console.error)——观察者不能弄坏被观察 的动作:审计写入失败,登录/邀请/撤销本身必须照常成功。audit_log故意没有外键。行要比 user/org 活得久(删号本身就是被审计的事件), 身份用冗余快照列(actorEmail、targetLabel)固化;查询侧再懒 join 当前用户表回填 没写快照的行。永不 UPDATE,append-only。
meta 是 JSON 字符串,序列化超过 2048 字节直接丢弃细节({"truncated":true})——
审计行必须小而平,大对象请进业务表。
事件从哪里流入
分三路接线,都在"离变更最近的地方":
- 登录统一由 better-auth 的
session.createdatabaseHook 覆盖(auth.server.ts)—— 邮箱密码、社交登录、2FA 二步全部经过建会话这一步,一处 hook 全收。会话上impersonatedBy非空即管理员代登录,记admin.impersonate_start而非普通登录。 - 认证端点(改密/重置/2FA 开关/删号/封禁/停止代登录)经
hooks.after的AUDIT_PATH_ACTIONS路径映射,成功才记。 - 组织事件挂在 organization 插件的 organizationHooks 上;席位/订阅事件由各自的 server 函数与 webhook 分支落笔。
两个视图与保留期
- 组织视图
/app/audit:org admin 可见(owner 计入),只看本组织的行。 - 全局视图
/admin/audit:站点管理员专属(其余人 404),跨组织全量。
两者同一套游标分页(createdAt 倒序)与筛选;action 筛选值必须在目录内,过期书签里的
非法值静默降级为不过滤而不是报错。
保留期 180 天,由 每日 Cron 清理超期行——这是 append-only
表唯一的删除路径。要改期限调 AUDIT_RETENTION_DAYS;合规要求更长时,先导出再清。
两步验证(TOTP)
better-auth twoFactor 插件,账号页组件在 auth/components/two-factor-settings.tsx:
- 开启是两步握手:
enable({ password })只签发密钥和备份码(展示二维码), 标志位在第一次verifyTotp成功后才翻转——扫到一半放弃的 setup 永远不会把人锁在门外。 - 备份码只展示一次(验证成功后),提供一键复制;丢了只能关掉重开。
- 登录时进入
/two-factor页:TOTP 码或备份码二选一,可勾选信任此设备跳过后续挑战。 - 开/关都会落审计(
auth.2fa_enabled/auth.2fa_disabled)。
代登录(Impersonation)
better-auth admin 插件自带:管理员在 /admin/users 一键以任意用户身份登录排障。
模板补的是全程留痕——起点由 session.create hook 识别 impersonatedBy 记
impersonate_start,终点经 /admin/stop-impersonating 端点记 impersonate_stop,
两行都带管理员与目标用户双方身份。没有留痕的代登录能力等于给自己埋雷,这两行是给
未来的你(和你的企业客户)看的。