koprogo_api/infrastructure/
audit.rs

1use chrono::Utc;
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// Audit log event types
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub enum AuditEventType {
8    // Authentication events
9    UserLogin,
10    UserLogout,
11    UserRegistration,
12    TokenRefresh,
13    AuthenticationFailed,
14
15    // Data modification events
16    BuildingCreated,
17    BuildingUpdated,
18    BuildingDeleted,
19    JournalEntryCreated,
20    JournalEntryDeleted,
21    UnitCreated,
22    UnitUpdated,
23    UnitDeleted,
24    UnitAssignedToOwner,
25    UnitOwnerCreated,
26    UnitOwnerUpdated,
27    UnitOwnerDeleted,
28    OwnerCreated,
29    OwnerUpdated,
30    ExpenseCreated,
31    ExpenseMarkedPaid,
32    InvoiceUpdated,
33    InvoiceSubmitted,
34    InvoiceApproved,
35    InvoiceRejected,
36    MeetingCreated,
37    MeetingCompleted,
38    MeetingQuorumValidated,
39    MeetingMinutesSent,
40    DocumentUploaded,
41    DocumentDeleted,
42
43    // Board management events
44    BoardMemberElected,
45    BoardMemberRemoved,
46    BoardMemberMandateRenewed,
47    BoardDecisionCreated,
48    BoardDecisionUpdated,
49    BoardDecisionCompleted,
50    BoardDecisionNotesAdded,
51
52    // Voting events (Issue #46 - Phase 2)
53    ResolutionCreated,
54    ResolutionDeleted,
55    VoteCast,
56    VoteChanged,
57    VotingClosed,
58
59    // Ticketing events (Issue #85 - Phase 2)
60    TicketCreated,
61    TicketAssigned,
62    TicketStatusChanged,
63    TicketResolved,
64    TicketClosed,
65    TicketCancelled,
66    TicketReopened,
67    TicketDeleted,
68    TicketWorkOrderSent,
69
70    // Notification events (Issue #86 - Phase 2)
71    NotificationCreated,
72    NotificationRead,
73    NotificationDeleted,
74    NotificationPreferenceUpdated,
75
76    // Payment events (Issue #84 - Phase 2)
77    PaymentCreated,
78    PaymentProcessing,
79    PaymentRequiresAction,
80    PaymentSucceeded,
81    PaymentFailed,
82    PaymentCancelled,
83    PaymentRefunded,
84    PaymentDeleted,
85
86    // Payment method events (Issue #84 - Phase 2)
87    PaymentMethodCreated,
88    PaymentMethodUpdated,
89    PaymentMethodSetDefault,
90    PaymentMethodDeactivated,
91    PaymentMethodReactivated,
92    PaymentMethodDeleted,
93
94    // Convocation events (Issue #88 - Phase 2)
95    ConvocationCreated,
96    ConvocationScheduled,
97    ConvocationSent,
98    ConvocationCancelled,
99    ConvocationDeleted,
100    ConvocationReminderSent,
101    ConvocationAttendanceUpdated,
102    ConvocationProxySet,
103    SecondConvocationScheduled,
104
105    // Quote events (Contractor Quotes Module - Issue #91 - Phase 2)
106    QuoteCreated,
107    QuoteSubmitted,
108    QuoteUnderReview,
109    QuoteAccepted,
110    QuoteRejected,
111    QuoteWithdrawn,
112    QuoteExpired,
113    QuoteRatingUpdated,
114    QuoteComparisonPerformed,
115    QuoteDeleted,
116
117    // SEL events (Local Exchange Trading System - Issue #49 - Phase 2)
118    ExchangeCreated,
119    ExchangeRequested,
120    ExchangeStarted,
121    ExchangeCompleted,
122    ExchangeCancelled,
123    ExchangeProviderRated,
124    ExchangeRequesterRated,
125    ExchangeDeleted,
126    CreditBalanceUpdated,
127    CreditBalanceCreated,
128
129    // Notice events (Community Notice Board - Issue #49 - Phase 2)
130    NoticeCreated,
131    NoticeUpdated,
132    NoticePublished,
133    NoticeArchived,
134    NoticePinned,
135    NoticeUnpinned,
136    NoticeExpirationSet,
137    NoticeExpired,
138    NoticeDeleted,
139
140    // Skill events (Skills Directory - Issue #49 - Phase 3)
141    SkillCreated,
142    SkillUpdated,
143    SkillMarkedAvailable,
144    SkillMarkedUnavailable,
145    SkillDeleted,
146
147    // Shared Object events (Object Sharing Library - Issue #49 - Phase 4)
148    SharedObjectCreated,
149    SharedObjectUpdated,
150    SharedObjectMarkedAvailable,
151    SharedObjectMarkedUnavailable,
152    SharedObjectBorrowed,
153    SharedObjectReturned,
154    SharedObjectDeleted,
155
156    // Resource Booking events (Resource Booking Calendar - Issue #49 - Phase 5)
157    ResourceBookingCreated,
158    ResourceBookingUpdated,
159    ResourceBookingCancelled,
160    ResourceBookingCompleted,
161    ResourceBookingNoShow,
162    ResourceBookingConfirmed,
163    ResourceBookingDeleted,
164
165    // Gamification events (Achievements & Challenges - Issue #49 - Phase 6)
166    AchievementCreated,
167    AchievementUpdated,
168    AchievementDeleted,
169    AchievementAwarded,
170    ChallengeCreated,
171    ChallengeActivated,
172    ChallengeUpdated,
173    ChallengeCompleted,
174    ChallengeCancelled,
175    ChallengeDeleted,
176    ChallengeProgressIncremented,
177    ChallengeProgressCompleted,
178
179    // Payment reminder events
180    PaymentReminderCreated,
181    PaymentReminderSent,
182    PaymentReminderOpened,
183    PaymentReminderPaid,
184    PaymentReminderCancelled,
185    PaymentReminderEscalated,
186    PaymentReminderTrackingAdded,
187    PaymentRemindersBulkCreated,
188    PaymentReminderDeleted,
189
190    // État Daté events (Belgian legal requirement for property sales)
191    EtatDateCreated,
192    EtatDateInProgress,
193    EtatDateGenerated,
194    EtatDateDelivered,
195    EtatDateFinancialUpdate,
196    EtatDateAdditionalDataUpdate,
197    EtatDateDeleted,
198
199    // Budget events (Annual budget management)
200    BudgetCreated,
201    BudgetUpdated,
202    BudgetSubmitted,
203    BudgetApproved,
204    BudgetRejected,
205    BudgetArchived,
206    BudgetDeleted,
207
208    // Work Report events (Digital Maintenance Logbook - Issue #134)
209    WorkReportCreated,
210    WorkReportUpdated,
211    WorkReportDeleted,
212    WorkReportPhotoAdded,
213    WorkReportDocumentAdded,
214
215    // Technical Inspection events (Digital Maintenance Logbook - Issue #134)
216    TechnicalInspectionCreated,
217    TechnicalInspectionUpdated,
218    TechnicalInspectionDeleted,
219    TechnicalInspectionReportAdded,
220    TechnicalInspectionPhotoAdded,
221    TechnicalInspectionCertificateAdded,
222
223    // Security events
224    UnauthorizedAccess,
225    RateLimitExceeded,
226    InvalidToken,
227
228    // Two-Factor Authentication events (Issue #78 - Security Hardening)
229    TwoFactorSetupInitiated,
230    TwoFactorEnabled,
231    TwoFactorDisabled,
232    TwoFactorVerified,
233    TwoFactorVerificationFailed,
234    BackupCodeUsed,
235    BackupCodesRegenerated,
236    TwoFactorReverificationRequired,
237
238    // IoT events (Linky/Ores Smart Meter Integration - Issue #133 - IoT Phase 0)
239    IoTReadingCreated,
240    IoTReadingsBulkCreated,
241    LinkyDeviceConfigured,
242    LinkyDataSynced,
243    LinkyDeviceDeleted,
244    LinkySyncToggled,
245
246    // GDPR events (Data Privacy Compliance - Article 30: Records of Processing)
247    GdprDataExported,
248    GdprDataExportFailed,
249    GdprDataErased,
250    GdprDataErasureFailed,
251    GdprErasureCheckRequested,
252    // GDPR Article 16: Right to Rectification
253    GdprDataRectified,
254    GdprDataRectificationFailed,
255    // GDPR Article 18: Right to Restriction of Processing
256    GdprProcessingRestricted,
257    GdprProcessingRestrictionFailed,
258    // GDPR Article 21: Right to Object (Marketing)
259    GdprMarketingOptOut,
260    GdprMarketingOptIn,
261    GdprMarketingPreferenceChangeFailed,
262
263    // GDPR Article 7: Consent Management (Issue #337)
264    ConsentRecorded,
265    ConsentStatusChecked,
266
267    // GDPR Article 33: Security Incidents & APD Notification (Issue #317)
268    SecurityIncidentReported,
269
270    // Accounting events
271    AccountCreated,
272    AccountUpdated,
273    AccountDeleted,
274    BelgianPCMNSeeded,
275
276    // Financial reporting events
277    ReportGenerated,
278}
279
280/// Audit log entry
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct AuditLogEntry {
283    /// Unique ID for this audit entry
284    pub id: Uuid,
285    /// Timestamp of the event
286    pub timestamp: chrono::DateTime<chrono::Utc>,
287    /// Type of event
288    pub event_type: AuditEventType,
289    /// User ID who performed the action (if authenticated)
290    pub user_id: Option<Uuid>,
291    /// Organization ID (for multi-tenant isolation)
292    pub organization_id: Option<Uuid>,
293    /// Resource type affected (e.g., "Building", "Unit")
294    pub resource_type: Option<String>,
295    /// Resource ID affected
296    pub resource_id: Option<Uuid>,
297    /// IP address of the client
298    pub ip_address: Option<String>,
299    /// User agent string
300    pub user_agent: Option<String>,
301    /// Additional metadata as JSON
302    pub metadata: Option<serde_json::Value>,
303    /// Success or failure
304    pub success: bool,
305    /// Error message if failed
306    pub error_message: Option<String>,
307}
308
309impl AuditLogEntry {
310    /// Create a new audit log entry
311    pub fn new(
312        event_type: AuditEventType,
313        user_id: Option<Uuid>,
314        organization_id: Option<Uuid>,
315    ) -> Self {
316        Self {
317            id: Uuid::new_v4(),
318            timestamp: Utc::now(),
319            event_type,
320            user_id,
321            organization_id,
322            resource_type: None,
323            resource_id: None,
324            ip_address: None,
325            user_agent: None,
326            metadata: None,
327            success: true,
328            error_message: None,
329        }
330    }
331
332    /// Set resource information
333    pub fn with_resource(mut self, resource_type: &str, resource_id: Uuid) -> Self {
334        self.resource_type = Some(resource_type.to_string());
335        self.resource_id = Some(resource_id);
336        self
337    }
338
339    /// Set client information
340    pub fn with_client_info(
341        mut self,
342        ip_address: Option<String>,
343        user_agent: Option<String>,
344    ) -> Self {
345        self.ip_address = ip_address;
346        self.user_agent = user_agent;
347        self
348    }
349
350    /// Set metadata
351    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
352        self.metadata = Some(metadata);
353        self
354    }
355
356    /// Mark as failed with error message
357    pub fn with_error(mut self, error_message: String) -> Self {
358        self.success = false;
359        self.error_message = Some(error_message);
360        self
361    }
362
363    /// Add details to metadata as a string
364    pub fn with_details(mut self, details: String) -> Self {
365        let details_json = serde_json::json!({ "details": details });
366        self.metadata = Some(details_json);
367        self
368    }
369
370    /// Log this entry (currently to stdout, can be extended to database/file)
371    pub fn log(&self) {
372        // Redact sensitive information for logging (GDPR compliance)
373        let redact_presence = |present| if present { "[REDACTED]" } else { "None" };
374        let redacted_user = redact_presence(self.user_id.is_some());
375        let redacted_org = redact_presence(self.organization_id.is_some());
376        let redacted_resource_id = redact_presence(self.resource_id.is_some());
377        let redacted_ip = redact_presence(self.ip_address.is_some());
378        let redacted_error = self.error_message.as_ref().map(|_| "[REDACTED]");
379
380        let log_message = format!(
381            "[AUDIT] {} | {:?} | User: {} | Org: {} | Resource: {}/{} | Success: {} | IP: {}",
382            self.timestamp.format("%Y-%m-%d %H:%M:%S"),
383            self.event_type,
384            redacted_user,
385            redacted_org,
386            self.resource_type.as_deref().unwrap_or("None"),
387            redacted_resource_id,
388            self.success,
389            redacted_ip
390        );
391
392        if self.success {
393            log::info!("{}", log_message);
394        } else {
395            log::warn!(
396                "{} | Error: {}",
397                log_message,
398                redacted_error.unwrap_or("None")
399            );
400        }
401
402        // TODO: In production, write full (unredacted) audit data to:
403        // - Database table (audit_logs) with encryption at rest
404        // - Rotating log files in secure location with restricted access
405        // - SIEM system (Security Information and Event Management)
406        // Note: Full audit data (including IP, error messages) should only be
407        // stored in secure, access-controlled systems for compliance and forensics
408    }
409}
410
411/// Helper function to log audit events asynchronously
412///
413/// This is a convenience function for background audit logging
414/// without database persistence (logs to stdout/file only).
415///
416/// Parameters:
417/// - event_type: Type of audit event
418/// - user_id: Optional user ID who performed the action
419/// - organization_id: Optional organization ID for multi-tenant isolation
420/// - details: Optional details string
421/// - metadata: Optional additional metadata as JSON
422pub async fn log_audit_event(
423    event_type: AuditEventType,
424    user_id: Option<Uuid>,
425    organization_id: Option<Uuid>,
426    details: Option<String>,
427    metadata: Option<serde_json::Value>,
428) {
429    let mut entry = AuditLogEntry::new(event_type, user_id, organization_id);
430
431    if let Some(details_str) = details {
432        entry = entry.with_details(details_str);
433    }
434
435    if let Some(meta) = metadata {
436        entry.metadata = Some(meta);
437    }
438
439    entry.log();
440}
441
442/// Helper macro to create and log audit entries
443#[macro_export]
444macro_rules! audit_log {
445    ($event_type:expr, $user_id:expr, $org_id:expr) => {
446        $crate::infrastructure::audit::AuditLogEntry::new($event_type, $user_id, $org_id).log()
447    };
448    ($event_type:expr, $user_id:expr, $org_id:expr, $resource_type:expr, $resource_id:expr) => {
449        $crate::infrastructure::audit::AuditLogEntry::new($event_type, $user_id, $org_id)
450            .with_resource($resource_type, $resource_id)
451            .log()
452    };
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn test_audit_log_creation() {
461        let user_id = Uuid::new_v4();
462        let org_id = Uuid::new_v4();
463        let building_id = Uuid::new_v4();
464
465        let entry =
466            AuditLogEntry::new(AuditEventType::BuildingCreated, Some(user_id), Some(org_id))
467                .with_resource("Building", building_id)
468                .with_client_info(Some("192.168.1.1".to_string()), None);
469
470        assert_eq!(entry.user_id, Some(user_id));
471        assert_eq!(entry.organization_id, Some(org_id));
472        assert_eq!(entry.resource_id, Some(building_id));
473        assert!(entry.success);
474    }
475
476    #[test]
477    fn test_audit_log_with_error() {
478        let entry = AuditLogEntry::new(AuditEventType::AuthenticationFailed, None, None)
479            .with_error("Invalid credentials".to_string());
480
481        assert!(!entry.success);
482        assert_eq!(entry.error_message, Some("Invalid credentials".to_string()));
483    }
484}