stable secure version

This commit is contained in:
valueon 2025-03-16 02:15:07 +01:00
parent 5a0c7e0ae8
commit 98c8e8d545
8 changed files with 180 additions and 114 deletions

View file

@ -1,8 +1,10 @@
import uvicorn from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query, status
from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, Response from fastapi.responses import JSONResponse, Response
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.security import OAuth2PasswordRequestForm
import uvicorn
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
import uuid import uuid
import os import os
@ -10,6 +12,10 @@ import json
import asyncio import asyncio
from datetime import datetime from datetime import datetime
import logging import logging
from database import get_db, Database
from agent_service import AgentService, get_agent_service
from datetime import timedelta
from typing import Dict
from models import ( from models import (
Workspace, Workspace,
@ -21,8 +27,16 @@ from models import (
LogEntry, LogEntry,
Result Result
) )
from database import get_db, Database
from agent_service import AgentService, get_agent_service from auth import (
ACCESS_TOKEN_EXPIRE_MINUTES,
authenticate_user,
create_access_token,
fake_users_db,
get_current_user
)
# Konfiguration des Loggers # Konfiguration des Loggers
logging.basicConfig( logging.basicConfig(
@ -32,18 +46,17 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
app = FastAPI(title="Data Platform API", description="Backend-API für die Multi-Agent Datenplattform") app = FastAPI(title="PowerOn | Data Platform API", description="Backend-API für die Multi-Agent Platform von ValueOn AG")
# CORS-Konfiguration für Frontend-Anfragen # CORS-Konfiguration für Frontend-Anfragen
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], # In Produktion einschränken allow_origins=["http://localhost:8080"],
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
) )
# Pfad zum Webparts-Verzeichnis # Pfad zum Webparts-Verzeichnis
WEBPARTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webparts") WEBPARTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webparts")
@ -52,7 +65,9 @@ UPLOAD_DIR = os.path.join(os.getcwd(), "uploads")
os.makedirs(UPLOAD_DIR, exist_ok=True) os.makedirs(UPLOAD_DIR, exist_ok=True)
# Komponenten des Frontends # Komponenten des Frontends
app.mount("/webparts", StaticFiles(directory="webparts"), name="webparts") app.mount("/static", StaticFiles(directory="static"), name="static")
# API - ENDPUNKTE
@app.get("/", tags=["General"]) @app.get("/", tags=["General"])
async def root(): async def root():
@ -64,9 +79,34 @@ async def root():
async def get_test(): async def get_test():
return "OK 1.0" return "OK 1.0"
# Token-Endpunkt
@app.post("/token", response_model=Dict[str, str])
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Ungültige Zugangsdaten",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"]}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
# Geschützter Endpunkt (Beispiel)
@app.get("/api/users/me")
async def read_users_me(current_user = Depends(get_current_user)):
return current_user
# Workspace-Endpunkte # Workspace-Endpunkte
@app.get("/api/workspaces", tags=["Workspaces"], response_model=List[Workspace]) @app.get("/api/workspaces", tags=["Workspaces"], response_model=List[Workspace])
async def get_workspaces(db: Database = Depends(get_db)): async def get_workspaces(
current_user = Depends(get_current_user), # <-- Authentifizierung hinzugefügt
db: Database = Depends(get_db)
):
"""Alle verfügbaren Workspaces abrufen""" """Alle verfügbaren Workspaces abrufen"""
return db.get_all_workspaces() return db.get_all_workspaces()
@ -187,6 +227,42 @@ async def upload_file(
raise HTTPException(status_code=500, detail=f"Fehler beim Hochladen der Datei: {str(e)}") raise HTTPException(status_code=500, detail=f"Fehler beim Hochladen der Datei: {str(e)}")
@app.delete("/api/files/{file_id}", tags=["Files"])
async def delete_file(
file_id: str,
current_user = Depends(get_current_user),
db: Database = Depends(get_db)
):
"""Löscht eine Datei"""
try:
# Hole die Datei aus der Datenbank
file = db.get_file(file_id)
if not file:
raise HTTPException(status_code=404, detail=f"Datei mit ID {file_id} nicht gefunden")
# Prüfe, ob die physische Datei existiert
if "path" in file and os.path.exists(file["path"]):
try:
# Versuche, die physische Datei zu löschen
os.remove(file["path"])
except Exception as e:
logger.warning(f"Konnte physische Datei nicht löschen: {e}")
# Lösche die Datei aus der Datenbank
success = db.delete_file(file_id)
if not success:
raise HTTPException(status_code=500, detail="Fehler beim Löschen der Datei aus der Datenbank")
return {"success": True, "message": f"Datei '{file.get('name', 'unbekannt')}' wurde gelöscht"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Fehler beim Löschen der Datei: {e}")
raise HTTPException(status_code=500, detail=f"Fehler beim Löschen der Datei: {str(e)}")
# Prompt-Endpunkte # Prompt-Endpunkte
@app.get("/api/prompts", tags=["Prompts"], response_model=List[Prompt]) @app.get("/api/prompts", tags=["Prompts"], response_model=List[Prompt])
async def get_prompts(workspace_id: Optional[str] = Query(None), db: Database = Depends(get_db)): async def get_prompts(workspace_id: Optional[str] = Query(None), db: Database = Depends(get_db)):

70
gwserver/auth.py Normal file
View file

@ -0,0 +1,70 @@
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
# Konfigurationsvariablen
SECRET_KEY = "dein-geheimer-schlüssel" # In Produktion aus Umgebungsvariablen laden!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 600
# Password-Hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2 Setup
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Beispiel-Benutzer (in Produktion aus Datenbank laden)
fake_users_db = {
"admin": {
"username": "admin",
"hashed_password": pwd_context.hash("admin123"),
}
}
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return user_dict
return None
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user["hashed_password"]):
return False
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Ungültige Authentifizierungsdaten",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username)
if user is None:
raise credentials_exception
return user

View file

@ -1,65 +1,11 @@
[ [
{ {
"id": "file_001", "id": "0399d060-5beb-46e6-9c3c-830ae7a471cb",
"name": "Quartalsbericht Q1 2025.pdf", "name": "Index.pdf",
"type": "document", "type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\gwserver\\uploads\\0399d060-5beb-46e6-9c3c-830ae7a471cb.pdf",
"content_type": "application/pdf", "content_type": "application/pdf",
"size": 2500000, "size": 114510,
"upload_date": "2025-03-14T00:25:25.076526", "upload_date": "2025-03-16T02:10:23.229879"
"path": "uploads/dummy_file1.pdf"
},
{
"id": "file_002",
"name": "Marktanalyse-Diagramm.png",
"type": "image",
"content_type": "image/png",
"size": 1150000,
"upload_date": "2025-03-14T00:25:25.076526",
"path": "uploads/dummy_file2.png"
},
{
"id": "file_003",
"name": "Finanzdaten_2024-2025.xlsx",
"type": "document",
"content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size": 3880000,
"upload_date": "2025-03-14T00:25:25.076526",
"path": "uploads/dummy_file3.xlsx"
},
{
"id": "77bb2097-7044-4267-aae6-f2ca1c119ab8",
"name": "11 PowerOn HLD - Journey.pdf",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\77bb2097-7044-4267-aae6-f2ca1c119ab8.pdf",
"content_type": "application/pdf",
"size": 56687,
"upload_date": "2025-03-14T00:25:55.641089"
},
{
"id": "f066b3d6-7d59-4851-a8eb-de4334f4a64e",
"name": "11 PowerOn HLD - Journey.pdf",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\f066b3d6-7d59-4851-a8eb-de4334f4a64e.pdf",
"content_type": "application/pdf",
"size": 56687,
"upload_date": "2025-03-14T09:46:37.306858"
},
{
"id": "bb36ca5e-cae0-441d-b59c-feb80ecf6d51",
"name": "11 PowerOn HLD - Journey.pdf",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\bb36ca5e-cae0-441d-b59c-feb80ecf6d51.pdf",
"content_type": "application/pdf",
"size": 56687,
"upload_date": "2025-03-14T10:53:08.043663"
},
{
"id": "a40ec285-afab-45fe-9668-ba99d4da3a70",
"name": "ReportingSheet2025.xlsx",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\a40ec285-afab-45fe-9668-ba99d4da3a70.xlsx",
"content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size": 14727,
"upload_date": "2025-03-14T13:55:15.194910"
} }
] ]

View file

@ -250,6 +250,19 @@ class Database:
self._save_data(self.files_file, files) self._save_data(self.files_file, files)
return file_data return file_data
def delete_file(self, file_id: str) -> bool:
"""Löscht eine Datei aus der Datenbank"""
try:
index = next((i for i, file in enumerate(self.data["files"]) if file["id"] == file_id), -1)
if index == -1:
return False
self.data["files"].pop(index)
self._save_data()
return True
except Exception as e:
logger.error(f"Datenbankfehler beim Löschen der Datei: {e}")
return False
# Prompt-Methoden # Prompt-Methoden
def get_all_prompts(self) -> List[Dict[str, Any]]: def get_all_prompts(self) -> List[Dict[str, Any]]:
"""Gibt alle Prompts zurück""" """Gibt alle Prompts zurück"""

1
gwserver/static/test.txt Normal file
View file

@ -0,0 +1 @@
OK 1.0

View file

@ -16,3 +16,6 @@ openpyxl==3.1.2
PyPDF2==3.0.1 PyPDF2==3.0.1
beautifulsoup4==4.12.2 beautifulsoup4==4.12.2
requests==2.31.0 requests==2.31.0
python-jose[cryptography]
passlib==1.7.4
bcrypt==3.2.0

View file

@ -1,43 +0,0 @@
@echo off
echo Data Platform - Multi-Agent Service
echo Startskript fuer gwserver
echo ----------------------------------------
:: Verzeichnisstruktur erstellen, falls sie nicht existiert
if not exist gwserver\data mkdir gwserver\data
if not exist gwserver\uploads mkdir gwserver\uploads
if not exist gwserver\results mkdir gwserver\results
if not exist gwserver\webparts mkdir gwserver\webparts
:: Prüfen, ob Python installiert ist
python --version >nul 2>&1
if %errorlevel% neq 0 (
echo Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder hoeher.
exit /b 1
)
:: Virtuelle Umgebung erstellen, falls sie nicht existiert
if not exist gwserver\venv (
echo Erstelle virtuelle Python-Umgebung...
cd gwserver
python -m venv venv
cd ..
)
:: Virtuelle Umgebung aktivieren
echo Aktiviere virtuelle Umgebung...
call gwserver\venv\Scripts\activate
:: Abhängigkeiten installieren
echo Installiere Abhaengigkeiten...
pip install -r requirements.txt
:: Starte gwserver in neuem Fenster
echo Starte gwserver-Server...
start cmd /k "cd gwserver && call venv\Scripts\activate && uvicorn app:app --reload --host 0.0.0.0 --port 8000"
echo ----------------------------------------
echo Server wurden gestartet!
echo Gateway laeuft auf: http://localhost:8000
echo API-Dokumentation: http://localhost:8000/docs
pause

View file

@ -13,7 +13,7 @@ echo "----------------------------------------"
mkdir -p gwserver/data mkdir -p gwserver/data
mkdir -p gwserver/uploads mkdir -p gwserver/uploads
mkdir -p gwserver/results mkdir -p gwserver/results
mkdir -p gwserver/webparts mkdir -p gwserver/static
# Prüfen, ob Python installiert ist # Prüfen, ob Python installiert ist
if command -v python3 &>/dev/null; then if command -v python3 &>/dev/null; then