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
+56 -45
View File
@@ -225,7 +225,7 @@ func (h *Handler) handleEndpointCreate(w http.ResponseWriter, r *http.Request) {
DefaultDelaySeconds *int64 `json:"default_delay_seconds"`
}
if err := httpx.DecodeJSON(r, &req); err != nil {
h.audit(actorString(p), "endpoint_create", "", "bad_request", ip)
h.auditP(p, "endpoint_create", "", "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "请求体无效")
return
}
@@ -234,7 +234,7 @@ func (h *Handler) handleEndpointCreate(w http.ResponseWriter, r *http.Request) {
delaySec = *req.DefaultDelaySeconds
}
if errMsg := validateEndpointFields(req.ID, req.Name, req.Remark, req.LoginPassword, req.TalkPassword, delaySec, h.maxScheduleSeconds()); errMsg != "" {
h.audit(actorString(p), "endpoint_create", req.ID, "bad_request", ip)
h.auditP(p, "endpoint_create", req.ID, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", errMsg)
return
}
@@ -245,7 +245,7 @@ func (h *Handler) handleEndpointCreate(w http.ResponseWriter, r *http.Request) {
if loginPW == "" {
pw, err := generateLoginPassword()
if err != nil {
h.audit(actorString(p), "endpoint_create", id, "error", ip)
h.auditP(p, "endpoint_create", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
@@ -254,7 +254,7 @@ func (h *Handler) handleEndpointCreate(w http.ResponseWriter, r *http.Request) {
}
loginHash, err := h.hash.Hash(r.Context(), auth.PasswordLogin, loginPW)
if err != nil {
h.audit(actorString(p), "endpoint_create", id, "error", ip)
h.auditP(p, "endpoint_create", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
@@ -262,7 +262,7 @@ func (h *Handler) handleEndpointCreate(w http.ResponseWriter, r *http.Request) {
if req.TalkPassword != "" {
th, hashErr := h.hash.Hash(r.Context(), auth.PasswordTalk, req.TalkPassword)
if hashErr != nil {
h.audit(actorString(p), "endpoint_create", id, "error", ip)
h.auditP(p, "endpoint_create", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
@@ -280,15 +280,15 @@ func (h *Handler) handleEndpointCreate(w http.ResponseWriter, r *http.Request) {
})
if err != nil {
if isUniqueConstraint(err) {
h.audit(actorString(p), "endpoint_create", id, "id_taken", ip)
h.auditP(p, "endpoint_create", id, "id_taken", ip)
httpx.WriteError(w, http.StatusConflict, "id_taken", "编号已占用")
return
}
h.audit(actorString(p), "endpoint_create", id, "error", ip)
h.auditP(p, "endpoint_create", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
h.audit(actorString(p), "endpoint_create", createdID, "ok", ip)
h.auditP(p, "endpoint_create", createdID, "ok", ip)
data := map[string]any{"id": createdID}
if pwGenerated {
data[loginPasswordOnceKey] = loginPW
@@ -322,28 +322,28 @@ func (h *Handler) handleEndpointPatch(w http.ResponseWriter, r *http.Request) {
Enabled *bool `json:"enabled"`
}
if err := httpx.DecodeJSON(r, &req); err != nil {
h.audit(actorString(p), "endpoint_patch", id, "bad_request", ip)
h.auditP(p, "endpoint_patch", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "请求体无效")
return
}
if req.Name == nil && req.Remark == nil && req.DefaultDelaySeconds == nil && req.Enabled == nil {
h.audit(actorString(p), "endpoint_patch", id, "bad_request", ip)
h.auditP(p, "endpoint_patch", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "无更新字段")
return
}
if req.Name != nil && !protocol.ValidName(*req.Name) {
h.audit(actorString(p), "endpoint_patch", id, "bad_request", ip)
h.auditP(p, "endpoint_patch", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "名称不合法")
return
}
if req.Remark != nil && utf8.RuneCountInString(*req.Remark) > maxRemarkChars {
h.audit(actorString(p), "endpoint_patch", id, "bad_request", ip)
h.auditP(p, "endpoint_patch", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "备注过长")
return
}
if req.DefaultDelaySeconds != nil {
if msg := validateDelaySeconds(*req.DefaultDelaySeconds, h.maxScheduleSeconds()); msg != "" {
h.audit(actorString(p), "endpoint_patch", id, "bad_request", ip)
h.auditP(p, "endpoint_patch", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", msg)
return
}
@@ -361,11 +361,11 @@ func (h *Handler) handleEndpointPatch(w http.ResponseWriter, r *http.Request) {
wasEnabled, err = h.patchEndpoint(r.Context(), id, req.Name, req.Remark, req.DefaultDelaySeconds, patchEnabled)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
h.audit(actorString(p), "endpoint_patch", id, "not_found", ip)
h.auditP(p, "endpoint_patch", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "端不存在")
return
}
h.audit(actorString(p), "endpoint_patch", id, "error", ip)
h.auditP(p, "endpoint_patch", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
@@ -374,12 +374,12 @@ func (h *Handler) handleEndpointPatch(w http.ResponseWriter, r *http.Request) {
var found bool
found, err = h.setEndpointEnabled(r.Context(), id, *req.Enabled)
if err != nil {
h.audit(actorString(p), "endpoint_patch", id, "error", ip)
h.auditP(p, "endpoint_patch", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
if !found {
h.audit(actorString(p), "endpoint_patch", id, "not_found", ip)
h.auditP(p, "endpoint_patch", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "端不存在")
return
}
@@ -391,11 +391,19 @@ func (h *Handler) handleEndpointPatch(w http.ResponseWriter, r *http.Request) {
}
row, err := h.getEndpoint(r.Context(), id)
if err != nil {
h.audit(actorString(p), "endpoint_patch", id, "error", ip)
h.auditP(p, "endpoint_patch", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
h.audit(actorString(p), "endpoint_patch", id, "ok", ip)
detail := map[string]any{}
if req.Enabled != nil {
detail["enabled"] = *req.Enabled
}
if len(detail) == 0 {
h.auditP(p, "endpoint_patch", id, "ok", ip)
} else {
h.auditPD(p, "endpoint_patch", id, "ok", ip, detail)
}
httpx.WriteOK(w, row.toAPI(h.endpointLoginLocked(row.ID), true))
}
@@ -406,17 +414,17 @@ func (h *Handler) handleEndpointDelete(w http.ResponseWriter, r *http.Request) {
ok, err := h.deleteEndpointBasic(r.Context(), id)
if err != nil {
h.audit(actorString(p), "endpoint_delete", id, "error", ip)
h.auditP(p, "endpoint_delete", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
if !ok {
h.audit(actorString(p), "endpoint_delete", id, "not_found", ip)
h.auditP(p, "endpoint_delete", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "端不存在")
return
}
h.afterDeleteKick(r.Context(), id)
h.audit(actorString(p), "endpoint_delete", id, "ok", ip)
h.auditP(p, "endpoint_delete", id, "ok", ip)
httpx.WriteOK(w, map[string]any{})
}
@@ -429,14 +437,14 @@ func (h *Handler) handleEndpointBatch(w http.ResponseWriter, r *http.Request) {
Action string `json:"action"`
}
if err := httpx.DecodeJSON(r, &req); err != nil || len(req.IDs) == 0 {
h.audit(actorString(p), "endpoint_batch", "", "bad_request", ip)
h.auditP(p, "endpoint_batch", "", "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "请求体无效")
return
}
switch req.Action {
case "disable", "enable", "delete":
default:
h.audit(actorString(p), "endpoint_batch", "", "bad_request", ip)
h.auditP(p, "endpoint_batch", "", "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "action 无效")
return
}
@@ -470,7 +478,10 @@ func (h *Handler) handleEndpointBatch(w http.ResponseWriter, r *http.Request) {
}
okIDs = append(okIDs, id)
}
h.audit(actorString(p), "endpoint_batch_"+req.Action, strings.Join(okIDs, ","), "ok", ip)
h.auditPD(p, "endpoint_batch_"+req.Action, strings.Join(okIDs, ","), batchAuditResult(len(okIDs), len(failed)), ip, map[string]any{
"ok_ids": okIDs,
"failed": failed,
})
httpx.WriteOK(w, map[string]any{"ok_ids": okIDs, "failed": failed})
}
@@ -481,22 +492,22 @@ func (h *Handler) handleEndpointKick(w http.ResponseWriter, r *http.Request) {
exists, err := h.endpointExists(r.Context(), id)
if err != nil {
h.audit(actorString(p), "endpoint_kick", id, "error", ip)
h.auditP(p, "endpoint_kick", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
if !exists {
h.audit(actorString(p), "endpoint_kick", id, "not_found", ip)
h.auditP(p, "endpoint_kick", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "端不存在")
return
}
kicked, err := h.kickEndpoint(r.Context(), id)
if err != nil {
h.audit(actorString(p), "endpoint_kick", id, "error", ip)
h.auditP(p, "endpoint_kick", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
h.audit(actorString(p), "endpoint_kick", id, "ok", ip)
h.auditP(p, "endpoint_kick", id, "ok", ip)
httpx.WriteOK(w, map[string]any{"kicked": kicked})
}
@@ -509,12 +520,12 @@ func (h *Handler) handleEndpointResetLoginPassword(w http.ResponseWriter, r *htt
LoginPassword string `json:"login_password"`
}
if err := httpx.DecodeJSON(r, &req); err != nil {
h.audit(actorString(p), "endpoint_reset_login_password", id, "bad_request", ip)
h.auditP(p, "endpoint_reset_login_password", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "请求体无效")
return
}
if !protocol.ValidLoginPassword(req.LoginPassword) {
h.audit(actorString(p), "endpoint_reset_login_password", id, "bad_request", ip)
h.auditP(p, "endpoint_reset_login_password", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "登录密码不合法")
return
}
@@ -523,7 +534,7 @@ func (h *Handler) handleEndpointResetLoginPassword(w http.ResponseWriter, r *htt
if pw == "" {
gen, err := generateLoginPassword()
if err != nil {
h.audit(actorString(p), "endpoint_reset_login_password", id, "error", ip)
h.auditP(p, "endpoint_reset_login_password", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
@@ -531,23 +542,23 @@ func (h *Handler) handleEndpointResetLoginPassword(w http.ResponseWriter, r *htt
}
hash, err := h.hash.Hash(r.Context(), auth.PasswordLogin, pw)
if err != nil {
h.audit(actorString(p), "endpoint_reset_login_password", id, "error", ip)
h.auditP(p, "endpoint_reset_login_password", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
ok, err := h.resetLoginPassword(r.Context(), id, hash)
if err != nil {
h.audit(actorString(p), "endpoint_reset_login_password", id, "error", ip)
h.auditP(p, "endpoint_reset_login_password", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
if !ok {
h.audit(actorString(p), "endpoint_reset_login_password", id, "not_found", ip)
h.auditP(p, "endpoint_reset_login_password", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "端不存在")
return
}
_, _ = h.passwordResetKick(r.Context(), id)
h.audit(actorString(p), "endpoint_reset_login_password", id, "ok", ip)
h.auditP(p, "endpoint_reset_login_password", id, "ok", ip)
httpx.WriteOK(w, map[string]any{loginPasswordOnceKey: pw})
}
@@ -560,12 +571,12 @@ func (h *Handler) handleEndpointTalkPassword(w http.ResponseWriter, r *http.Requ
TalkPassword string `json:"talk_password"`
}
if err := httpx.DecodeJSON(r, &req); err != nil {
h.audit(actorString(p), "endpoint_talk_password", id, "bad_request", ip)
h.auditP(p, "endpoint_talk_password", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "请求体无效")
return
}
if !protocol.ValidTalkPassword(req.TalkPassword) {
h.audit(actorString(p), "endpoint_talk_password", id, "bad_request", ip)
h.auditP(p, "endpoint_talk_password", id, "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "对话密码不合法")
return
}
@@ -574,7 +585,7 @@ func (h *Handler) handleEndpointTalkPassword(w http.ResponseWriter, r *http.Requ
if req.TalkPassword != "" {
th, err := h.hash.Hash(r.Context(), auth.PasswordTalk, req.TalkPassword)
if err != nil {
h.audit(actorString(p), "endpoint_talk_password", id, "error", ip)
h.auditP(p, "endpoint_talk_password", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
@@ -582,16 +593,16 @@ func (h *Handler) handleEndpointTalkPassword(w http.ResponseWriter, r *http.Requ
}
ok, err := h.setTalkPassword(r.Context(), id, talkHash)
if err != nil {
h.audit(actorString(p), "endpoint_talk_password", id, "error", ip)
h.auditP(p, "endpoint_talk_password", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
if !ok {
h.audit(actorString(p), "endpoint_talk_password", id, "not_found", ip)
h.auditP(p, "endpoint_talk_password", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "端不存在")
return
}
h.audit(actorString(p), "endpoint_talk_password", id, "ok", ip)
h.auditP(p, "endpoint_talk_password", id, "ok", ip)
httpx.WriteOK(w, map[string]any{"talk_password_set": talkHash.Valid})
}
@@ -602,17 +613,17 @@ func (h *Handler) handleEndpointUnlock(w http.ResponseWriter, r *http.Request) {
exists, err := h.endpointExists(r.Context(), id)
if err != nil {
h.audit(actorString(p), "endpoint_unlock", id, "error", ip)
h.auditP(p, "endpoint_unlock", id, "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
if !exists {
h.audit(actorString(p), "endpoint_unlock", id, "not_found", ip)
h.auditP(p, "endpoint_unlock", id, "not_found", ip)
httpx.WriteError(w, http.StatusNotFound, "not_found", "端不存在")
return
}
h.locks.ClearEndpoint(id)
h.audit(actorString(p), "endpoint_unlock", id, "ok", ip)
h.auditP(p, "endpoint_unlock", id, "ok", ip)
httpx.WriteOK(w, map[string]any{})
}