feat: enable audit logging for authentication and task operations
Add audit_service.log_event() calls to track key user activities: - auth_login: successful and failed login attempts with IP/user agent - auth_logout: single session and all sessions logout - task_delete: task deletion with user context - file_upload: file upload with filename, size, and type - admin_cleanup: manual cleanup trigger with statistics Each event captures client IP (from X-Forwarded-For/X-Real-IP headers), user agent, and relevant metadata for compliance and debugging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -405,6 +405,22 @@ async def trigger_cleanup(
|
||||
f"{result['total_files_deleted']} files, {result['total_bytes_freed']} bytes"
|
||||
)
|
||||
|
||||
# Log admin cleanup action
|
||||
audit_service.log_event(
|
||||
db=db,
|
||||
event_type="admin_cleanup",
|
||||
event_category="admin",
|
||||
description=f"Manual cleanup: {result['total_files_deleted']} files, {result['total_bytes_freed']} bytes freed",
|
||||
user_id=admin_user.id,
|
||||
success=True,
|
||||
metadata={
|
||||
"files_deleted": result['total_files_deleted'],
|
||||
"bytes_freed": result['total_bytes_freed'],
|
||||
"users_processed": result['users_processed'],
|
||||
"max_files_per_user": files_to_keep
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Cleanup completed successfully",
|
||||
|
||||
@@ -17,6 +17,7 @@ from app.models.user import User
|
||||
from app.models.session import Session as UserSession
|
||||
from app.schemas.auth import LoginRequest, Token, UserResponse
|
||||
from app.services.external_auth_service import external_auth_service
|
||||
from app.services.audit_service import audit_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -66,6 +67,17 @@ async def login(
|
||||
logger.warning(
|
||||
f"External auth failed for user {login_data.username}: {error_msg}"
|
||||
)
|
||||
# Log failed login attempt
|
||||
audit_service.log_event(
|
||||
db=db,
|
||||
event_type="auth_login",
|
||||
event_category="authentication",
|
||||
description=f"Login failed for {login_data.username}: {error_msg}",
|
||||
ip_address=get_client_ip(request),
|
||||
user_agent=get_user_agent(request),
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=error_msg or "Authentication failed",
|
||||
@@ -151,6 +163,20 @@ async def login(
|
||||
expires_delta=internal_token_expires
|
||||
)
|
||||
|
||||
# Log successful login
|
||||
audit_service.log_event(
|
||||
db=db,
|
||||
event_type="auth_login",
|
||||
event_category="authentication",
|
||||
description=f"User logged in successfully",
|
||||
user_id=user.id,
|
||||
ip_address=get_client_ip(request),
|
||||
user_agent=get_user_agent(request),
|
||||
resource_type="session",
|
||||
resource_id=str(session.id),
|
||||
success=True
|
||||
)
|
||||
|
||||
return {
|
||||
"access_token": internal_access_token,
|
||||
"token_type": "bearer",
|
||||
@@ -165,6 +191,7 @@ async def login(
|
||||
|
||||
@router.post("/logout", summary="User logout")
|
||||
async def logout(
|
||||
request: Request,
|
||||
session_id: Optional[int] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
@@ -174,9 +201,6 @@ async def logout(
|
||||
|
||||
- **session_id**: Session ID to logout (optional, logs out all if not provided)
|
||||
"""
|
||||
# TODO: Implement proper current_user dependency from JWT token
|
||||
# For now, this is a placeholder
|
||||
|
||||
if session_id:
|
||||
# Logout specific session
|
||||
session = db.query(UserSession).filter(
|
||||
@@ -188,6 +212,20 @@ async def logout(
|
||||
db.delete(session)
|
||||
db.commit()
|
||||
logger.info(f"Logged out session {session_id} for user {current_user.email}")
|
||||
|
||||
# Log logout event
|
||||
audit_service.log_event(
|
||||
db=db,
|
||||
event_type="auth_logout",
|
||||
event_category="authentication",
|
||||
description=f"User logged out session {session_id}",
|
||||
user_id=current_user.id,
|
||||
ip_address=get_client_ip(request),
|
||||
user_agent=get_user_agent(request),
|
||||
resource_type="session",
|
||||
resource_id=str(session_id),
|
||||
success=True
|
||||
)
|
||||
return {"message": "Logged out successfully"}
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -206,6 +244,19 @@ async def logout(
|
||||
|
||||
db.commit()
|
||||
logger.info(f"Logged out all {count} sessions for user {current_user.email}")
|
||||
|
||||
# Log logout event
|
||||
audit_service.log_event(
|
||||
db=db,
|
||||
event_type="auth_logout",
|
||||
event_category="authentication",
|
||||
description=f"User logged out all {count} sessions",
|
||||
user_id=current_user.id,
|
||||
ip_address=get_client_ip(request),
|
||||
user_agent=get_user_agent(request),
|
||||
success=True,
|
||||
metadata={"sessions_count": count}
|
||||
)
|
||||
return {"message": f"Logged out {count} sessions"}
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
import shutil
|
||||
import hashlib
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File, BackgroundTasks
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File, BackgroundTasks, Request
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from sqlalchemy.orm import Session
|
||||
import json
|
||||
@@ -47,6 +47,7 @@ from app.services.task_service import task_service
|
||||
from app.services.file_access_service import file_access_service
|
||||
from app.services.ocr_service import OCRService
|
||||
from app.services.service_pool import get_service_pool, PoolConfig
|
||||
from app.services.audit_service import audit_service
|
||||
|
||||
# Import dual-track components
|
||||
try:
|
||||
@@ -67,6 +68,22 @@ logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/api/v2/tasks", tags=["Tasks"])
|
||||
|
||||
|
||||
def get_client_ip(request: Request) -> str:
|
||||
"""Extract client IP address from request"""
|
||||
forwarded = request.headers.get("X-Forwarded-For")
|
||||
if forwarded:
|
||||
return forwarded.split(",")[0].strip()
|
||||
real_ip = request.headers.get("X-Real-IP")
|
||||
if real_ip:
|
||||
return real_ip
|
||||
return request.client.host if request.client else "unknown"
|
||||
|
||||
|
||||
def get_user_agent(request: Request) -> str:
|
||||
"""Extract user agent from request"""
|
||||
return request.headers.get("User-Agent", "unknown")[:500]
|
||||
|
||||
|
||||
def process_task_ocr(
|
||||
task_id: str,
|
||||
task_db_id: int,
|
||||
@@ -518,6 +535,7 @@ async def update_task(
|
||||
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_task(
|
||||
task_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
@@ -539,6 +557,20 @@ async def delete_task(
|
||||
)
|
||||
|
||||
logger.info(f"Deleted task {task_id} for user {current_user.email}")
|
||||
|
||||
# Log task deletion
|
||||
audit_service.log_event(
|
||||
db=db,
|
||||
event_type="task_delete",
|
||||
event_category="task",
|
||||
description=f"Task deleted",
|
||||
user_id=current_user.id,
|
||||
ip_address=get_client_ip(request),
|
||||
user_agent=get_user_agent(request),
|
||||
resource_type="task",
|
||||
resource_id=task_id,
|
||||
success=True
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user