107 lines
2.2 KiB
Go
107 lines
2.2 KiB
Go
package audit
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/models"
|
|
)
|
|
|
|
// Store manages audit logs
|
|
type Store struct {
|
|
mu sync.RWMutex
|
|
logs []models.AuditLog
|
|
nextID int64
|
|
maxLogs int // Maximum number of logs to keep (0 = unlimited)
|
|
}
|
|
|
|
// NewStore creates a new audit log store
|
|
func NewStore(maxLogs int) *Store {
|
|
return &Store{
|
|
logs: make([]models.AuditLog, 0),
|
|
nextID: 1,
|
|
maxLogs: maxLogs,
|
|
}
|
|
}
|
|
|
|
// Log records an audit log entry
|
|
func (s *Store) Log(actor, action, resource, result, message, ip, userAgent string) *models.AuditLog {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
id := fmt.Sprintf("audit-%d", s.nextID)
|
|
s.nextID++
|
|
|
|
entry := models.AuditLog{
|
|
ID: id,
|
|
Actor: actor,
|
|
Action: action,
|
|
Resource: resource,
|
|
Result: result,
|
|
Message: message,
|
|
IP: ip,
|
|
UserAgent: userAgent,
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
s.logs = append(s.logs, entry)
|
|
|
|
// Enforce max logs limit
|
|
if s.maxLogs > 0 && len(s.logs) > s.maxLogs {
|
|
// Remove oldest logs
|
|
excess := len(s.logs) - s.maxLogs
|
|
s.logs = s.logs[excess:]
|
|
}
|
|
|
|
return &entry
|
|
}
|
|
|
|
// List returns audit logs, optionally filtered
|
|
func (s *Store) List(actor, action, resource string, limit int) []models.AuditLog {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var filtered []models.AuditLog
|
|
for i := len(s.logs) - 1; i >= 0; i-- { // Reverse iteration (newest first)
|
|
log := s.logs[i]
|
|
|
|
if actor != "" && log.Actor != actor {
|
|
continue
|
|
}
|
|
if action != "" && log.Action != action {
|
|
continue
|
|
}
|
|
if resource != "" && !containsResource(log.Resource, resource) {
|
|
continue
|
|
}
|
|
|
|
filtered = append(filtered, log)
|
|
if limit > 0 && len(filtered) >= limit {
|
|
break
|
|
}
|
|
}
|
|
|
|
return filtered
|
|
}
|
|
|
|
// Get returns a specific audit log by ID
|
|
func (s *Store) Get(id string) (*models.AuditLog, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
for _, log := range s.logs {
|
|
if log.ID == id {
|
|
return &log, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("audit log %s not found", id)
|
|
}
|
|
|
|
// containsResource checks if resource string contains the search term
|
|
func containsResource(resource, search string) bool {
|
|
return resource == search ||
|
|
(len(resource) > len(search) && resource[:len(search)] == search)
|
|
}
|