Audit Log

This commit is contained in:
2026-03-27 14:58:12 +01:00
parent d7ae840ac6
commit a3247a69b6
13 changed files with 734 additions and 9 deletions
+65
View File
@@ -0,0 +1,65 @@
"""
Router Audit Log consultazione degli eventi di sistema.
Endpoint:
GET /api/v1/audit-log lista paginata con filtri (solo admin/super_admin)
Permessi:
- admin: vede solo gli eventi del proprio tenant
- super_admin: vede tutti i tenant (filtrabile per tenant_id)
"""
import uuid
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Query
from app.dependencies import AdminUser, DB
from app.schemas.audit_log import AuditLogListResponse
from app.services.audit_service import AuditService
router = APIRouter(prefix="/audit-log", tags=["Audit Log"])
@router.get("", response_model=AuditLogListResponse)
async def list_audit_log(
current_user: AdminUser,
db: DB,
page: int = Query(1, ge=1, description="Numero di pagina"),
page_size: int = Query(25, ge=1, le=100, description="Elementi per pagina"),
action: Optional[str] = Query(None, description="Filtra per azione (es. auth.login, user.*)"),
user_id: Optional[uuid.UUID] = Query(None, description="Filtra per utente"),
outcome: Optional[str] = Query(None, pattern="^(success|failure)$", description="Esito: success o failure"),
date_from: Optional[datetime] = Query(None, description="Data inizio (ISO 8601)"),
date_to: Optional[datetime] = Query(None, description="Data fine (ISO 8601)"),
resource_type: Optional[str] = Query(None, description="Tipo risorsa (user, mailbox, message, ...)"),
tenant_id: Optional[uuid.UUID] = Query(None, description="Filtra per tenant (solo super_admin)"),
) -> AuditLogListResponse:
"""
Restituisce la lista paginata degli eventi di audit.
- Admin: vede solo gli eventi del proprio tenant (tenant_id ignorato).
- Super Admin: vede tutti i tenant, filtrabile per tenant_id.
"""
svc = AuditService(db)
# Determina il tenant_id effettivo da applicare al filtro
if current_user.is_super_admin:
# Super admin: usa il tenant_id passato come filtro (None = tutti)
effective_tenant_id = tenant_id
else:
# Admin normale: sempre vincolato al proprio tenant
effective_tenant_id = current_user.tenant_id
return await svc.list(
tenant_id=effective_tenant_id,
page=page,
page_size=page_size,
action=action,
user_id=user_id,
outcome=outcome,
date_from=date_from,
date_to=date_to,
resource_type=resource_type,
)
+21
View File
@@ -161,13 +161,34 @@ async def totp_disable(
summary="Cambio password utente corrente",
)
async def change_password(
request: Request,
body: PasswordChangeRequest,
current_user: CurrentUser,
db: DB,
) -> None:
from app.core.security import verify_password, hash_password
from app.services.audit_service import log_audit
if not verify_password(body.current_password, current_user.password_hash):
from app.services.audit_service import log_audit as _la
await _la(
db,
"auth.password_changed",
tenant_id=current_user.tenant_id,
user_id=current_user.id,
outcome="failure",
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
payload={"reason": "wrong_current_password"},
)
raise InvalidCredentialsError()
current_user.password_hash = hash_password(body.new_password)
await log_audit(
db,
"auth.password_changed",
tenant_id=current_user.tenant_id,
user_id=current_user.id,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)