Initial commit: AegisSight-Monitor (OSINT-Monitoringsystem)

Dieser Commit ist enthalten in:
claude-dev
2026-03-04 17:53:18 +01:00
Commit 8312d24912
51 geänderte Dateien mit 19355 neuen und 0 gelöschten Zeilen

115
src/routers/feedback.py Normale Datei
Datei anzeigen

@@ -0,0 +1,115 @@
"""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
import aiosmtplib
from fastapi import APIRouter, Depends, HTTPException, status
from auth import get_current_user
from models import FeedbackRequest
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
@router.post("/feedback", status_code=204)
async def send_feedback(
data: FeedbackRequest,
current_user: dict = Depends(get_current_user),
):
"""Feedback per E-Mail an das Team senden."""
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.",
)
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.",
)
username = current_user["username"]
email = current_user.get("email", "")
category_label = CATEGORY_LABELS.get(data.category, data.category)
message_escaped = html.escape(data.message)
subject = f"[AegisSight Feedback] {category_label} von {username}"
html_body = f"""\
<div style="font-family:Arial,sans-serif;max-width:600px;margin:0 auto;">
<div style="background:#151D2E;color:#E8ECF4;padding:20px;border-radius:8px 8px 0 0;">
<h2 style="margin:0;color:#C8A851;">Neues Feedback</h2>
</div>
<div style="background:#1A2440;color:#E8ECF4;padding:20px;border-radius:0 0 8px 8px;">
<table style="border-collapse:collapse;">
<tr><td style="color:#8896AB;padding:4px 16px 4px 0;">Kategorie:</td><td><strong>{category_label}</strong></td></tr>
<tr><td style="color:#8896AB;padding:4px 16px 4px 0;">Nutzer:</td><td>{html.escape(username)}</td></tr>
<tr><td style="color:#8896AB;padding:4px 16px 4px 0;">E-Mail:</td><td>{html.escape(email) if email else "nicht hinterlegt"}</td></tr>
</table>
<hr style="border:none;border-top:1px solid #1E2D45;margin:16px 0;">
<div style="white-space:pre-wrap;line-height:1.5;">{message_escaped}</div>
</div>
</div>"""
msg = MIMEMultipart("alternative")
msg["From"] = f"{SMTP_FROM_NAME} <{SMTP_FROM_EMAIL}>"
msg["To"] = FEEDBACK_EMAIL
msg["Subject"] = subject
if email:
msg["Reply-To"] = email
text_fallback = f"Feedback von {username} ({category_label}):\n\n{data.message}"
msg.attach(MIMEText(text_fallback, "plain", "utf-8"))
msg.attach(MIMEText(html_body, "html", "utf-8"))
try:
await aiosmtplib.send(
msg,
hostname=SMTP_HOST,
port=SMTP_PORT,
username=SMTP_USER if SMTP_USER else None,
password=SMTP_PASSWORD if SMTP_PASSWORD else None,
start_tls=SMTP_USE_TLS,
)
_user_timestamps[user_id].append(now)
logger.info(f"Feedback von {username} ({category_label}) gesendet")
except Exception as e:
logger.error(f"Feedback-E-Mail fehlgeschlagen: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="E-Mail konnte nicht gesendet werden.",
)