feat: complete external auth V2 migration with advanced features

This commit implements comprehensive external Azure AD authentication
with complete task management, file download, and admin monitoring systems.

## Core Features Implemented (80% Complete)

### 1. Token Auto-Refresh Mechanism 
- Backend: POST /api/v2/auth/refresh endpoint
- Frontend: Auto-refresh 5 minutes before expiration
- Auto-retry on 401 errors with seamless token refresh

### 2. File Download System 
- Three format support: JSON / Markdown / PDF
- Endpoints: GET /api/v2/tasks/{id}/download/{format}
- File access control with ownership validation
- Frontend download buttons in TaskHistoryPage

### 3. Complete Task Management 
Backend Endpoints:
- POST /api/v2/tasks/{id}/start - Start task
- POST /api/v2/tasks/{id}/cancel - Cancel task
- POST /api/v2/tasks/{id}/retry - Retry failed task
- GET /api/v2/tasks - List with filters (status, filename, date range)
- GET /api/v2/tasks/stats - User statistics

Frontend Features:
- Status-based action buttons (Start/Cancel/Retry)
- Advanced search and filtering (status, filename, date range)
- Pagination and sorting
- Task statistics dashboard (5 stat cards)

### 4. Admin Monitoring System  (Backend)
Admin APIs:
- GET /api/v2/admin/stats - System statistics
- GET /api/v2/admin/users - User list with stats
- GET /api/v2/admin/users/top - User leaderboard
- GET /api/v2/admin/audit-logs - Audit log query system
- GET /api/v2/admin/audit-logs/user/{id}/summary

Admin Features:
- Email-based admin check (ymirliu@panjit.com.tw)
- Comprehensive system metrics (users, tasks, sessions, activity)
- Audit logging service for security tracking

### 5. User Isolation & Security 
- Row-level security on all task queries
- File access control with ownership validation
- Strict user_id filtering on all operations
- Session validation and expiry checking
- Admin privilege verification

## New Files Created

Backend:
- backend/app/models/user_v2.py - User model for external auth
- backend/app/models/task.py - Task model with user isolation
- backend/app/models/session.py - Session management
- backend/app/models/audit_log.py - Audit log model
- backend/app/services/external_auth_service.py - External API client
- backend/app/services/task_service.py - Task CRUD with isolation
- backend/app/services/file_access_service.py - File access control
- backend/app/services/admin_service.py - Admin operations
- backend/app/services/audit_service.py - Audit logging
- backend/app/routers/auth_v2.py - V2 auth endpoints
- backend/app/routers/tasks.py - Task management endpoints
- backend/app/routers/admin.py - Admin endpoints
- backend/alembic/versions/5e75a59fb763_*.py - DB migration

Frontend:
- frontend/src/services/apiV2.ts - Complete V2 API client
- frontend/src/types/apiV2.ts - V2 type definitions
- frontend/src/pages/TaskHistoryPage.tsx - Task history UI

Modified Files:
- backend/app/core/deps.py - Added get_current_admin_user_v2
- backend/app/main.py - Registered admin router
- frontend/src/pages/LoginPage.tsx - V2 login integration
- frontend/src/components/Layout.tsx - User display and logout
- frontend/src/App.tsx - Added /tasks route

## Documentation
- openspec/changes/.../PROGRESS_UPDATE.md - Detailed progress report

## Pending Items (20%)
1. Database migration execution for audit_logs table
2. Frontend admin dashboard page
3. Frontend audit log viewer

## Testing Status
- Manual testing:  Authentication flow verified
- Unit tests:  Pending
- Integration tests:  Pending

## Security Enhancements
-  User isolation (row-level security)
-  File access control
-  Token expiry validation
-  Admin privilege verification
-  Audit logging infrastructure
-  Token encryption (noted, low priority)
-  Rate limiting (noted, low priority)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
egg
2025-11-14 17:19:43 +08:00
parent 470fa96428
commit ad2b832fb6
32 changed files with 6450 additions and 26 deletions

View File

@@ -0,0 +1,191 @@
"""
Tool_OCR - Admin Router
Administrative endpoints for system management
"""
import logging
from typing import Optional
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from app.core.deps import get_db, get_current_admin_user_v2
from app.models.user_v2 import User
from app.services.admin_service import admin_service
from app.services.audit_service import audit_service
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v2/admin", tags=["Admin"])
@router.get("/stats", summary="Get system statistics")
async def get_system_stats(
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin_user_v2)
):
"""
Get overall system statistics
Requires admin privileges
"""
try:
stats = admin_service.get_system_statistics(db)
return stats
except Exception as e:
logger.exception("Failed to get system stats")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get system stats: {str(e)}"
)
@router.get("/users", summary="List all users")
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=100),
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin_user_v2)
):
"""
Get list of all users with statistics
Requires admin privileges
"""
try:
skip = (page - 1) * page_size
users, total = admin_service.get_user_list(db, skip=skip, limit=page_size)
return {
"users": users,
"total": total,
"page": page,
"page_size": page_size,
"has_more": (skip + len(users)) < total
}
except Exception as e:
logger.exception("Failed to list users")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to list users: {str(e)}"
)
@router.get("/users/top", summary="Get top users")
async def get_top_users(
metric: str = Query("tasks", regex="^(tasks|completed_tasks)$"),
limit: int = Query(10, ge=1, le=50),
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin_user_v2)
):
"""
Get top users by metric
- **metric**: Ranking metric (tasks or completed_tasks)
- **limit**: Number of users to return
Requires admin privileges
"""
try:
top_users = admin_service.get_top_users(db, metric=metric, limit=limit)
return {
"metric": metric,
"users": top_users
}
except Exception as e:
logger.exception("Failed to get top users")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get top users: {str(e)}"
)
@router.get("/audit-logs", summary="Get audit logs")
async def get_audit_logs(
user_id: Optional[int] = Query(None),
event_category: Optional[str] = Query(None),
event_type: Optional[str] = Query(None),
date_from: Optional[str] = Query(None),
date_to: Optional[str] = Query(None),
success_only: Optional[bool] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(100, ge=1, le=500),
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin_user_v2)
):
"""
Get audit logs with filtering
- **user_id**: Filter by user ID (optional)
- **event_category**: Filter by category (authentication, task, admin, system)
- **event_type**: Filter by event type (optional)
- **date_from**: Filter from date (YYYY-MM-DD, optional)
- **date_to**: Filter to date (YYYY-MM-DD, optional)
- **success_only**: Filter by success status (optional)
Requires admin privileges
"""
try:
# Parse dates
date_from_dt = datetime.fromisoformat(date_from) if date_from else None
date_to_dt = datetime.fromisoformat(date_to) if date_to else None
skip = (page - 1) * page_size
logs, total = audit_service.get_logs(
db=db,
user_id=user_id,
event_category=event_category,
event_type=event_type,
date_from=date_from_dt,
date_to=date_to_dt,
success_only=success_only,
skip=skip,
limit=page_size
)
return {
"logs": [log.to_dict() for log in logs],
"total": total,
"page": page,
"page_size": page_size,
"has_more": (skip + len(logs)) < total
}
except Exception as e:
logger.exception("Failed to get audit logs")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get audit logs: {str(e)}"
)
@router.get("/audit-logs/user/{user_id}/summary", summary="Get user activity summary")
async def get_user_activity_summary(
user_id: int,
days: int = Query(30, ge=1, le=365),
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin_user_v2)
):
"""
Get user activity summary for the last N days
- **user_id**: User ID
- **days**: Number of days to look back (default: 30)
Requires admin privileges
"""
try:
summary = audit_service.get_user_activity_summary(db, user_id=user_id, days=days)
return summary
except Exception as e:
logger.exception(f"Failed to get activity summary for user {user_id}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get user activity summary: {str(e)}"
)