1use chrono::Utc;
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub enum AuditEventType {
8 UserLogin,
10 UserLogout,
11 UserRegistration,
12 TokenRefresh,
13 AuthenticationFailed,
14
15 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 BoardMemberElected,
45 BoardMemberRemoved,
46 BoardMemberMandateRenewed,
47 BoardDecisionCreated,
48 BoardDecisionUpdated,
49 BoardDecisionCompleted,
50 BoardDecisionNotesAdded,
51
52 ResolutionCreated,
54 ResolutionDeleted,
55 VoteCast,
56 VoteChanged,
57 VotingClosed,
58
59 TicketCreated,
61 TicketAssigned,
62 TicketStatusChanged,
63 TicketResolved,
64 TicketClosed,
65 TicketCancelled,
66 TicketReopened,
67 TicketDeleted,
68 TicketWorkOrderSent,
69
70 NotificationCreated,
72 NotificationRead,
73 NotificationDeleted,
74 NotificationPreferenceUpdated,
75
76 PaymentCreated,
78 PaymentProcessing,
79 PaymentRequiresAction,
80 PaymentSucceeded,
81 PaymentFailed,
82 PaymentCancelled,
83 PaymentRefunded,
84 PaymentDeleted,
85
86 PaymentMethodCreated,
88 PaymentMethodUpdated,
89 PaymentMethodSetDefault,
90 PaymentMethodDeactivated,
91 PaymentMethodReactivated,
92 PaymentMethodDeleted,
93
94 ConvocationCreated,
96 ConvocationScheduled,
97 ConvocationSent,
98 ConvocationCancelled,
99 ConvocationDeleted,
100 ConvocationReminderSent,
101 ConvocationAttendanceUpdated,
102 ConvocationProxySet,
103 SecondConvocationScheduled,
104
105 QuoteCreated,
107 QuoteSubmitted,
108 QuoteUnderReview,
109 QuoteAccepted,
110 QuoteRejected,
111 QuoteWithdrawn,
112 QuoteExpired,
113 QuoteRatingUpdated,
114 QuoteComparisonPerformed,
115 QuoteDeleted,
116
117 ExchangeCreated,
119 ExchangeRequested,
120 ExchangeStarted,
121 ExchangeCompleted,
122 ExchangeCancelled,
123 ExchangeProviderRated,
124 ExchangeRequesterRated,
125 ExchangeDeleted,
126 CreditBalanceUpdated,
127 CreditBalanceCreated,
128
129 NoticeCreated,
131 NoticeUpdated,
132 NoticePublished,
133 NoticeArchived,
134 NoticePinned,
135 NoticeUnpinned,
136 NoticeExpirationSet,
137 NoticeExpired,
138 NoticeDeleted,
139
140 SkillCreated,
142 SkillUpdated,
143 SkillMarkedAvailable,
144 SkillMarkedUnavailable,
145 SkillDeleted,
146
147 SharedObjectCreated,
149 SharedObjectUpdated,
150 SharedObjectMarkedAvailable,
151 SharedObjectMarkedUnavailable,
152 SharedObjectBorrowed,
153 SharedObjectReturned,
154 SharedObjectDeleted,
155
156 ResourceBookingCreated,
158 ResourceBookingUpdated,
159 ResourceBookingCancelled,
160 ResourceBookingCompleted,
161 ResourceBookingNoShow,
162 ResourceBookingConfirmed,
163 ResourceBookingDeleted,
164
165 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 PaymentReminderCreated,
181 PaymentReminderSent,
182 PaymentReminderOpened,
183 PaymentReminderPaid,
184 PaymentReminderCancelled,
185 PaymentReminderEscalated,
186 PaymentReminderTrackingAdded,
187 PaymentRemindersBulkCreated,
188 PaymentReminderDeleted,
189
190 EtatDateCreated,
192 EtatDateInProgress,
193 EtatDateGenerated,
194 EtatDateDelivered,
195 EtatDateFinancialUpdate,
196 EtatDateAdditionalDataUpdate,
197 EtatDateDeleted,
198
199 BudgetCreated,
201 BudgetUpdated,
202 BudgetSubmitted,
203 BudgetApproved,
204 BudgetRejected,
205 BudgetArchived,
206 BudgetDeleted,
207
208 WorkReportCreated,
210 WorkReportUpdated,
211 WorkReportDeleted,
212 WorkReportPhotoAdded,
213 WorkReportDocumentAdded,
214
215 TechnicalInspectionCreated,
217 TechnicalInspectionUpdated,
218 TechnicalInspectionDeleted,
219 TechnicalInspectionReportAdded,
220 TechnicalInspectionPhotoAdded,
221 TechnicalInspectionCertificateAdded,
222
223 UnauthorizedAccess,
225 RateLimitExceeded,
226 InvalidToken,
227
228 TwoFactorSetupInitiated,
230 TwoFactorEnabled,
231 TwoFactorDisabled,
232 TwoFactorVerified,
233 TwoFactorVerificationFailed,
234 BackupCodeUsed,
235 BackupCodesRegenerated,
236 TwoFactorReverificationRequired,
237
238 IoTReadingCreated,
240 IoTReadingsBulkCreated,
241 LinkyDeviceConfigured,
242 LinkyDataSynced,
243 LinkyDeviceDeleted,
244 LinkySyncToggled,
245
246 GdprDataExported,
248 GdprDataExportFailed,
249 GdprDataErased,
250 GdprDataErasureFailed,
251 GdprErasureCheckRequested,
252 GdprDataRectified,
254 GdprDataRectificationFailed,
255 GdprProcessingRestricted,
257 GdprProcessingRestrictionFailed,
258 GdprMarketingOptOut,
260 GdprMarketingOptIn,
261 GdprMarketingPreferenceChangeFailed,
262
263 ConsentRecorded,
265 ConsentStatusChecked,
266
267 SecurityIncidentReported,
269
270 AccountCreated,
272 AccountUpdated,
273 AccountDeleted,
274 BelgianPCMNSeeded,
275
276 ReportGenerated,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct AuditLogEntry {
283 pub id: Uuid,
285 pub timestamp: chrono::DateTime<chrono::Utc>,
287 pub event_type: AuditEventType,
289 pub user_id: Option<Uuid>,
291 pub organization_id: Option<Uuid>,
293 pub resource_type: Option<String>,
295 pub resource_id: Option<Uuid>,
297 pub ip_address: Option<String>,
299 pub user_agent: Option<String>,
301 pub metadata: Option<serde_json::Value>,
303 pub success: bool,
305 pub error_message: Option<String>,
307}
308
309impl AuditLogEntry {
310 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 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 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 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
352 self.metadata = Some(metadata);
353 self
354 }
355
356 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 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 pub fn log(&self) {
372 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 }
409}
410
411pub 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#[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}