Files
llmgw/internal/web/audit.go
T

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)
}
}