// Package auditlog provides security audit logging for compliance and security monitoring. // Audit logs are separate from application logs and capture security-relevant events // with consistent structure for analysis and alerting. package auditlog import ( "context" "time" "github.com/gocql/gocql" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation" ) // EventType represents the type of security event type EventType string const ( // Authentication events EventTypeLoginAttempt EventType = "login_attempt" EventTypeLoginSuccess EventType = "login_success" EventTypeLoginFailure EventType = "login_failure" EventTypeLogout EventType = "logout" EventTypeTokenRefresh EventType = "token_refresh" EventTypeTokenRevoked EventType = "token_revoked" // Account events EventTypeAccountCreated EventType = "account_created" EventTypeAccountDeleted EventType = "account_deleted" EventTypeAccountLocked EventType = "account_locked" EventTypeAccountUnlocked EventType = "account_unlocked" EventTypeEmailVerified EventType = "email_verified" // Recovery events EventTypeRecoveryInitiated EventType = "recovery_initiated" EventTypeRecoveryCompleted EventType = "recovery_completed" EventTypeRecoveryFailed EventType = "recovery_failed" // Access control events EventTypeAccessDenied EventType = "access_denied" EventTypePermissionChanged EventType = "permission_changed" // Sharing events EventTypeCollectionShared EventType = "collection_shared" EventTypeCollectionUnshared EventType = "collection_unshared" EventTypeSharingBlocked EventType = "sharing_blocked" ) // Outcome represents the result of the audited action type Outcome string const ( OutcomeSuccess Outcome = "success" OutcomeFailure Outcome = "failure" OutcomeBlocked Outcome = "blocked" ) // AuditEvent represents a security audit event type AuditEvent struct { Timestamp time.Time `json:"timestamp"` EventType EventType `json:"event_type"` Outcome Outcome `json:"outcome"` UserID string `json:"user_id,omitempty"` Email string `json:"email,omitempty"` // Always masked ClientIP string `json:"client_ip,omitempty"` UserAgent string `json:"user_agent,omitempty"` Resource string `json:"resource,omitempty"` Action string `json:"action,omitempty"` Details map[string]string `json:"details,omitempty"` FailReason string `json:"fail_reason,omitempty"` } // AuditLogger provides security audit logging functionality type AuditLogger interface { // Log records a security audit event Log(ctx context.Context, event AuditEvent) // LogAuth logs an authentication event with common fields LogAuth(ctx context.Context, eventType EventType, outcome Outcome, email string, clientIP string, details map[string]string) // LogAccess logs an access control event LogAccess(ctx context.Context, eventType EventType, outcome Outcome, userID string, resource string, action string, details map[string]string) } type auditLoggerImpl struct { logger *zap.Logger } // NewAuditLogger creates a new audit logger func NewAuditLogger(logger *zap.Logger) AuditLogger { // Create a named logger specifically for audit events // This allows filtering audit logs separately from application logs auditLogger := logger.Named("AUDIT") return &auditLoggerImpl{ logger: auditLogger, } } // Log records a security audit event func (a *auditLoggerImpl) Log(ctx context.Context, event AuditEvent) { // Set timestamp if not provided if event.Timestamp.IsZero() { event.Timestamp = time.Now().UTC() } // Build zap fields fields := []zap.Field{ zap.String("audit_event", string(event.EventType)), zap.String("outcome", string(event.Outcome)), zap.Time("event_time", event.Timestamp), } if event.UserID != "" { fields = append(fields, zap.String("user_id", event.UserID)) } if event.Email != "" { fields = append(fields, zap.String("email", validation.MaskEmail(event.Email))) // Always mask for safety } if event.ClientIP != "" { fields = append(fields, zap.String("client_ip", validation.MaskIP(event.ClientIP))) // Always mask for safety } if event.UserAgent != "" { fields = append(fields, zap.String("user_agent", event.UserAgent)) } if event.Resource != "" { fields = append(fields, zap.String("resource", event.Resource)) } if event.Action != "" { fields = append(fields, zap.String("action", event.Action)) } if event.FailReason != "" { fields = append(fields, zap.String("fail_reason", event.FailReason)) } if len(event.Details) > 0 { fields = append(fields, zap.Any("details", event.Details)) } // Try to get request ID from context if requestID, ok := ctx.Value(constants.SessionID).(string); ok && requestID != "" { fields = append(fields, zap.String("request_id", requestID)) } // Log at INFO level - audit events are always important a.logger.Info("security_audit", fields...) } // LogAuth logs an authentication event with common fields func (a *auditLoggerImpl) LogAuth(ctx context.Context, eventType EventType, outcome Outcome, email string, clientIP string, details map[string]string) { event := AuditEvent{ Timestamp: time.Now().UTC(), EventType: eventType, Outcome: outcome, Email: email, // Should be pre-masked by caller ClientIP: clientIP, Details: details, } // Extract user ID from context if available if userID, ok := ctx.Value(constants.SessionUserID).(gocql.UUID); ok { event.UserID = userID.String() } a.Log(ctx, event) } // LogAccess logs an access control event func (a *auditLoggerImpl) LogAccess(ctx context.Context, eventType EventType, outcome Outcome, userID string, resource string, action string, details map[string]string) { event := AuditEvent{ Timestamp: time.Now().UTC(), EventType: eventType, Outcome: outcome, UserID: userID, Resource: resource, Action: action, Details: details, } a.Log(ctx, event) }