用户与管理员
角色字段、管理台入口与命令行提权
最近更新 2026 年 11 月 3 日
用户管理基于 Better Auth 的 admin 插件。管理员在个人中心的管理台查看全部用户、改角色、 封禁与删除;第一个管理员由命令行产生。
角色
users.role 是普通文本列(src/db/schema.ts),默认 'user'。本仓只用两个取值:
| 取值 | 含义 |
|---|---|
user | 普通用户,只能进自己的个人中心 |
admin | 管理员,额外可见「管理」分组与管理台 |
判定是精确匹配:assertAdminRole 只认 'admin',未登录或字段缺失一律拒绝(用 404 而非 403, 不向非管理员泄漏这个页面存在)。
提权
第一个管理员无法由 UI 产生——管理台本身要 admin 才能进。用脚本:
pnpm db:admin:local <your-email> # 本地 D1
pnpm db:admin:remote <your-email> # 线上 D1脚本只做一件事:UPDATE users SET role = 'admin' WHERE email = ?。role 是普通列,直接改库即可, 用户下次取会话时带上新角色。也可以用 pnpm db:studio:local 在图形界面里改。
两个命令都必须显式指定目标库(--local/--remote),缺参数时脚本打印用法并退出, 避免误改生产。邮箱先经正则校验、再转义 SQL 单引号,命令以参数数组传给 wrangler(不经 shell), 恶意邮箱无法注入命令。
管理台
/dashboard/admin/users,入口在个人中心侧边栏的「管理」分组;/dashboard/admin/revenue 是营收总览。 两者都受双重约束:features.enableAdmin 开关与 admin 角色都满足才可见、可进。
- 列表:服务端直接查 D1(
fetchAdminUsers),支持按邮箱搜索、按角色与状态筛选、offset 翻页,
每页 20 条。
- 改角色:
authClient.admin.setRole({ userId, role })。 - 封禁:
authClient.admin.banUser({ userId, banReason, banExpiresIn })。banExpiresIn的单位是
秒,界面上按天输入后换算;留空即永久。封禁立即生效:被禁用户登录被拒 (BANNED_USER,HTTP 403),已有会话也失效。
- 解封:
authClient.admin.unbanUser({ userId })。 - 删除:
authClient.admin.removeUser({ userId }),连带删掉其会话,不可撤销。 - 模拟登录:
authClient.admin.impersonateUser({ userId }),可查看并吊销该用户的会话。
关闭features.enableAdmin会让/api/auth/admin/*这组 HTTP 端点整体 404,但角色判定不在 路由层:非 admin 调用插件端点由插件自己返回 403。UI 门禁与 API 门禁两者缺一不可。
`role` 与 `ADMIN_EMAILS` 是两套机制
role: 'admin' 不会自动获得 /api/email 的发信权限。那里是双重邮箱白名单:收件人与调用者 邮箱都必须出现在 ADMIN_EMAILS 环境变量里(src/routes/api/email/$.ts)。role 管管理台, ADMIN_EMAILS 管发信路由,各管各的。
验证
登录管理台账号后打开 /dashboard/admin/users,应能看到用户列表;把 features.enableAdmin 关掉, 侧栏入口消失,同一地址 404。