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 MeetingMinutesSent,
39 DocumentUploaded,
40 DocumentDeleted,
41
42 BoardMemberElected,
44 BoardMemberRemoved,
45 BoardMemberMandateRenewed,
46 BoardDecisionCreated,
47 BoardDecisionUpdated,
48 BoardDecisionCompleted,
49 BoardDecisionNotesAdded,
50
51 ResolutionCreated,
53 ResolutionDeleted,
54 VoteCast,
55 VoteChanged,
56 VotingClosed,
57
58 TicketCreated,
60 TicketAssigned,
61 TicketStatusChanged,
62 TicketResolved,
63 TicketClosed,
64 TicketCancelled,
65 TicketReopened,
66 TicketDeleted,
67 TicketWorkOrderSent,
68
69 NotificationCreated,
71 NotificationRead,
72 NotificationDeleted,
73 NotificationPreferenceUpdated,
74
75 PaymentCreated,
77 PaymentProcessing,
78 PaymentRequiresAction,
79 PaymentSucceeded,
80 PaymentFailed,
81 PaymentCancelled,
82 PaymentRefunded,
83 PaymentDeleted,
84
85 PaymentMethodCreated,
87 PaymentMethodUpdated,
88 PaymentMethodSetDefault,
89 PaymentMethodDeactivated,
90 PaymentMethodReactivated,
91 PaymentMethodDeleted,
92
93 ConvocationCreated,
95 ConvocationScheduled,
96 ConvocationSent,
97 ConvocationCancelled,
98 ConvocationDeleted,
99 ConvocationReminderSent,
100 ConvocationAttendanceUpdated,
101 ConvocationProxySet,
102 SecondConvocationScheduled,
103
104 QuoteCreated,
106 QuoteSubmitted,
107 QuoteUnderReview,
108 QuoteAccepted,
109 QuoteRejected,
110 QuoteWithdrawn,
111 QuoteExpired,
112 QuoteRatingUpdated,
113 QuoteComparisonPerformed,
114 QuoteDeleted,
115
116 ExchangeCreated,
118 ExchangeRequested,
119 ExchangeStarted,
120 ExchangeCompleted,
121 ExchangeCancelled,
122 ExchangeProviderRated,
123 ExchangeRequesterRated,
124 ExchangeDeleted,
125 CreditBalanceUpdated,
126 CreditBalanceCreated,
127
128 NoticeCreated,
130 NoticeUpdated,
131 NoticePublished,
132 NoticeArchived,
133 NoticePinned,
134 NoticeUnpinned,
135 NoticeExpirationSet,
136 NoticeExpired,
137 NoticeDeleted,
138
139 SkillCreated,
141 SkillUpdated,
142 SkillMarkedAvailable,
143 SkillMarkedUnavailable,
144 SkillDeleted,
145
146 SharedObjectCreated,
148 SharedObjectUpdated,
149 SharedObjectMarkedAvailable,
150 SharedObjectMarkedUnavailable,
151 SharedObjectBorrowed,
152 SharedObjectReturned,
153 SharedObjectDeleted,
154
155 ResourceBookingCreated,
157 ResourceBookingUpdated,
158 ResourceBookingCancelled,
159 ResourceBookingCompleted,
160 ResourceBookingNoShow,
161 ResourceBookingConfirmed,
162 ResourceBookingDeleted,
163
164 AchievementCreated,
166 AchievementUpdated,
167 AchievementDeleted,
168 AchievementAwarded,
169 ChallengeCreated,
170 ChallengeActivated,
171 ChallengeUpdated,
172 ChallengeCompleted,
173 ChallengeCancelled,
174 ChallengeDeleted,
175 ChallengeProgressIncremented,
176 ChallengeProgressCompleted,
177
178 PaymentReminderCreated,
180 PaymentReminderSent,
181 PaymentReminderOpened,
182 PaymentReminderPaid,
183 PaymentReminderCancelled,
184 PaymentReminderEscalated,
185 PaymentReminderTrackingAdded,
186 PaymentRemindersBulkCreated,
187 PaymentReminderDeleted,
188
189 EtatDateCreated,
191 EtatDateInProgress,
192 EtatDateGenerated,
193 EtatDateDelivered,
194 EtatDateFinancialUpdate,
195 EtatDateAdditionalDataUpdate,
196 EtatDateDeleted,
197
198 BudgetCreated,
200 BudgetUpdated,
201 BudgetSubmitted,
202 BudgetApproved,
203 BudgetRejected,
204 BudgetArchived,
205 BudgetDeleted,
206
207 WorkReportCreated,
209 WorkReportUpdated,
210 WorkReportDeleted,
211 WorkReportPhotoAdded,
212 WorkReportDocumentAdded,
213
214 TechnicalInspectionCreated,
216 TechnicalInspectionUpdated,
217 TechnicalInspectionDeleted,
218 TechnicalInspectionReportAdded,
219 TechnicalInspectionPhotoAdded,
220 TechnicalInspectionCertificateAdded,
221
222 UnauthorizedAccess,
224 RateLimitExceeded,
225 InvalidToken,
226
227 TwoFactorSetupInitiated,
229 TwoFactorEnabled,
230 TwoFactorDisabled,
231 TwoFactorVerified,
232 TwoFactorVerificationFailed,
233 BackupCodeUsed,
234 BackupCodesRegenerated,
235 TwoFactorReverificationRequired,
236
237 IoTReadingCreated,
239 IoTReadingsBulkCreated,
240 LinkyDeviceConfigured,
241 LinkyDataSynced,
242 LinkyDeviceDeleted,
243 LinkySyncToggled,
244
245 GdprDataExported,
247 GdprDataExportFailed,
248 GdprDataErased,
249 GdprDataErasureFailed,
250 GdprErasureCheckRequested,
251 GdprDataRectified,
253 GdprDataRectificationFailed,
254 GdprProcessingRestricted,
256 GdprProcessingRestrictionFailed,
257 GdprMarketingOptOut,
259 GdprMarketingOptIn,
260 GdprMarketingPreferenceChangeFailed,
261
262 ConsentRecorded,
264 ConsentStatusChecked,
265
266 SecurityIncidentReported,
268
269 AccountCreated,
271 AccountUpdated,
272 AccountDeleted,
273 BelgianPCMNSeeded,
274
275 ReportGenerated,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct AuditLogEntry {
282 pub id: Uuid,
284 pub timestamp: chrono::DateTime<chrono::Utc>,
286 pub event_type: AuditEventType,
288 pub user_id: Option<Uuid>,
290 pub organization_id: Option<Uuid>,
292 pub resource_type: Option<String>,
294 pub resource_id: Option<Uuid>,
296 pub ip_address: Option<String>,
298 pub user_agent: Option<String>,
300 pub metadata: Option<serde_json::Value>,
302 pub success: bool,
304 pub error_message: Option<String>,
306}
307
308impl AuditLogEntry {
309 pub fn new(
311 event_type: AuditEventType,
312 user_id: Option<Uuid>,
313 organization_id: Option<Uuid>,
314 ) -> Self {
315 Self {
316 id: Uuid::new_v4(),
317 timestamp: Utc::now(),
318 event_type,
319 user_id,
320 organization_id,
321 resource_type: None,
322 resource_id: None,
323 ip_address: None,
324 user_agent: None,
325 metadata: None,
326 success: true,
327 error_message: None,
328 }
329 }
330
331 pub fn with_resource(mut self, resource_type: &str, resource_id: Uuid) -> Self {
333 self.resource_type = Some(resource_type.to_string());
334 self.resource_id = Some(resource_id);
335 self
336 }
337
338 pub fn with_client_info(
340 mut self,
341 ip_address: Option<String>,
342 user_agent: Option<String>,
343 ) -> Self {
344 self.ip_address = ip_address;
345 self.user_agent = user_agent;
346 self
347 }
348
349 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
351 self.metadata = Some(metadata);
352 self
353 }
354
355 pub fn with_error(mut self, error_message: String) -> Self {
357 self.success = false;
358 self.error_message = Some(error_message);
359 self
360 }
361
362 pub fn with_details(mut self, details: String) -> Self {
364 let details_json = serde_json::json!({ "details": details });
365 self.metadata = Some(details_json);
366 self
367 }
368
369 pub fn log(&self) {
371 let redact_presence = |present| if present { "[REDACTED]" } else { "None" };
373 let redacted_user = redact_presence(self.user_id.is_some());
374 let redacted_org = redact_presence(self.organization_id.is_some());
375 let redacted_resource_id = redact_presence(self.resource_id.is_some());
376 let redacted_ip = redact_presence(self.ip_address.is_some());
377 let redacted_error = self.error_message.as_ref().map(|_| "[REDACTED]");
378
379 let log_message = format!(
380 "[AUDIT] {} | {:?} | User: {} | Org: {} | Resource: {}/{} | Success: {} | IP: {}",
381 self.timestamp.format("%Y-%m-%d %H:%M:%S"),
382 self.event_type,
383 redacted_user,
384 redacted_org,
385 self.resource_type.as_deref().unwrap_or("None"),
386 redacted_resource_id,
387 self.success,
388 redacted_ip
389 );
390
391 if self.success {
392 log::info!("{}", log_message);
393 } else {
394 log::warn!(
395 "{} | Error: {}",
396 log_message,
397 redacted_error.unwrap_or("None")
398 );
399 }
400
401 }
408}
409
410pub async fn log_audit_event(
422 event_type: AuditEventType,
423 user_id: Option<Uuid>,
424 organization_id: Option<Uuid>,
425 details: Option<String>,
426 metadata: Option<serde_json::Value>,
427) {
428 let mut entry = AuditLogEntry::new(event_type, user_id, organization_id);
429
430 if let Some(details_str) = details {
431 entry = entry.with_details(details_str);
432 }
433
434 if let Some(meta) = metadata {
435 entry.metadata = Some(meta);
436 }
437
438 entry.log();
439}
440
441#[macro_export]
443macro_rules! audit_log {
444 ($event_type:expr, $user_id:expr, $org_id:expr) => {
445 $crate::infrastructure::audit::AuditLogEntry::new($event_type, $user_id, $org_id).log()
446 };
447 ($event_type:expr, $user_id:expr, $org_id:expr, $resource_type:expr, $resource_id:expr) => {
448 $crate::infrastructure::audit::AuditLogEntry::new($event_type, $user_id, $org_id)
449 .with_resource($resource_type, $resource_id)
450 .log()
451 };
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn test_audit_log_creation() {
460 let user_id = Uuid::new_v4();
461 let org_id = Uuid::new_v4();
462 let building_id = Uuid::new_v4();
463
464 let entry =
465 AuditLogEntry::new(AuditEventType::BuildingCreated, Some(user_id), Some(org_id))
466 .with_resource("Building", building_id)
467 .with_client_info(Some("192.168.1.1".to_string()), None);
468
469 assert_eq!(entry.user_id, Some(user_id));
470 assert_eq!(entry.organization_id, Some(org_id));
471 assert_eq!(entry.resource_id, Some(building_id));
472 assert!(entry.success);
473 }
474
475 #[test]
476 fn test_audit_log_with_error() {
477 let entry = AuditLogEntry::new(AuditEventType::AuthenticationFailed, None, None)
478 .with_error("Invalid credentials".to_string());
479
480 assert!(!entry.success);
481 assert_eq!(entry.error_message, Some("Invalid credentials".to_string()));
482 }
483}