多用户与角色:owner / admin / reader 三级,用户管理页 + 密码上库

- users 表加 password_hash 列;后台账号(owner+admin)密码 bcrypt 存行内,
  首次登录把 env / settings 引导凭据自迁移成行哈希
- 会话 token 从用户名改为携带用户 ID,角色与停用状态每请求查库,
  改角色 / 停用账号即时生效(存量会话立即 401)
- 登录:先查 users 表,再走 settings 哈希 / env 引导链;
  admin/admin 开发模式在任何账号设过密码后失效
- 权限:系统设置、用户管理仅 owner;内容管理 admin+owner;
  admin 后台新增 用户 页(创建 / 重置密码 / 停用 / 删除),
  设置页「登录与存储」tab 对管理员隐藏
- 账户页加修改密码表单(旧密码校验,OAuth/Passkey 首设免旧密码);
  评论区管理员身份跟随各自账号,不再统一挂站主名下
- 修复:providers 为 nil 时账户页白屏(Go nil slice 序列化成 null)
This commit is contained in:
Sakurasan committed 2026-10-01 22:35:37 +08:00
1 parent e5dee4daf1
commit 4bb2ff4145
23 files changed
+917 -222

No files matched your search

+4 -4
View File
@@ -142,7 +142,7 @@ func (a *API) afterIdentity(w http.ResponseWriter, r *http.Request, provider, ex
u, err := a.Store.GetUserByIdentity(provider, externUID)
switch {
case err == nil && u.Role == model.RoleOwner:
a.issueAdminSession(w, r)
a.issueAdminSession(w, r, u.ID)
return ""
case err == nil:
return a.issueReaderSession(w, r, u.ID)
@@ -186,8 +186,8 @@ func (a *API) loginBack(w http.ResponseWriter, r *http.Request) string {
// issueAdminSession 让已绑定的第三方身份直接换发后台会话 —— 「绑定即提权」
// 的落点。Secure / SameSite 与密码登录发的 cookie 完全一致,否则 HTTPS 下
// 浏览器会把它当不安全 cookie 丢掉。
func (a *API) issueAdminSession(w http.ResponseWriter, r *http.Request) {
token, exp := a.AdminSessions.Issue(a.cfg().AdminUser)
func (a *API) issueAdminSession(w http.ResponseWriter, r *http.Request, userID int64) {
token, exp := a.AdminSessions.Issue(userID)
http.SetCookie(w, &http.Cookie{
Name: adminCookieName, Value: token, Path: "/", HttpOnly: true,
Secure: isTLS(r), SameSite: http.SameSiteLaxMode,
@@ -271,7 +271,7 @@ func (a *API) passkeyFinish(w http.ResponseWriter, r *http.Request) {
return
}
if u.Role == model.RoleOwner {
a.issueAdminSession(w, r)
a.issueAdminSession(w, r, u.ID)
httpx.OK(w, map[string]any{"ok": true, "role": u.Role})
return
}