Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
86 lines
2.3 KiB
Go
86 lines
2.3 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/audit"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// SetAudit has every change made through these pages recorded.
|
|
func (p *AccountPages) SetAudit(s audit.Sink) { p.audit = s }
|
|
|
|
// auditNote is what a handler says about the change it made: whom it was
|
|
// about, and how it ended in words — pages answer 200 to a refusal too.
|
|
type auditNote struct{ subject, result, refused string }
|
|
|
|
type auditNoteKey struct{}
|
|
|
|
// noteAudit lets the handler of a change say whom it concerned and how it
|
|
// ended.
|
|
func noteAudit(r *http.Request, subject, result, refused string) {
|
|
if n, ok := r.Context().Value(auditNoteKey{}).(*auditNote); ok {
|
|
n.subject, n.result, n.refused = subject, result, refused
|
|
}
|
|
}
|
|
|
|
type statusRecorder struct {
|
|
http.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
func (w *statusRecorder) WriteHeader(code int) {
|
|
if w.status == 0 {
|
|
w.status = code
|
|
}
|
|
w.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
func (w *statusRecorder) Write(b []byte) (int, error) {
|
|
if w.status == 0 {
|
|
w.status = http.StatusOK
|
|
}
|
|
return w.ResponseWriter.Write(b)
|
|
}
|
|
|
|
// audited runs a change and records it: who, which page, the form with its
|
|
// secrets taken out, and how it ended.
|
|
func (p *AccountPages) audited(w http.ResponseWriter, r *http.Request, actor store.User,
|
|
run func(w http.ResponseWriter, r *http.Request),
|
|
) {
|
|
if p.audit == nil || r.Method == http.MethodGet || r.Method == http.MethodHead {
|
|
run(w, r)
|
|
return
|
|
}
|
|
note := &auditNote{}
|
|
r = r.WithContext(context.WithValue(r.Context(), auditNoteKey{}, note))
|
|
sw := &statusRecorder{ResponseWriter: w}
|
|
run(sw, r)
|
|
fields := audit.FormFields(r.PostForm)
|
|
if note.result != "" {
|
|
fields["result"] = note.result
|
|
}
|
|
if note.refused != "" {
|
|
fields["refused"] = note.refused
|
|
}
|
|
audit.Write(r.Context(), p.audit, p.log, store.AuditEvent{
|
|
At: time.Now(), Actor: actor.Email, Source: "web", Action: r.Method + " " + r.URL.Path,
|
|
Target: note.subject, Detail: audit.Redact(fields), Status: sw.status,
|
|
})
|
|
}
|
|
|
|
// selfAudited records a change a person makes to their own account: a key
|
|
// made or revoked, a password changed.
|
|
func (p *AccountPages) selfAudited(h http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
_, user, ok := p.session(r)
|
|
if !ok {
|
|
h(w, r)
|
|
return
|
|
}
|
|
p.audited(w, r, user, h)
|
|
}
|
|
}
|