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