68 lines
1.8 KiB
Go
68 lines
1.8 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"xorm.io/xorm"
|
|
|
|
"github.com/forgeo/forgebucket/internal/events"
|
|
"github.com/forgeo/forgebucket/internal/models"
|
|
)
|
|
|
|
// statusRecorder wraps ResponseWriter to capture the written status code.
|
|
type statusRecorder struct {
|
|
http.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
func (r *statusRecorder) WriteHeader(code int) {
|
|
r.status = code
|
|
r.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
// AuditLog middleware records every state-mutating request (POST/PUT/PATCH/DELETE)
|
|
// to the audit_log table and publishes an audit.event to the event bus.
|
|
// It runs after auth middleware so the actor is available in context.
|
|
func AuditLog(db *xorm.Engine, bus events.EventBus) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
|
|
next.ServeHTTP(rec, r)
|
|
|
|
actorID, _ := UserIDFromContext(r.Context())
|
|
actorName, _ := r.Context().Value(ContextKeyUsername).(string)
|
|
|
|
entry := &models.AuditLog{
|
|
ActorID: actorID,
|
|
ActorName: actorName,
|
|
Method: r.Method,
|
|
Path: r.URL.Path,
|
|
StatusCode: rec.status,
|
|
IPAddress: r.RemoteAddr,
|
|
UserAgent: r.UserAgent(),
|
|
OccurredAt: time.Now().UTC(),
|
|
}
|
|
|
|
go func() {
|
|
db.Insert(entry) //nolint:errcheck
|
|
bus.Publish(events.SubjectAuditEvent, events.AuditEvent{ //nolint:errcheck
|
|
ActorID: entry.ActorID,
|
|
ActorName: entry.ActorName,
|
|
Action: entry.Method + " " + entry.Path,
|
|
ResourcePath: entry.Path,
|
|
IPAddress: entry.IPAddress,
|
|
StatusCode: entry.StatusCode,
|
|
At: entry.OccurredAt,
|
|
})
|
|
}()
|
|
})
|
|
}
|
|
}
|