fix: 独立审计日志并补齐令牌身份与失败记录

This commit is contained in:
Nixevol
2026-09-30 16:22:49 +08:00
parent eb4e2db918
commit b7c8b6ffd6
12 changed files with 641 additions and 124 deletions
+41 -18
View File
@@ -7,10 +7,21 @@ import (
"errors"
"net/http"
"strings"
"unicode/utf8"
"git.asio.asia/nixevol/NixMsg/internal/httpx"
)
const (
minTokenNameChars = 1
maxTokenNameChars = 64
)
func validTokenName(name string) bool {
n := utf8.RuneCountInString(name)
return n >= minTokenNameChars && n <= maxTokenNameChars
}
func (h *Handler) handleTokenList(w http.ResponseWriter, r *http.Request) {
items, err := h.listAPITokens(r.Context())
if err != nil {
@@ -45,32 +56,37 @@ func (h *Handler) handleTokenCreate(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
}
if err := httpx.DecodeJSON(r, &req); err != nil || strings.TrimSpace(req.Name) == "" {
h.audit(actorString(p), "token_create", "", "bad_request", ip)
if err := httpx.DecodeJSON(r, &req); err != nil {
h.auditP(p, "token_create", "", "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "名称不能为空")
return
}
name := strings.TrimSpace(req.Name)
if !validTokenName(name) {
h.auditP(p, "token_create", "", "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "名称须为 1–64 字符")
return
}
plain, hash, err := h.tokens.Issue(r.Context())
if err != nil {
h.audit(actorString(p), "token_create", "", "error", ip)
h.auditP(p, "token_create", "", "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
id, err := newTokenID()
if err != nil {
h.audit(actorString(p), "token_create", "", "error", ip)
h.auditP(p, "token_create", "", "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
created, err := h.insertAPIToken(r.Context(), id, name, hash)
if err != nil {
h.audit(actorString(p), "token_create", id, "error", ip)
h.auditP(p, "token_create", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
h.audit(actorString(p), "token_create", id, "ok", ip)
h.auditP(p, "token_create", id, "ok", ip)
httpx.WriteOK(w, map[string]any{
"id": id,
"name": name,
@@ -89,20 +105,20 @@ func (h *Handler) handleTokenPatch(w http.ResponseWriter, r *http.Request) {
Enabled *bool `json:"enabled"`
}
if err := httpx.DecodeJSON(r, &req); err != nil {
h.audit(actorString(p), "token_update", id, "bad_request", ip)
h.auditP(p, "token_update", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "请求体无效")
return
}
if req.Name == nil && req.Enabled == nil {
h.audit(actorString(p), "token_update", id, "bad_request", ip)
h.auditP(p, "token_update", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "无更新字段")
return
}
if req.Name != nil {
n := strings.TrimSpace(*req.Name)
if n == "" {
h.audit(actorString(p), "token_update", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "名称不能为空")
if !validTokenName(n) {
h.auditP(p, "token_update", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "名称须为 1–64 字符")
return
}
req.Name = &n
@@ -110,21 +126,28 @@ func (h *Handler) handleTokenPatch(w http.ResponseWriter, r *http.Request) {
if err := h.updateAPIToken(r.Context(), id, req.Name, req.Enabled); err != nil {
if errors.Is(err, sql.ErrNoRows) {
h.audit(actorString(p), "token_update", id, "not_found", ip)
h.auditP(p, "token_update", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "令牌不存在")
return
}
h.audit(actorString(p), "token_update", id, "error", ip)
h.auditP(p, "token_update", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
row, err := h.getAPITokenByID(r.Context(), id)
if err != nil {
h.audit(actorString(p), "token_update", id, "error", ip)
h.auditP(p, "token_update", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
h.audit(actorString(p), "token_update", id, "ok", ip)
detail := map[string]any{}
if req.Enabled != nil {
detail["enabled"] = *req.Enabled
}
if req.Name != nil {
detail["name_changed"] = true
}
h.auditPD(p, "token_update", id, "ok", ip, detail)
resp := map[string]any{
"id": row.ID,
"name": row.Name,
@@ -144,15 +167,15 @@ func (h *Handler) handleTokenDelete(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if err := h.deleteAPIToken(r.Context(), id); err != nil {
if errors.Is(err, sql.ErrNoRows) {
h.audit(actorString(p), "token_delete", id, "not_found", ip)
h.auditP(p, "token_delete", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "令牌不存在")
return
}
h.audit(actorString(p), "token_delete", id, "error", ip)
h.auditP(p, "token_delete", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
h.audit(actorString(p), "token_delete", id, "ok", ip)
h.auditP(p, "token_delete", id, "ok", ip)
httpx.WriteOK(w, map[string]any{})
}