feat: add audit logging for login, logout, imports, and report generation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-24 10:36:25 +00:00
parent 4a3648a018
commit 9650889d24
3 changed files with 59 additions and 3 deletions

View file

@ -1,10 +1,11 @@
"""Auth API router: login, register, refresh, logout, MFA, password change."""
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from app.core.dependencies import get_current_user
from app.database import get_db
from app.services.audit_service import log_action
from app.models.user import User
from app.schemas.auth import (
ChangePasswordRequest,
@ -36,10 +37,19 @@ router = APIRouter()
@router.post("/login", response_model=TokenResponse)
def login(data: LoginRequest, db: Session = Depends(get_db)):
def login(data: LoginRequest, request: Request, db: Session = Depends(get_db)):
"""Authenticate with email/password (+ optional MFA) and receive tokens."""
user = authenticate_user(db, data.email, data.password, data.mfa_code)
access, refresh = create_tokens(db, user)
log_action(
db,
user_id=user.id,
action="login",
entity_type="user",
entity_id=user.id,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
return TokenResponse(
access_token=access,
refresh_token=refresh,
@ -79,11 +89,21 @@ def refresh(data: RefreshRequest, db: Session = Depends(get_db)):
@router.post("/logout")
def logout(
data: RefreshRequest,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Revoke the supplied refresh token (logout)."""
revoke_refresh_token(db, data.refresh_token)
log_action(
db,
user_id=current_user.id,
action="logout",
entity_type="user",
entity_id=current_user.id,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
return {"detail": "Logged out"}

View file

@ -14,6 +14,7 @@ from app.database import get_db
from app.models.audit import ImportLog
from app.models.user import User
from app.schemas.import_schemas import ImportPreview, ImportResult
from app.services.audit_service import log_action
from app.services.csv_parser import parse_csv
from app.services.icd_service import import_icd_from_xlsx
from app.services.import_service import confirm_import, preview_import
@ -140,7 +141,20 @@ async def confirm_csv_import(
detail=f"CSV parsing failed: {exc}",
)
return confirm_import(db, parsed, file.filename, user.id)
result = confirm_import(db, parsed, file.filename, user.id)
log_action(
db,
user_id=user.id,
action="csv_imported",
entity_type="import",
new_values={
"filename": file.filename,
"imported": result.imported,
"skipped": result.skipped,
"updated": result.updated,
},
)
return result
# ---------------------------------------------------------------------------
@ -194,6 +208,18 @@ async def upload_icd_xlsx(
db.add(log)
db.commit()
log_action(
db,
user_id=user.id,
action="icd_xlsx_imported",
entity_type="import",
new_values={
"filename": file.filename,
"updated": result["updated"],
"errors": len(result["errors"]),
},
)
return ICDImportResponse(**result)

View file

@ -18,6 +18,7 @@ from app.schemas.report import (
ReportListResponse,
ReportMeta,
)
from app.services.audit_service import log_action
logger = logging.getLogger(__name__)
@ -144,6 +145,15 @@ def generate_report(
db.commit()
db.refresh(report)
log_action(
db,
user_id=user.id,
action="report_generated",
entity_type="report",
entity_id=report.id,
new_values={"jahr": jahr, "kw": kw, "filename": filename},
)
return ReportMeta.model_validate(report)
except ImportError as exc:
raise HTTPException(501, f"Required service not yet available: {exc}")