feat(auth,gateway,user): X-API-Key 真接线 + key 一律超管 + owner 档案真落盘(裁决 15.4.2/15.4.3/10.4.5/08.4.2)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 15.4.2:管理 API 与 Bearer 共用同一 Authorize 路径,X-API-Key 命中签发表即过门;未知/已吊销/空值 401 且不回落 Bearer(fail-closed)
- 15.4.3:key 表去掉角色维度,任一有效 key 鉴权后即 owner;签发接口取消 role 分级(不再读请求体)
- 10.4.5:新增 GET/PUT /api/v1/admin/owner-profile(owner-only,PUT 带审计 owner.profile.update),0600 原子落盘 + 活档案热替换;前端主人档案改接真实 API,读不到即禁用保存,删掉硬编码默认档案
- 08.4.2:persona 的 identity.systemPrompt 必填,缺失/空白 400 显式报错,不再派生回落
This commit is contained in:
AskaEth committed 2026-10-04 12:07:51 +08:00
1 parent 8c6466cb00
commit adef36538f
13 files changed
+1223 -199

No files matched your search

+42 -7
View File
@@ -22,10 +22,10 @@ func (s *Server) Handler() http.Handler {
// 已认证角色」:它指 viewer 角色存在的那个只读监控
// 表面(模块清单、运行时指标/trace、日志尾部
// 与通知 feed)—— 见 registerAdminRoutes。
anyAuth := s.auth.Authorize()
ownerAuth := s.auth.Authorize(auth.RoleOwner)
writeAuth := s.auth.Authorize(auth.RoleOwner, auth.RoleAdmin)
viewerAuth := s.auth.Authorize(auth.RoleOwner, auth.RoleAdmin, auth.RoleViewer)
anyAuth := s.authorize()
ownerAuth := s.authorize(auth.RoleOwner)
writeAuth := s.authorize(auth.RoleOwner, auth.RoleAdmin)
viewerAuth := s.authorize(auth.RoleOwner, auth.RoleAdmin, auth.RoleViewer)
s.registerPublicRoutes(mux)
s.registerUserRoutes(mux, anyAuth, ownerAuth, writeAuth)
@@ -121,8 +121,7 @@ func (s *Server) registerHistoryRoutes(mux *http.ServeMux, writeAuth func(http.H
// - ownerAuth(owner):带审计的策略写、每个获取凭据的
// 渠道端点(login-qr/login-status)以及每一个其他变更。
//
// 每个写 handler 都会持久化到真实后端存储,或用明确的
// "M10 / not-wired" 错误诚实作答,而不是编造成功。
// 管理写路由表见 registerAdminWriteRoutes。
func (s *Server) registerAdminRoutes(
mux *http.ServeMux,
ownerAuth func(http.Handler) http.Handler,
@@ -163,9 +162,19 @@ func (s *Server) registerAdminRoutes(
mux.Handle("POST /api/v1/admin/rag/query", writeAuth(http.HandlerFunc(s.adminSearchRag)))
mux.Handle("POST /api/v1/admin/plugins/policy", ownerAuth(http.HandlerFunc(s.adminSetPolicy)))
// 管理写:仅 owner,带审计。
s.registerAdminWriteRoutes(mux, ownerAuth)
}
// registerAdminWriteRoutes 挂载管理写路由:仅 owner,带审计。每个 handler 都会
// 持久化到真实后端存储,或用明确的 "M10 / not-wired" 错误诚实作答,而不是编造成功。
func (s *Server) registerAdminWriteRoutes(mux *http.ServeMux, ownerAuth func(http.Handler) http.Handler) {
mux.Handle("POST /api/v1/admin/personas", ownerAuth(http.HandlerFunc(s.adminCreatePersona)))
mux.Handle("PUT /api/v1/admin/personas/{id}", ownerAuth(http.HandlerFunc(s.adminUpdatePersona)))
// 主人档案(裁决 10.4.5):白名单 + 显示名/关系备注的真实读写。
// **仅 owner** —— 这份档案决定谁能进 owner 车道(design/11 §0 的身份红线),
// 因此既不给 viewer 读、也不给 admin 写。
mux.Handle("GET /api/v1/admin/owner-profile", ownerAuth(http.HandlerFunc(s.adminGetOwnerProfile)))
mux.Handle("PUT /api/v1/admin/owner-profile", ownerAuth(http.HandlerFunc(s.adminPutOwnerProfile)))
mux.Handle("POST /api/v1/admin/users", ownerAuth(http.HandlerFunc(s.adminCreateUser)))
mux.Handle("POST /api/v1/admin/users/apikey", ownerAuth(http.HandlerFunc(s.adminIssueApiKey)))
mux.Handle("DELETE /api/v1/admin/users/apikey/{id}", ownerAuth(http.HandlerFunc(s.adminRevokeApiKey)))
@@ -212,6 +221,32 @@ func (s *Server) mountWebhooks(mux *http.ServeMux) {
}
}
// authorize 返回要求给定角色的凭据门。它由 Bearer JWT 门加上 `X-API-Key`
// 分支组成(裁决 15.4.2):key 表已注入时两种凭据都收,否则只认 Bearer。
// 按裁决 15.4.3,任何有效 key 一律 owner 级(owner 满足一切角色要求),
// 未知/已吊销 key 一律 401 且不回落 Bearer(fail-closed,见 auth.WithAPIKey)。
func (s *Server) authorize(required ...auth.Role) func(http.Handler) http.Handler {
bearer := s.auth.Authorize(required...)
keys := s.apiKeyTable()
if keys == nil {
return bearer
}
return auth.WithAPIKey(keys, bearer, s.log, required...)
}
// apiKeyTable 返回注入的 API key 表;未注入(或注入的实现不是 key 表)时为
// nil,此时鉴权只认 Bearer —— 「表不在」等同空表,绝不因此放行任何 key。
func (s *Server) apiKeyTable() auth.KeyTable {
if s.users == nil {
return nil
}
table, ok := s.users.(auth.KeyTable)
if !ok {
return nil
}
return table
}
// withMiddleware 把已路由的 handler 包进外层,最外层在前:
// 上传能力 URL 防护、SPA 回退(当配置了前端时)、
// panic recover、CORS 与请求日志。