mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-12-22 22:32:41 +00:00
175 lines
4.4 KiB
Go
175 lines
4.4 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/mhsanaei/3x-ui/v2/database"
|
|
"github.com/mhsanaei/3x-ui/v2/database/model"
|
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
|
)
|
|
|
|
// AuditLogService handles audit logging
|
|
type AuditLogService struct{}
|
|
|
|
// AuditAction represents an audit log entry
|
|
type AuditAction struct {
|
|
ID int `json:"id"`
|
|
UserID int `json:"user_id"`
|
|
Username string `json:"username"`
|
|
Action string `json:"action"` // CREATE, UPDATE, DELETE, LOGIN, LOGOUT, etc.
|
|
Resource string `json:"resource"` // inbound, client, setting, etc.
|
|
ResourceID int `json:"resource_id"`
|
|
IP string `json:"ip"`
|
|
UserAgent string `json:"user_agent"`
|
|
Details string `json:"details"` // JSON string with additional details
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// LogAction logs an audit action with error handling
|
|
func (s *AuditLogService) LogAction(userID int, username, action, resource string, resourceID int, ip, userAgent string, details map[string]interface{}) error {
|
|
db := database.GetDB()
|
|
|
|
detailsJSON := ""
|
|
if details != nil {
|
|
jsonData, err := json.Marshal(details)
|
|
if err != nil {
|
|
logger.Warning("Failed to marshal audit log details:", err)
|
|
} else {
|
|
detailsJSON = string(jsonData)
|
|
}
|
|
}
|
|
|
|
auditLog := model.AuditLog{
|
|
UserID: userID,
|
|
Username: username,
|
|
Action: action,
|
|
Resource: resource,
|
|
ResourceID: resourceID,
|
|
IP: ip,
|
|
UserAgent: userAgent,
|
|
Details: detailsJSON,
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
if err := db.Create(&auditLog).Error; err != nil {
|
|
logger.Warningf("Failed to create audit log: user=%d, action=%s, resource=%s, error=%v", userID, action, resource, err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAuditLogs retrieves audit logs with filters and pagination
|
|
func (s *AuditLogService) GetAuditLogs(userID, limit, offset int, action, resource string, startTime, endTime *time.Time) ([]AuditAction, int64, error) {
|
|
db := database.GetDB()
|
|
|
|
query := db.Model(&model.AuditLog{})
|
|
|
|
if userID > 0 {
|
|
query = query.Where("user_id = ?", userID)
|
|
}
|
|
if action != "" {
|
|
query = query.Where("action = ?", action)
|
|
}
|
|
if resource != "" {
|
|
query = query.Where("resource = ?", resource)
|
|
}
|
|
if startTime != nil {
|
|
query = query.Where("timestamp >= ?", startTime)
|
|
}
|
|
if endTime != nil {
|
|
query = query.Where("timestamp <= ?", endTime)
|
|
}
|
|
|
|
var total int64
|
|
if err := query.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
var logs []model.AuditLog
|
|
if err := query.Order("timestamp DESC").Limit(limit).Offset(offset).Find(&logs).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
actions := make([]AuditAction, len(logs))
|
|
for i, log := range logs {
|
|
actions[i] = AuditAction{
|
|
ID: log.ID,
|
|
UserID: log.UserID,
|
|
Username: log.Username,
|
|
Action: log.Action,
|
|
Resource: log.Resource,
|
|
ResourceID: log.ResourceID,
|
|
IP: log.IP,
|
|
UserAgent: log.UserAgent,
|
|
Details: log.Details,
|
|
Timestamp: log.Timestamp,
|
|
}
|
|
}
|
|
|
|
return actions, total, nil
|
|
}
|
|
|
|
// CleanOldLogs removes audit logs older than specified days
|
|
func (s *AuditLogService) CleanOldLogs(days int) error {
|
|
if days <= 0 {
|
|
return fmt.Errorf("days must be greater than 0")
|
|
}
|
|
|
|
db := database.GetDB()
|
|
cutoff := time.Now().AddDate(0, 0, -days)
|
|
|
|
result := db.Where("timestamp < ?", cutoff).Delete(&model.AuditLog{})
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
|
|
logger.Infof("Cleaned %d old audit logs (older than %d days)", result.RowsAffected, days)
|
|
return nil
|
|
}
|
|
|
|
// GetAuditStats returns statistics about audit logs
|
|
func (s *AuditLogService) GetAuditStats(startTime, endTime *time.Time) (map[string]interface{}, error) {
|
|
db := database.GetDB()
|
|
|
|
query := db.Model(&model.AuditLog{})
|
|
if startTime != nil {
|
|
query = query.Where("timestamp >= ?", startTime)
|
|
}
|
|
if endTime != nil {
|
|
query = query.Where("timestamp <= ?", endTime)
|
|
}
|
|
|
|
var totalLogs int64
|
|
if err := query.Count(&totalLogs).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Count by action
|
|
var actionCounts []struct {
|
|
Action string
|
|
Count int64
|
|
}
|
|
query.Select("action, COUNT(*) as count").
|
|
Group("action").
|
|
Scan(&actionCounts)
|
|
|
|
// Count by resource
|
|
var resourceCounts []struct {
|
|
Resource string
|
|
Count int64
|
|
}
|
|
query.Select("resource, COUNT(*) as count").
|
|
Group("resource").
|
|
Scan(&resourceCounts)
|
|
|
|
stats := map[string]interface{}{
|
|
"total_logs": totalLogs,
|
|
"action_counts": actionCounts,
|
|
"resource_counts": resourceCounts,
|
|
}
|
|
|
|
return stats, nil
|
|
}
|