// 会话身份的解析与角色守卫。token 只带用户 ID(防篡改靠 HMAC),角色与 // 停用状态每请求从 users 表现读——后台改角色 / 停用账号即时生效, // 不用等 7 天会话过期。 package admin import ( "context" "net/http" "oneblog/internal/httpx" "oneblog/internal/model" ) type actor struct { ID int64 Handle string Role string } type actorKey struct{} func (a *API) guard(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := bearer(r) if token == "" { if c, err := r.Cookie(cookieName); err == nil { token = c.Value } } id, err := a.Sessions.Verify(token) if err != nil { httpx.Unauthorized(w) return } u, err := a.Store.GetUserByID(id) // 只认后台账号行:token 是我们签发的,理论上不会指到 reader, // 但防御式校验一层;停用的账号立即失效。 if err != nil || u.Provider != "admin" || u.Banned { httpx.Unauthorized(w) return } act := actor{ID: u.ID, Handle: u.Handle, Role: u.Role} next(w, r.WithContext(context.WithValue(r.Context(), actorKey{}, act))) } } // guardOwner 在 guard 之上加角色门槛:系统设置、用户管理只属于站主。 func (a *API) guardOwner(next http.HandlerFunc) http.HandlerFunc { return a.guard(func(w http.ResponseWriter, r *http.Request) { if actorFrom(r).Role != model.RoleOwner { httpx.Error(w, http.StatusForbidden, "需要站主权限") return } next(w, r) }) } func actorFrom(r *http.Request) actor { act, _ := r.Context().Value(actorKey{}).(actor) return act }