159 lines
3.9 KiB
Go
159 lines
3.9 KiB
Go
// Package auditlog provides a system for logging events that require permissions to the audit log
|
|
package auditlog
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.haelnorr.com/h/oslstats/internal/db"
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
type Logger struct {
|
|
conn *bun.DB
|
|
}
|
|
|
|
func NewLogger(conn *bun.DB) *Logger {
|
|
return &Logger{conn: conn}
|
|
}
|
|
|
|
// LogSuccess logs a successful permission-protected action
|
|
func (l *Logger) LogSuccess(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
user *db.User,
|
|
action string,
|
|
resourceType string,
|
|
resourceID any, // Can be int, string, or nil
|
|
details map[string]any,
|
|
r *http.Request,
|
|
) error {
|
|
return l.log(ctx, tx, user, action, resourceType, resourceID, details, "success", nil, r)
|
|
}
|
|
|
|
// LogError logs a failed action due to an error
|
|
func (l *Logger) LogError(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
user *db.User,
|
|
action string,
|
|
resourceType string,
|
|
resourceID any,
|
|
err error,
|
|
r *http.Request,
|
|
) error {
|
|
errMsg := err.Error()
|
|
return l.log(ctx, tx, user, action, resourceType, resourceID, nil, "error", &errMsg, r)
|
|
}
|
|
|
|
func (l *Logger) log(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
user *db.User,
|
|
action string,
|
|
resourceType string,
|
|
resourceID any,
|
|
details map[string]any,
|
|
result string,
|
|
errorMessage *string,
|
|
r *http.Request,
|
|
) error {
|
|
if user == nil {
|
|
return errors.New("user cannot be nil for audit logging")
|
|
}
|
|
|
|
// Convert resourceID to string
|
|
var resourceIDStr *string
|
|
if resourceID != nil {
|
|
idStr := fmt.Sprintf("%v", resourceID)
|
|
resourceIDStr = &idStr
|
|
}
|
|
|
|
// Marshal details to JSON
|
|
var detailsJSON json.RawMessage
|
|
if details != nil {
|
|
jsonBytes, err := json.Marshal(details)
|
|
if err != nil {
|
|
return errors.Wrap(err, "json.Marshal details")
|
|
}
|
|
detailsJSON = jsonBytes
|
|
}
|
|
|
|
// Extract IP and User-Agent from request
|
|
ipAddress := r.RemoteAddr
|
|
userAgent := r.UserAgent()
|
|
|
|
log := &db.AuditLog{
|
|
UserID: user.ID,
|
|
Action: action,
|
|
ResourceType: resourceType,
|
|
ResourceID: resourceIDStr,
|
|
Details: detailsJSON,
|
|
IPAddress: ipAddress,
|
|
UserAgent: userAgent,
|
|
Result: result,
|
|
ErrorMessage: errorMessage,
|
|
CreatedAt: time.Now().Unix(),
|
|
}
|
|
|
|
return db.CreateAuditLog(ctx, tx, log)
|
|
}
|
|
|
|
// GetRecentLogs retrieves recent audit logs with pagination
|
|
func (l *Logger) GetRecentLogs(ctx context.Context, pageOpts *db.PageOpts) (*db.AuditLogs, error) {
|
|
var logs *db.AuditLogs
|
|
if err := db.WithTxFailSilently(ctx, l.conn, func(ctx context.Context, tx bun.Tx) error {
|
|
var err error
|
|
logs, err = db.GetAuditLogs(ctx, tx, pageOpts, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "db.GetAuditLogs")
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "db.WithTxFailSilently")
|
|
}
|
|
return logs, nil
|
|
}
|
|
|
|
// GetLogsByUser retrieves audit logs for a specific user
|
|
func (l *Logger) GetLogsByUser(ctx context.Context, userID int, pageOpts *db.PageOpts) (*db.AuditLogs, error) {
|
|
var logs *db.AuditLogs
|
|
if err := db.WithTxFailSilently(ctx, l.conn, func(ctx context.Context, tx bun.Tx) error {
|
|
var err error
|
|
logs, err = db.GetAuditLogsByUser(ctx, tx, userID, pageOpts)
|
|
if err != nil {
|
|
return errors.Wrap(err, "db.GetAuditLogsByUser")
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "db.WithTxFailSilently")
|
|
}
|
|
return logs, nil
|
|
}
|
|
|
|
// CleanupOldLogs deletes audit logs older than the specified number of days
|
|
func (l *Logger) CleanupOldLogs(ctx context.Context, daysToKeep int) (int, error) {
|
|
if daysToKeep <= 0 {
|
|
return 0, errors.New("daysToKeep must be positive")
|
|
}
|
|
|
|
cutoffTime := time.Now().AddDate(0, 0, -daysToKeep).Unix()
|
|
|
|
var count int
|
|
if err := db.WithTxFailSilently(ctx, l.conn, func(ctx context.Context, tx bun.Tx) error {
|
|
var err error
|
|
count, err = db.CleanupOldAuditLogs(ctx, tx, cutoffTime)
|
|
if err != nil {
|
|
return errors.Wrap(err, "db.CleanupOldAuditLogs")
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return 0, errors.Wrap(err, "db.WithTxFailSilently")
|
|
}
|
|
return count, nil
|
|
}
|