mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-12-23 23:02:42 +00:00
125 lines
2.7 KiB
Go
125 lines
2.7 KiB
Go
|
|
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
|
||
|
|
}
|