From 98c8e8d545317521fcfda20bc5e0150dc68948c7 Mon Sep 17 00:00:00 2001
From: valueon
Date: Sun, 16 Mar 2025 02:15:07 +0100
Subject: [PATCH] stable secure version
---
gwserver/app.py | 94 ++++++++++++++++++++++++++++++++++++----
gwserver/auth.py | 70 ++++++++++++++++++++++++++++++
gwserver/data/files.json | 64 +++------------------------
gwserver/database.py | 15 ++++++-
gwserver/static/test.txt | 1 +
requirements.txt | 5 ++-
start.bat | 43 ------------------
start.sh | 2 +-
8 files changed, 180 insertions(+), 114 deletions(-)
create mode 100644 gwserver/auth.py
create mode 100644 gwserver/static/test.txt
delete mode 100644 start.bat
diff --git a/gwserver/app.py b/gwserver/app.py
index 571d66a2..28a019fc 100644
--- a/gwserver/app.py
+++ b/gwserver/app.py
@@ -1,8 +1,10 @@
-import uvicorn
-from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query
+from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, Response
from fastapi.staticfiles import StaticFiles
+from fastapi.security import OAuth2PasswordRequestForm
+
+import uvicorn
from typing import List, Dict, Any, Optional
import uuid
import os
@@ -10,6 +12,10 @@ import json
import asyncio
from datetime import datetime
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 (
Workspace,
@@ -21,8 +27,16 @@ from models import (
LogEntry,
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
logging.basicConfig(
@@ -32,18 +46,17 @@ logging.basicConfig(
)
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
app.add_middleware(
CORSMiddleware,
- allow_origins=["*"], # In Produktion einschränken
+ allow_origins=["http://localhost:8080"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
-
# Pfad zum Webparts-Verzeichnis
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)
# 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"])
async def root():
@@ -64,9 +79,34 @@ async def root():
async def get_test():
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
@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"""
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)}")
+@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
@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)):
diff --git a/gwserver/auth.py b/gwserver/auth.py
new file mode 100644
index 00000000..d69f9bed
--- /dev/null
+++ b/gwserver/auth.py
@@ -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
\ No newline at end of file
diff --git a/gwserver/data/files.json b/gwserver/data/files.json
index 547c68f6..79c93337 100644
--- a/gwserver/data/files.json
+++ b/gwserver/data/files.json
@@ -1,65 +1,11 @@
[
{
- "id": "file_001",
- "name": "Quartalsbericht Q1 2025.pdf",
+ "id": "0399d060-5beb-46e6-9c3c-830ae7a471cb",
+ "name": "Index.pdf",
"type": "document",
+ "path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\gwserver\\uploads\\0399d060-5beb-46e6-9c3c-830ae7a471cb.pdf",
"content_type": "application/pdf",
- "size": 2500000,
- "upload_date": "2025-03-14T00:25:25.076526",
- "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"
+ "size": 114510,
+ "upload_date": "2025-03-16T02:10:23.229879"
}
]
\ No newline at end of file
diff --git a/gwserver/database.py b/gwserver/database.py
index d29c026b..a5362c46 100644
--- a/gwserver/database.py
+++ b/gwserver/database.py
@@ -249,7 +249,20 @@ class Database:
files.append(file_data)
self._save_data(self.files_file, files)
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
def get_all_prompts(self) -> List[Dict[str, Any]]:
"""Gibt alle Prompts zurück"""
diff --git a/gwserver/static/test.txt b/gwserver/static/test.txt
new file mode 100644
index 00000000..bebc6e70
--- /dev/null
+++ b/gwserver/static/test.txt
@@ -0,0 +1 @@
+OK 1.0
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index aa3c3c15..bfa971ca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,4 +15,7 @@ pandas==2.0.3
openpyxl==3.1.2
PyPDF2==3.0.1
beautifulsoup4==4.12.2
-requests==2.31.0
\ No newline at end of file
+requests==2.31.0
+python-jose[cryptography]
+passlib==1.7.4
+bcrypt==3.2.0
\ No newline at end of file
diff --git a/start.bat b/start.bat
deleted file mode 100644
index 187f5989..00000000
--- a/start.bat
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/start.sh b/start.sh
index 15161540..7004af23 100644
--- a/start.sh
+++ b/start.sh
@@ -13,7 +13,7 @@ echo "----------------------------------------"
mkdir -p gwserver/data
mkdir -p gwserver/uploads
mkdir -p gwserver/results
-mkdir -p gwserver/webparts
+mkdir -p gwserver/static
# Prüfen, ob Python installiert ist
if command -v python3 &>/dev/null; then