Files
GovAI/server/internal/middleware/audit.go
T
2026-06-15 23:48:37 +08:00

66 lines
1.4 KiB
Go

package middleware
import (
"context"
"encoding/json"
"net"
"net/http"
"github.com/jackc/pgx/v5/pgxpool"
)
// extractIP returns a clean IP without port. Falls back to "" so the INET
// column can take NULL via $5 when the value is empty.
func extractIP(r *http.Request) any {
addr := r.RemoteAddr
if host, _, err := net.SplitHostPort(addr); err == nil {
addr = host
}
if addr == "" {
return nil
}
return addr
}
// AuditLog records API access to the audit_logs table for important operations.
func AuditLog(pool *pgxpool.Pool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
// Only audit write operations
if r.Method == "GET" || r.Method == "OPTIONS" {
return
}
userID := GetUserID(r.Context())
if userID.String() == "00000000-0000-0000-0000-000000000000" {
return
}
details, _ := json.Marshal(map[string]string{
"method": r.Method,
"path": r.URL.Path,
})
ip := extractIP(r)
ua := r.UserAgent()
method := r.Method
path := r.URL.Path
go func() {
_, _ = pool.Exec(context.Background(),
`INSERT INTO audit_logs (user_id, action, resource_type, resource_id, details, ip_address, user_agent)
VALUES ($1, $2, $3, NULL, $4, $5, $6)`,
userID,
method+"."+path,
"api",
details,
ip,
ua,
)
}()
})
}
}