// File Path: monorepo/cloud/maplepress-backend/internal/service/securityevent/logger.go package securityevent import ( "context" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/securityevent" ) // Logger handles logging of security events // CWE-778: Ensures sufficient logging of security events for audit and forensics type Logger interface { // LogEvent logs a security event LogEvent(ctx context.Context, event *securityevent.SecurityEvent) error // LogAccountLocked logs an account lockout event LogAccountLocked(ctx context.Context, emailHash, clientIP string, failedAttempts int, lockoutDuration string) error // LogAccountUnlocked logs an account unlock event LogAccountUnlocked(ctx context.Context, emailHash, unlockedBy string) error // LogFailedLogin logs a failed login attempt LogFailedLogin(ctx context.Context, emailHash, clientIP string, remainingAttempts int) error // LogExcessiveFailedLogin logs excessive failed login attempts LogExcessiveFailedLogin(ctx context.Context, emailHash, clientIP string, attemptCount int) error // LogSuccessfulLogin logs a successful login LogSuccessfulLogin(ctx context.Context, emailHash, clientIP string) error // LogIPRateLimitExceeded logs IP rate limit exceeded LogIPRateLimitExceeded(ctx context.Context, clientIP string) error } type securityEventLogger struct { logger *zap.Logger } // NewSecurityEventLogger creates a new security event logger func NewSecurityEventLogger(logger *zap.Logger) Logger { return &securityEventLogger{ logger: logger.Named("security-events"), } } // ProvideSecurityEventLogger provides a SecurityEventLogger for dependency injection func ProvideSecurityEventLogger(logger *zap.Logger) Logger { return NewSecurityEventLogger(logger) } // LogEvent logs a security event func (s *securityEventLogger) LogEvent(ctx context.Context, event *securityevent.SecurityEvent) error { // Map severity to log level logFunc := s.logger.Info switch event.Severity { case securityevent.SeverityLow: logFunc = s.logger.Info case securityevent.SeverityMedium: logFunc = s.logger.Warn case securityevent.SeverityHigh, securityevent.SeverityCritical: logFunc = s.logger.Error } // Build log fields fields := []zap.Field{ zap.String("event_id", event.ID), zap.String("event_type", string(event.EventType)), zap.String("severity", string(event.Severity)), zap.String("email_hash", event.EmailHash), zap.String("client_ip", event.ClientIP), zap.Time("timestamp", event.Timestamp), } if event.UserAgent != "" { fields = append(fields, zap.String("user_agent", event.UserAgent)) } // Add metadata fields for key, value := range event.Metadata { fields = append(fields, zap.Any(key, value)) } logFunc(event.Message, fields...) // TODO: In production, also persist to a security event database/SIEM // This could be implemented as a repository pattern: // - Store in Cassandra for long-term retention // - Send to SIEM (Splunk, ELK, etc.) for analysis // - Send to monitoring/alerting system return nil } // LogAccountLocked logs an account lockout event func (s *securityEventLogger) LogAccountLocked(ctx context.Context, emailHash, clientIP string, failedAttempts int, lockoutDuration string) error { event := securityevent.NewSecurityEvent( securityevent.EventTypeAccountLocked, securityevent.SeverityHigh, emailHash, clientIP, "Account locked due to excessive failed login attempts", ) event.WithMetadata("failed_attempts", failedAttempts) event.WithMetadata("lockout_duration", lockoutDuration) return s.LogEvent(ctx, event) } // LogAccountUnlocked logs an account unlock event func (s *securityEventLogger) LogAccountUnlocked(ctx context.Context, emailHash, unlockedBy string) error { event := securityevent.NewSecurityEvent( securityevent.EventTypeAccountUnlocked, securityevent.SeverityMedium, emailHash, "", "Account manually unlocked by administrator", ) event.WithMetadata("unlocked_by", unlockedBy) return s.LogEvent(ctx, event) } // LogFailedLogin logs a failed login attempt func (s *securityEventLogger) LogFailedLogin(ctx context.Context, emailHash, clientIP string, remainingAttempts int) error { event := securityevent.NewSecurityEvent( securityevent.EventTypeFailedLogin, securityevent.SeverityMedium, emailHash, clientIP, "Failed login attempt - invalid credentials", ) event.WithMetadata("remaining_attempts", remainingAttempts) return s.LogEvent(ctx, event) } // LogExcessiveFailedLogin logs excessive failed login attempts func (s *securityEventLogger) LogExcessiveFailedLogin(ctx context.Context, emailHash, clientIP string, attemptCount int) error { event := securityevent.NewSecurityEvent( securityevent.EventTypeExcessiveFailedLogin, securityevent.SeverityHigh, emailHash, clientIP, "Excessive failed login attempts detected", ) event.WithMetadata("attempt_count", attemptCount) return s.LogEvent(ctx, event) } // LogSuccessfulLogin logs a successful login func (s *securityEventLogger) LogSuccessfulLogin(ctx context.Context, emailHash, clientIP string) error { event := securityevent.NewSecurityEvent( securityevent.EventTypeSuccessfulLogin, securityevent.SeverityLow, emailHash, clientIP, "Successful login", ) return s.LogEvent(ctx, event) } // LogIPRateLimitExceeded logs IP rate limit exceeded func (s *securityEventLogger) LogIPRateLimitExceeded(ctx context.Context, clientIP string) error { event := securityevent.NewSecurityEvent( securityevent.EventTypeIPRateLimitExceeded, securityevent.SeverityMedium, "", clientIP, "IP rate limit exceeded for login attempts", ) return s.LogEvent(ctx, event) }