grapholab / backend /models /audit.py
Fabio Antonini
feat: password reset via admin (no SMTP required)
f4fd404
"""
GraphoLab Backend — AuditLog ORM model.
Append-only table: no UPDATE or DELETE is ever issued by application code.
Every sensitive operation is recorded here for forensic chain-of-custody.
"""
from __future__ import annotations
import enum
from datetime import datetime
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column
from backend.database import Base
class AuditAction(str, enum.Enum):
login = "login"
project_create = "project_create"
project_delete = "project_delete"
document_upload = "document_upload"
document_delete = "document_delete"
analysis_run = "analysis_run"
analysis_clear = "analysis_clear"
pdf_download = "pdf_download"
password_reset_generate = "password_reset_generate"
password_reset_confirm = "password_reset_confirm"
class AuditLog(Base):
"""Immutable audit trail. Never UPDATE or DELETE rows from this table."""
__tablename__ = "audit_log"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
# UTC timestamp — server-generated, not client-supplied
timestamp: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True
)
# Who performed the action (snapshot: user may be deleted later)
user_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
user_email: Mapped[str] = mapped_column(String(256), nullable=False)
# What happened
action: Mapped[AuditAction] = mapped_column(Enum(AuditAction), nullable=False, index=True)
# On which resource (e.g. resource_type="project", resource_id=42)
resource_type: Mapped[str | None] = mapped_column(String(64), nullable=True)
resource_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
# Optional extra context (e.g. analysis type, filename)
detail: Mapped[str | None] = mapped_column(Text, nullable=True)
# Network info
ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True)