Spaces:
Running
Running
| import logging | |
| import traceback | |
| from datetime import datetime, timezone | |
| from typing import Optional, Dict, Any | |
| from firebase_admin import firestore | |
| logger = logging.getLogger("audit_logger") | |
| async def log_audit_event( | |
| action: str, | |
| actor_uid: str, | |
| actor_name: str, | |
| actor_email: str, | |
| actor_role: str, | |
| description: str, | |
| target_type: Optional[str] = None, | |
| target_id: Optional[str] = None, | |
| target_name: Optional[str] = None, | |
| route: Optional[str] = None, | |
| module: Optional[str] = None, | |
| metadata: Optional[Dict[str, Any]] = None, | |
| success: bool = True, | |
| ) -> None: | |
| """ | |
| Log an audit event to the accessAuditLogs collection. | |
| Fails safely - will not crash the app if logging fails. | |
| """ | |
| try: | |
| # Sanitize metadata to remove potential secrets | |
| safe_metadata = {} | |
| if metadata: | |
| for k, v in metadata.items(): | |
| if "password" in k.lower() or "token" in k.lower() or "secret" in k.lower(): | |
| safe_metadata[k] = "[REDACTED]" | |
| else: | |
| safe_metadata[k] = v | |
| db = firestore.client() | |
| log_entry = { | |
| "timestamp": firestore.SERVER_TIMESTAMP, | |
| "action": action, | |
| "actorUid": actor_uid, | |
| "actorName": actor_name, | |
| "actorEmail": actor_email, | |
| "actorRole": actor_role, | |
| "description": description, | |
| "success": success, | |
| } | |
| if target_type is not None: | |
| log_entry["targetType"] = target_type | |
| if target_id is not None: | |
| log_entry["targetId"] = target_id | |
| if target_name is not None: | |
| log_entry["targetName"] = target_name | |
| if route is not None: | |
| log_entry["route"] = route | |
| if module is not None: | |
| log_entry["module"] = module | |
| if safe_metadata: | |
| log_entry["metadata"] = safe_metadata | |
| db.collection("accessAuditLogs").add(log_entry) | |
| except Exception as e: | |
| logger.error(f"Failed to write audit log ({action}): {str(e)}") | |
| # We catch and log, but do not raise, so the main app flow continues | |