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.""" """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 sqlalchemy.orm import Session
from app.core.dependencies import get_current_user from app.core.dependencies import get_current_user
from app.database import get_db from app.database import get_db
from app.services.audit_service import log_action
from app.models.user import User from app.models.user import User
from app.schemas.auth import ( from app.schemas.auth import (
ChangePasswordRequest, ChangePasswordRequest,
@ -36,10 +37,19 @@ router = APIRouter()
@router.post("/login", response_model=TokenResponse) @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.""" """Authenticate with email/password (+ optional MFA) and receive tokens."""
user = authenticate_user(db, data.email, data.password, data.mfa_code) user = authenticate_user(db, data.email, data.password, data.mfa_code)
access, refresh = create_tokens(db, user) 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( return TokenResponse(
access_token=access, access_token=access,
refresh_token=refresh, refresh_token=refresh,
@ -79,11 +89,21 @@ def refresh(data: RefreshRequest, db: Session = Depends(get_db)):
@router.post("/logout") @router.post("/logout")
def logout( def logout(
data: RefreshRequest, data: RefreshRequest,
request: Request,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
): ):
"""Revoke the supplied refresh token (logout).""" """Revoke the supplied refresh token (logout)."""
revoke_refresh_token(db, data.refresh_token) 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"} 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.audit import ImportLog
from app.models.user import User from app.models.user import User
from app.schemas.import_schemas import ImportPreview, ImportResult 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.csv_parser import parse_csv
from app.services.icd_service import import_icd_from_xlsx from app.services.icd_service import import_icd_from_xlsx
from app.services.import_service import confirm_import, preview_import 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}", 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.add(log)
db.commit() 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) return ICDImportResponse(**result)

View file

@ -18,6 +18,7 @@ from app.schemas.report import (
ReportListResponse, ReportListResponse,
ReportMeta, ReportMeta,
) )
from app.services.audit_service import log_action
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -144,6 +145,15 @@ def generate_report(
db.commit() db.commit()
db.refresh(report) 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) return ReportMeta.model_validate(report)
except ImportError as exc: except ImportError as exc:
raise HTTPException(501, f"Required service not yet available: {exc}") raise HTTPException(501, f"Required service not yet available: {exc}")