"""Feedback-Router: Nutzer-Feedback per E-Mail an das Team.""" import html import time import logging from collections import defaultdict from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email import encoders from typing import List import aiosmtplib from fastapi import APIRouter, Depends, HTTPException, status, Form, UploadFile, File from auth import get_current_user from config import ( SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM_EMAIL, SMTP_FROM_NAME, SMTP_USE_TLS, ) logger = logging.getLogger("osint.feedback") router = APIRouter(prefix="/api", tags=["feedback"]) FEEDBACK_EMAIL = "feedback@aegis-sight.de" CATEGORY_LABELS = { "bug": "Fehlerbericht", "feature": "Feature-Wunsch", "question": "Frage", "other": "Sonstiges", } # In-Memory Rate-Limiting: max 3 pro Nutzer/Stunde _user_timestamps: dict[int, list[float]] = defaultdict(list) _MAX_PER_HOUR = 3 _WINDOW = 3600 _ALLOWED_TYPES = {"image/jpeg", "image/png"} _MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB _MAX_FILES = 3 @router.post("/feedback", status_code=204) async def send_feedback( category: str = Form(...), message: str = Form(..., min_length=10, max_length=5000), files: List[UploadFile] = File(default=[]), current_user: dict = Depends(get_current_user), ): """Feedback per E-Mail an das Team senden (mit optionalen Bild-Anhaengen).""" # Kategorie validieren if category not in CATEGORY_LABELS: raise HTTPException(status_code=422, detail="Ungueltige Kategorie.") user_id = current_user["id"] # Rate-Limiting now = time.time() cutoff = now - _WINDOW _user_timestamps[user_id] = [t for t in _user_timestamps[user_id] if t > cutoff] if len(_user_timestamps[user_id]) >= _MAX_PER_HOUR: raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Maximal 3 Feedback-Nachrichten pro Stunde. Bitte spaeter erneut versuchen.", ) # Dateien validieren if len(files) > _MAX_FILES: raise HTTPException(status_code=422, detail=f"Maximal {_MAX_FILES} Dateien erlaubt.") for f in files: if f.content_type not in _ALLOWED_TYPES: raise HTTPException(status_code=422, detail=f"Dateityp {f.content_type} nicht erlaubt (nur JPEG/PNG).") if not SMTP_HOST: logger.warning("SMTP nicht konfiguriert - Feedback nicht gesendet") raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="E-Mail-Versand nicht verfuegbar.", ) email = current_user.get("email", "") display_name = email.split("@")[0] if email else "Unbekannt" category_label = CATEGORY_LABELS.get(category, category) message_escaped = html.escape(message) subject = f"[AegisSight Feedback] {category_label} von {display_name}" html_body = f"""\
| Kategorie: | {category_label} |
| Nutzer: | {html.escape(display_name)} |
| E-Mail: | {html.escape(email) if email else "nicht hinterlegt"} |