From 54d85f0b34dd4f5a36cfd622aa85dacb927808ec Mon Sep 17 00:00:00 2001 From: Sepehr Date: Sun, 30 Nov 2025 19:33:59 +0100 Subject: [PATCH] feat: Add admin dashboard with authentication - Admin login/logout with Bearer token authentication - Secure admin dashboard page in frontend - Real-time system monitoring (memory, disk, translations) - Rate limits and cleanup service monitoring - Protected admin endpoints - Updated README with full SaaS documentation --- .env.example | 16 +- README.md | 271 ++++++++++++----- frontend/src/app/admin/page.tsx | 454 ++++++++++++++++++++++++++++ frontend/src/components/sidebar.tsx | 41 +++ main.py | 149 ++++++++- 5 files changed, 845 insertions(+), 86 deletions(-) create mode 100644 frontend/src/app/admin/page.tsx diff --git a/.env.example b/.env.example index dd5f17f..1a0f3fd 100644 --- a/.env.example +++ b/.env.example @@ -59,6 +59,20 @@ MAX_REQUEST_SIZE_MB=100 # Request timeout in seconds REQUEST_TIMEOUT_SECONDS=300 +# ============== Admin Authentication ============== +# Admin username +ADMIN_USERNAME=admin + +# Admin password (change in production!) +ADMIN_PASSWORD=changeme123 + +# Or use SHA256 hash of password (more secure) +# Generate with: python -c "import hashlib; print(hashlib.sha256(b'your_password').hexdigest())" +# ADMIN_PASSWORD_HASH= + +# Token secret for session management (auto-generated if not set) +# ADMIN_TOKEN_SECRET= + # ============== Monitoring ============== # Log level: DEBUG, INFO, WARNING, ERROR LOG_LEVEL=INFO @@ -67,4 +81,4 @@ LOG_LEVEL=INFO ENABLE_REQUEST_LOGGING=true # Memory usage threshold (percentage) -MAX_MEMORY_PERCENT=80 +MAX_MEMORY_PERCENT=80 \ No newline at end of file diff --git a/README.md b/README.md index 9f9f213..2621ca2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ๐Ÿ“„ Document Translation API -A powerful Python API for translating complex structured documents (Excel, Word, PowerPoint) while **strictly preserving** the original formatting, layout, and embedded media. +A powerful SaaS-ready Python API for translating complex structured documents (Excel, Word, PowerPoint) while **strictly preserving** the original formatting, layout, and embedded media. ## โœจ Features @@ -12,6 +12,7 @@ A powerful Python API for translating complex structured documents (Excel, Word, | **WebLLM** | Browser | Runs entirely in browser using WebGPU | | **DeepL** | Cloud | High-quality translations (API key required) | | **LibreTranslate** | Self-hosted | Open-source alternative | +| **OpenAI** | Cloud | GPT-4o/4o-mini with vision support | ### ๐Ÿ“Š Excel Translation (.xlsx) - โœ… Translates all cell content and sheet names @@ -32,11 +33,19 @@ A powerful Python API for translating complex structured documents (Excel, Word, - โœ… Image text extraction with text boxes added below images - โœ… Keeps layering order and positions -### ๐Ÿง  LLM Features (Ollama/WebLLM) +### ๐Ÿง  LLM Features (Ollama/WebLLM/OpenAI) - โœ… **Custom System Prompts**: Provide context for better translations - โœ… **Technical Glossary**: Define term mappings (e.g., `batterie=coil`) - โœ… **Presets**: HVAC, IT, Legal, Medical terminology -- โœ… **Vision Models**: Translate text within images (gemma3, qwen3-vl, llava) +- โœ… **Vision Models**: Translate text within images (gemma3, qwen3-vl, gpt-4o) + +### ๐Ÿข SaaS-Ready Features +- ๐Ÿšฆ **Rate Limiting**: Per-client IP with token bucket and sliding window algorithms +- ๐Ÿ”’ **Security Headers**: CSP, XSS protection, HSTS support +- ๐Ÿงน **Auto Cleanup**: Automatic file cleanup with TTL tracking +- ๐Ÿ“Š **Monitoring**: Health checks, metrics, and system status +- ๐Ÿ” **Admin Dashboard**: Secure admin panel with authentication +- ๐Ÿ“ **Request Logging**: Structured logging with unique request IDs ## ๐Ÿš€ Quick Start @@ -60,9 +69,15 @@ python main.py The API starts on `http://localhost:8000` -### Web Interface +### Frontend Setup -Open `http://localhost:8000/static/index.html` for the full-featured web interface. +```powershell +cd frontend +npm install +npm run dev +``` + +Frontend runs on `http://localhost:3000` ## ๐Ÿ“š API Documentation @@ -71,7 +86,9 @@ Open `http://localhost:8000/static/index.html` for the full-featured web interfa ## ๐Ÿ”ง API Endpoints -### POST /translate +### Translation + +#### POST /translate Translate a document with full customization. ```bash @@ -81,28 +98,63 @@ curl -X POST "http://localhost:8000/translate" \ -F "provider=ollama" \ -F "ollama_model=gemma3:12b" \ -F "translate_images=true" \ - -F "system_prompt=You are translating HVAC documents. Use: batterie=coil, CTA=AHU" + -F "system_prompt=You are translating HVAC documents." ``` -### Parameters +### Monitoring -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `file` | File | required | Document to translate (.xlsx, .docx, .pptx) | -| `target_language` | string | required | Target language code (en, fr, es, fa, etc.) | -| `provider` | string | google | Translation provider (google, ollama, webllm, deepl, libre) | -| `ollama_model` | string | llama3.2 | Ollama model name | -| `translate_images` | bool | false | Extract and translate image text with vision | -| `system_prompt` | string | "" | Custom instructions and glossary for LLM | +#### GET /health +Comprehensive health check with system status. -### GET /ollama/models -List available Ollama models. +```json +{ + "status": "healthy", + "translation_service": "google", + "memory": {"system_percent": 34.1, "system_available_gb": 61.7}, + "disk": {"total_files": 0, "total_size_mb": 0}, + "cleanup_service": {"is_running": true} +} +``` -### POST /ollama/configure -Configure Ollama settings. +#### GET /metrics +System metrics and statistics. -### GET /health -Health check endpoint. +#### GET /rate-limit/status +Current rate limit status for the requesting client. + +### Admin Endpoints (Authentication Required) + +#### POST /admin/login +Login to admin dashboard. + +```bash +curl -X POST "http://localhost:8000/admin/login" \ + -F "username=admin" \ + -F "password=your_password" +``` + +Response: +```json +{ + "status": "success", + "token": "your_bearer_token", + "expires_in": 86400 +} +``` + +#### GET /admin/dashboard +Get comprehensive dashboard data (requires Bearer token). + +```bash +curl "http://localhost:8000/admin/dashboard" \ + -H "Authorization: Bearer your_token" +``` + +#### POST /admin/cleanup/trigger +Manually trigger file cleanup. + +#### GET /admin/files/tracked +List currently tracked files. ## ๐ŸŒ Supported Languages @@ -120,22 +172,47 @@ Health check endpoint. ### Environment Variables (.env) ```env -# Translation Service +# ============== Translation Services ============== TRANSLATION_SERVICE=google +DEEPL_API_KEY=your_deepl_api_key_here # Ollama Configuration OLLAMA_BASE_URL=http://localhost:11434 -OLLAMA_MODEL=llama3.2 +OLLAMA_MODEL=llama3 +OLLAMA_VISION_MODEL=llava -# DeepL API Key (optional) -DEEPL_API_KEY=your_api_key_here - -# File Limits +# ============== File Limits ============== MAX_FILE_SIZE_MB=50 -# Directories -UPLOAD_DIR=./uploads -OUTPUT_DIR=./outputs +# ============== Rate Limiting (SaaS) ============== +RATE_LIMIT_ENABLED=true +RATE_LIMIT_PER_MINUTE=30 +RATE_LIMIT_PER_HOUR=200 +TRANSLATIONS_PER_MINUTE=10 +TRANSLATIONS_PER_HOUR=50 +MAX_CONCURRENT_TRANSLATIONS=5 + +# ============== Cleanup Service ============== +CLEANUP_ENABLED=true +CLEANUP_INTERVAL_MINUTES=15 +FILE_TTL_MINUTES=60 +INPUT_FILE_TTL_MINUTES=30 +OUTPUT_FILE_TTL_MINUTES=120 + +# ============== Security ============== +ENABLE_HSTS=false +CORS_ORIGINS=* + +# ============== Admin Authentication ============== +ADMIN_USERNAME=admin +ADMIN_PASSWORD=changeme123 # Change in production! +# Or use a SHA256 hash: +# ADMIN_PASSWORD_HASH=your_sha256_hash + +# ============== Monitoring ============== +LOG_LEVEL=INFO +ENABLE_REQUEST_LOGGING=true +MAX_MEMORY_PERCENT=80 ``` ### Ollama Setup @@ -179,6 +256,73 @@ vanne 3 voies=3-way valve - โš–๏ธ **Legal**: Legal documents - ๐Ÿฅ **Medical**: Healthcare terminology +## ๏ฟฝ Admin Dashboard + +Access the admin dashboard at `/admin` in the frontend. Features: + +- **System Status**: Health, uptime, and issues +- **Memory & Disk Monitoring**: Real-time usage stats +- **Translation Statistics**: Total translations, success rate +- **Rate Limit Management**: View active clients and limits +- **Cleanup Service**: Monitor and trigger manual cleanup + +### Default Credentials +- **Username**: admin +- **Password**: changeme123 + +โš ๏ธ **Change the default password in production!** + +## ๐Ÿ—๏ธ Project Structure + +``` +Translate/ +โ”œโ”€โ”€ main.py # FastAPI application with SaaS features +โ”œโ”€โ”€ config.py # Configuration with SaaS settings +โ”œโ”€โ”€ requirements.txt # Dependencies +โ”œโ”€โ”€ mcp_server.py # MCP server implementation +โ”œโ”€โ”€ middleware/ # SaaS middleware +โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ โ”œโ”€โ”€ rate_limiting.py # Rate limiting with token bucket +โ”‚ โ”œโ”€โ”€ validation.py # Input validation +โ”‚ โ”œโ”€โ”€ security.py # Security headers & logging +โ”‚ โ””โ”€โ”€ cleanup.py # Auto cleanup service +โ”œโ”€โ”€ services/ +โ”‚ โ””โ”€โ”€ translation_service.py # Translation providers +โ”œโ”€โ”€ translators/ +โ”‚ โ”œโ”€โ”€ excel_translator.py # Excel with image support +โ”‚ โ”œโ”€โ”€ word_translator.py # Word with image support +โ”‚ โ””โ”€โ”€ pptx_translator.py # PowerPoint with image support +โ”œโ”€โ”€ frontend/ # Next.js frontend +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ app/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx # Main translation page +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ admin/ # Admin dashboard +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ settings/ # Settings pages +โ”‚ โ”‚ โ””โ”€โ”€ components/ +โ”‚ โ””โ”€โ”€ package.json +โ”œโ”€โ”€ static/ +โ”‚ โ””โ”€โ”€ webllm.html # WebLLM standalone interface +โ”œโ”€โ”€ uploads/ # Temporary uploads (auto-cleaned) +โ””โ”€โ”€ outputs/ # Translated files (auto-cleaned) +``` + +## ๐Ÿ› ๏ธ Tech Stack + +### Backend +- **FastAPI**: Modern async web framework +- **openpyxl**: Excel manipulation +- **python-docx**: Word documents +- **python-pptx**: PowerPoint presentations +- **deep-translator**: Google/DeepL/Libre translation +- **psutil**: System monitoring +- **python-magic**: File type validation + +### Frontend +- **Next.js 15**: React framework +- **Tailwind CSS**: Styling +- **Lucide Icons**: Icon library +- **WebLLM**: Browser-based LLM + ## ๐Ÿ”Œ MCP Integration This API can be used as an MCP (Model Context Protocol) server for AI assistants. @@ -203,56 +347,27 @@ Add to your VS Code `settings.json` or `.vscode/mcp.json`: } ``` -### MCP Tools Available +## ๐Ÿš€ Production Deployment -| Tool | Description | -|------|-------------| -| `translate_document` | Translate a document file | -| `list_ollama_models` | Get available Ollama models | -| `get_supported_languages` | List supported language codes | -| `configure_translation` | Set translation provider and options | +### Security Checklist +- [ ] Change `ADMIN_PASSWORD` or set `ADMIN_PASSWORD_HASH` +- [ ] Set `CORS_ORIGINS` to your frontend domain +- [ ] Enable `ENABLE_HSTS=true` if using HTTPS +- [ ] Configure rate limits appropriately +- [ ] Set up log rotation for `logs/` directory +- [ ] Use a reverse proxy (nginx/traefik) for HTTPS -## ๐Ÿ—๏ธ Project Structure +### Docker Deployment (Coming Soon) +```dockerfile +FROM python:3.11-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . +EXPOSE 8000 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` -Translate/ -โ”œโ”€โ”€ main.py # FastAPI application -โ”œโ”€โ”€ config.py # Configuration -โ”œโ”€โ”€ requirements.txt # Dependencies -โ”œโ”€โ”€ mcp_server.py # MCP server implementation -โ”œโ”€โ”€ services/ -โ”‚ โ””โ”€โ”€ translation_service.py # Translation providers -โ”œโ”€โ”€ translators/ -โ”‚ โ”œโ”€โ”€ excel_translator.py # Excel with image support -โ”‚ โ”œโ”€โ”€ word_translator.py # Word with image support -โ”‚ โ””โ”€โ”€ pptx_translator.py # PowerPoint with image support -โ”œโ”€โ”€ utils/ -โ”‚ โ”œโ”€โ”€ file_handler.py # File operations -โ”‚ โ””โ”€โ”€ exceptions.py # Custom exceptions -โ”œโ”€โ”€ static/ -โ”‚ โ””โ”€โ”€ index.html # Web interface -โ”œโ”€โ”€ uploads/ # Temporary uploads -โ””โ”€โ”€ outputs/ # Translated files -``` - -## ๐Ÿงช Testing - -1. Start the API: `python main.py` -2. Open: http://localhost:8000/static/index.html -3. Configure Ollama model -4. Upload a document -5. Select target language and provider -6. Click "Translate Document" - -## ๐Ÿ› ๏ธ Tech Stack - -- **FastAPI**: Modern async web framework -- **openpyxl**: Excel manipulation -- **python-docx**: Word documents -- **python-pptx**: PowerPoint presentations -- **deep-translator**: Google/DeepL/Libre translation -- **requests**: Ollama API communication -- **Uvicorn**: ASGI server ## ๐Ÿ“ License @@ -264,4 +379,4 @@ Contributions welcome! Please submit a Pull Request. --- -**Built with โค๏ธ using Python, FastAPI, and Ollama** +**Built with โค๏ธ using Python, FastAPI, Next.js, and Ollama** diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx new file mode 100644 index 0000000..9bdd317 --- /dev/null +++ b/frontend/src/app/admin/page.tsx @@ -0,0 +1,454 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Shield, LogOut, RefreshCw, Trash2, Activity, HardDrive, Cpu, Clock, Users, FileText, AlertTriangle, CheckCircle } from "lucide-react"; + +interface DashboardData { + timestamp: string; + uptime: string; + status: string; + issues: string[]; + system: { + memory: { + process_rss_mb: number; + system_total_gb: number; + system_available_gb: number; + system_percent: number; + }; + disk: { + total_files: number; + total_size_mb: number; + usage_percent: number; + }; + }; + translations: { + total: number; + errors: number; + success_rate: number; + }; + cleanup: { + files_cleaned_total: number; + bytes_freed_total_mb: number; + cleanup_runs: number; + tracked_files_count: number; + is_running: boolean; + }; + rate_limits: { + total_requests: number; + total_translations: number; + active_clients: number; + config: { + requests_per_minute: number; + translations_per_minute: number; + }; + }; + config: { + max_file_size_mb: number; + supported_extensions: string[]; + translation_service: string; + }; +} + +export default function AdminPage() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [loginError, setLoginError] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [dashboard, setDashboard] = useState(null); + const [isRefreshing, setIsRefreshing] = useState(false); + + const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + + // Check if already authenticated + useEffect(() => { + const token = localStorage.getItem("admin_token"); + if (token) { + verifyToken(token); + } else { + setIsLoading(false); + } + }, []); + + const verifyToken = async (token: string) => { + try { + const response = await fetch(`${API_URL}/admin/verify`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (response.ok) { + setIsAuthenticated(true); + fetchDashboard(token); + } else { + localStorage.removeItem("admin_token"); + } + } catch (error) { + console.error("Token verification failed:", error); + localStorage.removeItem("admin_token"); + } + setIsLoading(false); + }; + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setLoginError(""); + setIsLoading(true); + + try { + const formData = new FormData(); + formData.append("username", username); + formData.append("password", password); + + const response = await fetch(`${API_URL}/admin/login`, { + method: "POST", + body: formData, + }); + + const data = await response.json(); + + if (response.ok) { + localStorage.setItem("admin_token", data.token); + setIsAuthenticated(true); + fetchDashboard(data.token); + } else { + setLoginError(data.detail || "Login failed"); + } + } catch (error) { + setLoginError("Connection error. Is the backend running?"); + } + setIsLoading(false); + }; + + const handleLogout = async () => { + const token = localStorage.getItem("admin_token"); + if (token) { + try { + await fetch(`${API_URL}/admin/logout`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + }); + } catch (error) { + console.error("Logout error:", error); + } + } + localStorage.removeItem("admin_token"); + setIsAuthenticated(false); + setDashboard(null); + }; + + const fetchDashboard = async (token?: string) => { + const authToken = token || localStorage.getItem("admin_token"); + if (!authToken) return; + + setIsRefreshing(true); + try { + const response = await fetch(`${API_URL}/admin/dashboard`, { + headers: { + Authorization: `Bearer ${authToken}`, + }, + }); + + if (response.ok) { + const data = await response.json(); + setDashboard(data); + } else if (response.status === 401) { + handleLogout(); + } + } catch (error) { + console.error("Failed to fetch dashboard:", error); + } + setIsRefreshing(false); + }; + + const triggerCleanup = async () => { + const token = localStorage.getItem("admin_token"); + if (!token) return; + + try { + const response = await fetch(`${API_URL}/admin/cleanup/trigger`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (response.ok) { + const data = await response.json(); + alert(`Cleanup completed: ${data.files_cleaned} files removed`); + fetchDashboard(); + } + } catch (error) { + console.error("Cleanup failed:", error); + alert("Cleanup failed"); + } + }; + + // Auto-refresh every 30 seconds + useEffect(() => { + if (isAuthenticated) { + const interval = setInterval(() => fetchDashboard(), 30000); + return () => clearInterval(interval); + } + }, [isAuthenticated]); + + if (isLoading) { + return ( +
+
+
+ ); + } + + if (!isAuthenticated) { + return ( +
+
+
+
+ +
+
+

Admin Access

+

Login to access the dashboard

+
+
+ +
+
+ + setUsername(e.target.value)} + className="w-full px-4 py-3 bg-zinc-900/50 border border-zinc-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="admin" + required + /> +
+ +
+ + setPassword(e.target.value)} + className="w-full px-4 py-3 bg-zinc-900/50 border border-zinc-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข" + required + /> +
+ + {loginError && ( +
+ {loginError} +
+ )} + + +
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

Admin Dashboard

+

System monitoring and management

+
+
+
+ + +
+
+ + {dashboard && ( + <> + {/* Status Banner */} +
+ {dashboard.status === "healthy" ? ( + + ) : ( + + )} +
+ + System {dashboard.status.charAt(0).toUpperCase() + dashboard.status.slice(1)} + + {dashboard.issues.length > 0 && ( +

{dashboard.issues.join(", ")}

+ )} +
+
+ + Uptime: {dashboard.uptime} +
+
+ + {/* Stats Grid */} +
+ {/* Total Requests */} +
+
+
+ +
+ Total Requests +
+
{dashboard.rate_limits.total_requests.toLocaleString()}
+
+ {dashboard.rate_limits.active_clients} active clients +
+
+ + {/* Translations */} +
+
+
+ +
+ Translations +
+
{dashboard.translations.total.toLocaleString()}
+
+ {dashboard.translations.success_rate}% success rate +
+
+ + {/* Memory Usage */} +
+
+
+ +
+ Memory Usage +
+
{dashboard.system.memory.system_percent}%
+
+ {dashboard.system.memory.system_available_gb.toFixed(1)} GB available +
+
+ + {/* Disk Usage */} +
+
+
+ +
+ Tracked Files +
+
{dashboard.cleanup.tracked_files_count}
+
+ {dashboard.system.disk.total_size_mb.toFixed(1)} MB total +
+
+
+ + {/* Detailed Panels */} +
+ {/* Rate Limits */} +
+

+ + Rate Limits Configuration +

+
+
+ Requests per minute + {dashboard.rate_limits.config.requests_per_minute} +
+
+ Translations per minute + {dashboard.rate_limits.config.translations_per_minute} +
+
+ Max file size + {dashboard.config.max_file_size_mb} MB +
+
+ Translation service + {dashboard.config.translation_service} +
+
+
+ + {/* Cleanup Service */} +
+
+

+ + Cleanup Service +

+ +
+
+
+ Service status + + {dashboard.cleanup.is_running ? "Running" : "Stopped"} + +
+
+ Files cleaned + {dashboard.cleanup.files_cleaned_total} +
+
+ Space freed + {dashboard.cleanup.bytes_freed_total_mb.toFixed(2)} MB +
+
+ Cleanup runs + {dashboard.cleanup.cleanup_runs} +
+
+
+
+ + {/* Footer Info */} +
+ Last updated: {new Date(dashboard.timestamp).toLocaleString()} โ€ข Auto-refresh every 30 seconds +
+ + )} +
+ ); +} diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index 042b369..4ce010c 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -8,6 +8,7 @@ import { Cloud, BookText, Upload, + Shield, } from "lucide-react"; import { Tooltip, @@ -43,6 +44,15 @@ const navigation = [ }, ]; +const adminNavigation = [ + { + name: "Admin Dashboard", + href: "/admin", + icon: Shield, + description: "System monitoring (login required)", + }, +]; + export function Sidebar() { const pathname = usePathname(); @@ -85,6 +95,37 @@ export function Sidebar() { ); })} + + {/* Admin Section */} +
+

Admin

+ {adminNavigation.map((item) => { + const isActive = pathname === item.href; + const Icon = item.icon; + + return ( + + + + + {item.name} + + + +

{item.description}

+
+
+ ); + })} +
{/* User section at bottom */} diff --git a/main.py b/main.py index 1c52b75..dce4268 100644 --- a/main.py +++ b/main.py @@ -3,16 +3,20 @@ Document Translation API FastAPI application for translating complex documents while preserving formatting SaaS-ready with rate limiting, validation, and robust error handling """ -from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Request, Depends +from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Request, Depends, Header from fastapi.responses import FileResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles +from fastapi.security import HTTPBasic, HTTPBasicCredentials from contextlib import asynccontextmanager from pathlib import Path from typing import Optional import asyncio import logging import os +import secrets +import hashlib +import time from config import config from translators import excel_translator, word_translator, pptx_translator @@ -31,6 +35,57 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +# ============== Admin Authentication ============== +ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin") +ADMIN_PASSWORD_HASH = os.getenv("ADMIN_PASSWORD_HASH", "") # SHA256 hash of password +ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "changeme123") # Default password (change in production!) +ADMIN_TOKEN_SECRET = os.getenv("ADMIN_TOKEN_SECRET", secrets.token_hex(32)) + +# Store active admin sessions (token -> expiry timestamp) +admin_sessions: dict = {} + +def hash_password(password: str) -> str: + """Hash password with SHA256""" + return hashlib.sha256(password.encode()).hexdigest() + +def verify_admin_password(password: str) -> bool: + """Verify admin password""" + if ADMIN_PASSWORD_HASH: + return hash_password(password) == ADMIN_PASSWORD_HASH + return password == ADMIN_PASSWORD + +def create_admin_token() -> str: + """Create a new admin session token""" + token = secrets.token_urlsafe(32) + # Token expires in 24 hours + admin_sessions[token] = time.time() + (24 * 60 * 60) + return token + +def verify_admin_token(token: str) -> bool: + """Verify admin token is valid and not expired""" + if token not in admin_sessions: + return False + if time.time() > admin_sessions[token]: + del admin_sessions[token] + return False + return True + +async def require_admin(authorization: Optional[str] = Header(None)) -> bool: + """Dependency to require admin authentication""" + if not authorization: + raise HTTPException(status_code=401, detail="Authorization header required") + + # Expect "Bearer " + parts = authorization.split(" ") + if len(parts) != 2 or parts[0].lower() != "bearer": + raise HTTPException(status_code=401, detail="Invalid authorization format. Use: Bearer ") + + token = parts[1] + if not verify_admin_token(token): + raise HTTPException(status_code=401, detail="Invalid or expired token") + + return True + # Initialize SaaS components rate_limit_config = RateLimitConfig( requests_per_minute=int(os.getenv("RATE_LIMIT_PER_MINUTE", "30")), @@ -773,6 +828,87 @@ async def reconstruct_document( # ============== SaaS Management Endpoints ============== +@app.post("/admin/login") +async def admin_login( + username: str = Form(...), + password: str = Form(...) +): + """ + Admin login endpoint + Returns a bearer token for authenticated admin access + """ + if username != ADMIN_USERNAME: + logger.warning(f"Failed admin login attempt with username: {username}") + raise HTTPException(status_code=401, detail="Invalid credentials") + + if not verify_admin_password(password): + logger.warning(f"Failed admin login attempt - wrong password") + raise HTTPException(status_code=401, detail="Invalid credentials") + + token = create_admin_token() + logger.info(f"Admin login successful") + + return { + "status": "success", + "token": token, + "expires_in": 86400, # 24 hours in seconds + "message": "Login successful" + } + + +@app.post("/admin/logout") +async def admin_logout(authorization: Optional[str] = Header(None)): + """Logout and invalidate admin token""" + if authorization: + parts = authorization.split(" ") + if len(parts) == 2 and parts[0].lower() == "bearer": + token = parts[1] + if token in admin_sessions: + del admin_sessions[token] + logger.info("Admin logout successful") + + return {"status": "success", "message": "Logged out"} + + +@app.get("/admin/verify") +async def verify_admin_session(is_admin: bool = Depends(require_admin)): + """Verify admin token is still valid""" + return {"status": "valid", "authenticated": True} + + +@app.get("/admin/dashboard") +async def get_admin_dashboard(is_admin: bool = Depends(require_admin)): + """Get comprehensive admin dashboard data""" + health_status = await health_checker.check_health() + cleanup_stats = cleanup_manager.get_stats() + rate_limit_stats = rate_limit_manager.get_stats() + tracked_files = cleanup_manager.get_tracked_files() + + return { + "timestamp": health_status.get("timestamp"), + "uptime": health_status.get("uptime_human"), + "status": health_status.get("status"), + "issues": health_status.get("issues", []), + "system": { + "memory": health_status.get("memory", {}), + "disk": health_status.get("disk", {}), + }, + "translations": health_status.get("translations", {}), + "cleanup": { + **cleanup_stats, + "tracked_files_count": len(tracked_files) + }, + "rate_limits": rate_limit_stats, + "config": { + "max_file_size_mb": config.MAX_FILE_SIZE_MB, + "supported_extensions": list(config.SUPPORTED_EXTENSIONS), + "translation_service": config.TRANSLATION_SERVICE, + "rate_limit_per_minute": rate_limit_config.requests_per_minute, + "translations_per_minute": rate_limit_config.translations_per_minute + } + } + + @app.get("/metrics") async def get_metrics(): """Get system metrics and statistics for monitoring""" @@ -815,8 +951,8 @@ async def get_rate_limit_status(request: Request): @app.post("/admin/cleanup/trigger") -async def trigger_cleanup(): - """Trigger manual cleanup of expired files""" +async def trigger_cleanup(is_admin: bool = Depends(require_admin)): + """Trigger manual cleanup of expired files (requires admin auth)""" try: cleaned = await cleanup_manager.cleanup_expired() return { @@ -830,8 +966,8 @@ async def trigger_cleanup(): @app.get("/admin/files/tracked") -async def get_tracked_files(): - """Get list of currently tracked files""" +async def get_tracked_files(is_admin: bool = Depends(require_admin)): + """Get list of currently tracked files (requires admin auth)""" tracked = cleanup_manager.get_tracked_files() return { "count": len(tracked), @@ -841,5 +977,4 @@ async def get_tracked_files(): if __name__ == "__main__": import uvicorn - uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) - + uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file