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
+14 -11
View File
@@ -26,6 +26,7 @@ func (h *Handler) setSessionCookie(w http.ResponseWriter, r *http.Request, value
func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
ip := httpx.ClientIP(r, h.trusted)
if locked, retry := h.locks.Check(auth.LockKey{Kind: auth.LockAdminIP, IP: ip}); locked {
h.auditAuthFail(ip, "rate_limited")
w.Header().Set("Retry-After", formatRetryAfter(retry))
httpx.WriteError(w, http.StatusTooManyRequests, "rate_limited", "登录已锁定,请稍后再试")
return
@@ -78,10 +79,12 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
func (h *Handler) failLogin(w http.ResponseWriter, ip string) {
locked, retry := h.locks.Fail(auth.LockKey{Kind: auth.LockAdminIP, IP: ip})
if locked {
h.auditAuthFail(ip, "rate_limited")
w.Header().Set("Retry-After", formatRetryAfter(retry))
httpx.WriteError(w, http.StatusTooManyRequests, "rate_limited", "登录已锁定,请稍后再试")
return
}
h.auditAuthFail(ip, "bad_password")
httpx.WriteError(w, http.StatusUnauthorized, "unauthorized", "用户名或密码错误")
}
@@ -92,7 +95,7 @@ func (h *Handler) handleLogout(w http.ResponseWriter, r *http.Request) {
_ = h.deleteSession(r.Context(), hashSessionHex(p.Session))
}
h.setSessionCookie(w, r, "", -1)
h.audit(actorString(p), "logout", "", "ok", ip)
h.auditP(p, "logout", "", "ok", ip)
httpx.WriteOK(w, map[string]any{})
}
@@ -114,7 +117,7 @@ func (h *Handler) handlePassword(w http.ResponseWriter, r *http.Request) {
if locked, retry := h.locks.Check(auth.LockKey{Kind: auth.LockAdminIP, IP: ip}); locked {
w.Header().Set("Retry-After", formatRetryAfter(retry))
h.audit(actorString(p), "password_change", "", "rate_limited", ip)
h.auditP(p, "password_change", "", "rate_limited", ip)
httpx.WriteError(w, http.StatusTooManyRequests, "rate_limited", "登录已锁定,请稍后再试")
return
}
@@ -124,19 +127,19 @@ func (h *Handler) handlePassword(w http.ResponseWriter, r *http.Request) {
NewPassword string `json:"new_password"`
}
if err := httpx.DecodeJSON(r, &req); err != nil {
h.audit(actorString(p), "password_change", "", "bad_request", ip)
h.auditP(p, "password_change", "", "bad_request", ip)
writeDecodeError(w, err)
return
}
if utf8.RuneCountInString(req.NewPassword) < minPasswordLen {
h.audit(actorString(p), "password_change", "", "bad_request", ip)
h.auditP(p, "password_change", "", "bad_request", ip)
httpx.WriteError(w, http.StatusBadRequest, "bad_request", "新密码至少 12 位")
return
}
phc, err := h.getAdminPasswordHash(r.Context())
if err != nil {
h.audit(actorString(p), "password_change", "", "error", ip)
h.auditP(p, "password_change", "", "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
@@ -145,34 +148,34 @@ func (h *Handler) handlePassword(w http.ResponseWriter, r *http.Request) {
locked, retry := h.locks.Fail(auth.LockKey{Kind: auth.LockAdminIP, IP: ip})
if locked {
w.Header().Set("Retry-After", formatRetryAfter(retry))
h.audit(actorString(p), "password_change", "", "rate_limited", ip)
h.auditP(p, "password_change", "", "rate_limited", ip)
httpx.WriteError(w, http.StatusTooManyRequests, "rate_limited", "登录已锁定,请稍后再试")
return
}
h.audit(actorString(p), "password_change", "", "unauthorized", ip)
h.auditP(p, "password_change", "", "unauthorized", ip)
httpx.WriteError(w, http.StatusUnauthorized, "unauthorized", "旧密码错误")
return
}
h.locks.Clear(auth.LockKey{Kind: auth.LockAdminIP, IP: ip})
newPHC, err := h.hash.Hash(r.Context(), auth.PasswordAdmin, req.NewPassword)
if err != nil {
h.audit(actorString(p), "password_change", "", "error", ip)
h.auditP(p, "password_change", "", "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
if err := h.setAdminPasswordHash(r.Context(), newPHC); err != nil {
h.audit(actorString(p), "password_change", "", "error", ip)
h.auditP(p, "password_change", "", "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
// 保留当前会话,作废其它会话;失败则返回 500,避免其它会话继续有效。
if p.Session != "" {
if err := h.deleteOtherSessions(r.Context(), hashSessionHex(p.Session)); err != nil {
h.audit(actorString(p), "password_change", "", "error", ip)
h.auditP(p, "password_change", "", "error", ip)
httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
}
h.audit(actorString(p), "password_change", "", "ok", ip)
h.auditP(p, "password_change", "", "ok", ip)
httpx.WriteOK(w, map[string]any{})
}