124 lines
3.9 KiB
Python
124 lines
3.9 KiB
Python
|
|
"""
|
||
|
|
User data export API endpoints for GDPR compliance
|
||
|
|
Implements Article 15 (Right to Access) and Article 20 (Right to Data Portability)
|
||
|
|
"""
|
||
|
|
|
||
|
|
from uuid import UUID
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||
|
|
from fastapi.responses import JSONResponse
|
||
|
|
import structlog
|
||
|
|
|
||
|
|
from shared.auth.decorators import get_current_user_dep
|
||
|
|
from shared.routing import RouteBuilder
|
||
|
|
from app.core.database import get_db
|
||
|
|
from app.services.data_export_service import DataExportService
|
||
|
|
|
||
|
|
logger = structlog.get_logger()
|
||
|
|
|
||
|
|
router = APIRouter()
|
||
|
|
route_builder = RouteBuilder('auth')
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/api/v1/users/me/export")
|
||
|
|
async def export_my_data(
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db = Depends(get_db)
|
||
|
|
):
|
||
|
|
"""
|
||
|
|
Export all personal data for the current user
|
||
|
|
|
||
|
|
GDPR Article 15 - Right of access by the data subject
|
||
|
|
GDPR Article 20 - Right to data portability
|
||
|
|
|
||
|
|
Returns complete user data in machine-readable JSON format including:
|
||
|
|
- Personal information
|
||
|
|
- Account data
|
||
|
|
- Consent history
|
||
|
|
- Security logs
|
||
|
|
- Audit trail
|
||
|
|
|
||
|
|
Response is provided in JSON format for easy data portability.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
user_id = UUID(current_user["sub"])
|
||
|
|
|
||
|
|
export_service = DataExportService(db)
|
||
|
|
data = await export_service.export_user_data(user_id)
|
||
|
|
|
||
|
|
logger.info(
|
||
|
|
"data_export_requested",
|
||
|
|
user_id=str(user_id),
|
||
|
|
email=current_user.get("email")
|
||
|
|
)
|
||
|
|
|
||
|
|
return JSONResponse(
|
||
|
|
content=data,
|
||
|
|
status_code=status.HTTP_200_OK,
|
||
|
|
headers={
|
||
|
|
"Content-Disposition": f'attachment; filename="user_data_export_{user_id}.json"',
|
||
|
|
"Content-Type": "application/json"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(
|
||
|
|
"data_export_failed",
|
||
|
|
user_id=current_user.get("sub"),
|
||
|
|
error=str(e)
|
||
|
|
)
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
|
|
detail="Failed to export user data"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/api/v1/users/me/export/summary")
|
||
|
|
async def get_export_summary(
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db = Depends(get_db)
|
||
|
|
):
|
||
|
|
"""
|
||
|
|
Get a summary of what data would be exported
|
||
|
|
|
||
|
|
Useful for showing users what data we have about them
|
||
|
|
before they request full export.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
user_id = UUID(current_user["sub"])
|
||
|
|
|
||
|
|
export_service = DataExportService(db)
|
||
|
|
data = await export_service.export_user_data(user_id)
|
||
|
|
|
||
|
|
summary = {
|
||
|
|
"user_id": str(user_id),
|
||
|
|
"data_categories": {
|
||
|
|
"personal_data": bool(data.get("personal_data")),
|
||
|
|
"account_data": bool(data.get("account_data")),
|
||
|
|
"consent_data": bool(data.get("consent_data")),
|
||
|
|
"security_data": bool(data.get("security_data")),
|
||
|
|
"onboarding_data": bool(data.get("onboarding_data")),
|
||
|
|
"audit_logs": bool(data.get("audit_logs"))
|
||
|
|
},
|
||
|
|
"data_counts": {
|
||
|
|
"active_sessions": data.get("account_data", {}).get("active_sessions_count", 0),
|
||
|
|
"consent_changes": data.get("consent_data", {}).get("total_consent_changes", 0),
|
||
|
|
"login_attempts": len(data.get("security_data", {}).get("recent_login_attempts", [])),
|
||
|
|
"audit_logs": data.get("audit_logs", {}).get("total_logs_exported", 0)
|
||
|
|
},
|
||
|
|
"export_format": "JSON",
|
||
|
|
"gdpr_articles": ["Article 15 (Right to Access)", "Article 20 (Data Portability)"]
|
||
|
|
}
|
||
|
|
|
||
|
|
return summary
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(
|
||
|
|
"export_summary_failed",
|
||
|
|
user_id=current_user.get("sub"),
|
||
|
|
error=str(e)
|
||
|
|
)
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
|
|
detail="Failed to generate export summary"
|
||
|
|
)
|