Merge SEC-11: use json.Marshal for audit details

This commit is contained in:
2026-03-13 01:06:55 -07:00
5 changed files with 217 additions and 19 deletions

View File

@@ -19,6 +19,7 @@ import (
"net"
"net/http"
"git.wntrmute.dev/kyle/mcias/internal/audit"
"git.wntrmute.dev/kyle/mcias/internal/auth"
"git.wntrmute.dev/kyle/mcias/internal/config"
"git.wntrmute.dev/kyle/mcias/internal/crypto"
@@ -224,7 +225,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
// Security: return a generic error whether the user exists or not.
// Always run a dummy Argon2 check to prevent timing-based user enumeration.
_, _ = auth.VerifyPassword("dummy", auth.DummyHash())
s.writeAudit(r, model.EventLoginFail, nil, nil, fmt.Sprintf(`{"username":%q,"reason":"unknown_user"}`, req.Username))
s.writeAudit(r, model.EventLoginFail, nil, nil, audit.JSON("username", req.Username, "reason", "unknown_user"))
middleware.WriteError(w, http.StatusUnauthorized, "invalid credentials", "unauthorized")
return
}
@@ -327,7 +328,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
}
s.writeAudit(r, model.EventLoginOK, &acct.ID, nil, "")
s.writeAudit(r, model.EventTokenIssued, &acct.ID, nil, fmt.Sprintf(`{"jti":%q}`, claims.JTI))
s.writeAudit(r, model.EventTokenIssued, &acct.ID, nil, audit.JSON("jti", claims.JTI))
writeJSON(w, http.StatusOK, loginResponse{
Token: tokenStr,
@@ -342,7 +343,7 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
return
}
s.writeAudit(r, model.EventTokenRevoked, nil, nil, fmt.Sprintf(`{"jti":%q,"reason":"logout"}`, claims.JTI))
s.writeAudit(r, model.EventTokenRevoked, nil, nil, audit.JSON("jti", claims.JTI, "reason", "logout"))
w.WriteHeader(http.StatusNoContent)
}
@@ -388,7 +389,7 @@ func (s *Server) handleRenew(w http.ResponseWriter, r *http.Request) {
return
}
s.writeAudit(r, model.EventTokenRenewed, &acct.ID, nil, fmt.Sprintf(`{"old_jti":%q,"new_jti":%q}`, claims.JTI, newClaims.JTI))
s.writeAudit(r, model.EventTokenRenewed, &acct.ID, nil, audit.JSON("old_jti", claims.JTI, "new_jti", newClaims.JTI))
writeJSON(w, http.StatusOK, loginResponse{
Token: newTokenStr,
@@ -492,7 +493,7 @@ func (s *Server) handleTokenIssue(w http.ResponseWriter, r *http.Request) {
actorID = &a.ID
}
}
s.writeAudit(r, model.EventTokenIssued, actorID, &acct.ID, fmt.Sprintf(`{"jti":%q}`, claims.JTI))
s.writeAudit(r, model.EventTokenIssued, actorID, &acct.ID, audit.JSON("jti", claims.JTI))
writeJSON(w, http.StatusOK, loginResponse{
Token: tokenStr,
@@ -512,7 +513,7 @@ func (s *Server) handleTokenRevoke(w http.ResponseWriter, r *http.Request) {
return
}
s.writeAudit(r, model.EventTokenRevoked, nil, nil, fmt.Sprintf(`{"jti":%q}`, jti))
s.writeAudit(r, model.EventTokenRevoked, nil, nil, audit.JSON("jti", jti))
w.WriteHeader(http.StatusNoContent)
}
@@ -607,7 +608,7 @@ func (s *Server) handleCreateAccount(w http.ResponseWriter, r *http.Request) {
return
}
s.writeAudit(r, model.EventAccountCreated, nil, &acct.ID, fmt.Sprintf(`{"username":%q}`, acct.Username))
s.writeAudit(r, model.EventAccountCreated, nil, &acct.ID, audit.JSON("username", acct.Username))
writeJSON(w, http.StatusCreated, accountToResponse(acct))
}
@@ -722,7 +723,7 @@ func (s *Server) handleSetRoles(w http.ResponseWriter, r *http.Request) {
return
}
s.writeAudit(r, model.EventRoleGranted, grantedBy, &acct.ID, fmt.Sprintf(`{"roles":%v}`, req.Roles))
s.writeAudit(r, model.EventRoleGranted, grantedBy, &acct.ID, audit.JSONWithRoles(req.Roles))
w.WriteHeader(http.StatusNoContent)
}
@@ -755,7 +756,7 @@ func (s *Server) handleGrantRole(w http.ResponseWriter, r *http.Request) {
return
}
s.writeAudit(r, model.EventRoleGranted, grantedBy, &acct.ID, fmt.Sprintf(`{"role":"%s"}`, req.Role))
s.writeAudit(r, model.EventRoleGranted, grantedBy, &acct.ID, audit.JSON("role", req.Role))
w.WriteHeader(http.StatusNoContent)
}
@@ -784,7 +785,7 @@ func (s *Server) handleRevokeRole(w http.ResponseWriter, r *http.Request) {
return
}
s.writeAudit(r, model.EventRoleRevoked, revokedBy, &acct.ID, fmt.Sprintf(`{"role":"%s"}`, role))
s.writeAudit(r, model.EventRoleRevoked, revokedBy, &acct.ID, audit.JSON("role", role))
w.WriteHeader(http.StatusNoContent)
}