3x-ui/web/middleware/audit.go

125 lines
2.7 KiB
Go
Raw Normal View History

package middleware
import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/web/service"
"github.com/mhsanaei/3x-ui/v2/web/session"
)
// AuditMiddleware logs all actions to audit log
func AuditMiddleware() gin.HandlerFunc {
auditService := service.AuditLogService{}
return func(c *gin.Context) {
// Skip audit for certain paths
path := c.Request.URL.Path
if shouldSkipAudit(path) {
c.Next()
return
}
// Get user info
user := session.GetLoginUser(c)
if user == nil {
c.Next()
return
}
// Log after request completes
c.Next()
// Extract action and resource from path
action, resource, resourceID := extractActionFromPath(c.Request.Method, path)
// Log the action
details := map[string]interface{}{
"method": c.Request.Method,
"path": path,
}
if err := auditService.LogAction(
user.Id,
user.Username,
action,
resource,
resourceID,
c.ClientIP(),
c.GetHeader("User-Agent"),
details,
); err != nil {
logger.Warning("Failed to log audit action:", err)
}
}
}
// shouldSkipAudit checks if path should be skipped from audit
func shouldSkipAudit(path string) bool {
skipPaths := []string{
"/assets/",
"/favicon.ico",
"/ws",
"/api/",
}
for _, skipPath := range skipPaths {
if len(path) >= len(skipPath) && path[:len(skipPath)] == skipPath {
return true
}
}
return false
}
// extractActionFromPath extracts action, resource and resource ID from path
func extractActionFromPath(method, path string) (action, resource string, resourceID int) {
// Map HTTP methods to actions
switch method {
case "POST":
if contains(path, "/add") || contains(path, "/create") {
action = "CREATE"
} else if contains(path, "/update") || contains(path, "/modify") {
action = "UPDATE"
} else {
action = "POST"
}
case "DELETE":
action = "DELETE"
case "GET":
action = "READ"
case "PUT":
action = "UPDATE"
default:
action = method
}
// Extract resource type
if contains(path, "/inbound") {
resource = "inbound"
} else if contains(path, "/client") {
resource = "client"
} else if contains(path, "/setting") {
resource = "setting"
} else if contains(path, "/user") {
resource = "user"
} else {
resource = "unknown"
}
// Extract resource ID if present (simplified)
// In production, parse from path parameters
return action, resource, 0
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || (len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || findSubstring(s, substr))))
}
func findSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}