From c3b10508d9c75502306f07f4756e73e73ef01884 Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 7 Oct 2026 01:09:55 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20impl=C3=A9mentation=20des=20sessions=20?= =?UTF-8?q?JWT=20avec=20cookies=20s=C3=A9curis=C3=A9s=20(expiration=2060?= =?UTF-8?q?=20min)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/auth.py | 28 +++++++++++++++++++++++++++- backend/main.py | 35 +++++++++++++++++++++++++++++------ requirements.txt | 3 ++- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/backend/auth.py b/backend/auth.py index e671d25..594118e 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -21,4 +21,30 @@ def get_totp_uri(secret: str, username: str) -> str: def verify_totp(secret: str, code: str) -> bool: """Vérifie le code à 6 chiffres soumis par l'utilisateur.""" totp = pyotp.TOTP(secret) - return totp.verify(code) \ No newline at end of file + return totp.verify(code) + +import jwt +from datetime import datetime, timedelta, timezone +import os + +# Dans l'idéal, cette clé secrète sera à placer dans votre fichier .env plus tard +SECRET_KEY = os.getenv("SECRET_KEY", "`*\$Tu*CCc5hglT$mX'HykxERDljBz") +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 60 + +def create_access_token(data: dict): + """Crée un jeton JWT valable 60 minutes.""" + to_encode = data.copy() + expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + +def verify_token(token: str): + """Vérifie la validité du jeton JWT.""" + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + return payload + except jwt.ExpiredSignatureError: + return None # Le jeton a expiré (60 min écoulées) + except jwt.InvalidTokenError: + return None # Le jeton est invalide \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 9ed7737..db9365a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, Depends, HTTPException, status +from fastapi import FastAPI, Depends, HTTPException, status, Request, Response from fastapi.responses import RedirectResponse, FileResponse from sqlalchemy.orm import Session import os @@ -6,7 +6,7 @@ import os # Importation de nos modules locaux from .database import engine, Base, get_db from .models import User -from .auth import get_password_hash, generate_totp_secret, get_totp_uri, verify_password, verify_totp +from .auth import get_password_hash, generate_totp_secret, get_totp_uri, verify_password, verify_totp, create_access_token, verify_token from .schemas import AdminCreate, LoginRequest # Création des tables dans la base de données @@ -85,8 +85,8 @@ def create_admin(admin_data: AdminCreate, db: Session = Depends(get_db)): } @app.post("/api/login") -def login(login_data: LoginRequest, db: Session = Depends(get_db)): - """Vérifie les identifiants et le code 2FA.""" +def login(login_data: LoginRequest, response: Response, db: Session = Depends(get_db)): + """Vérifie les identifiants et crée une session de 60 minutes.""" user = db.query(User).filter(User.username == login_data.username).first() # Vérification de l'utilisateur et du mot de passe @@ -103,8 +103,31 @@ def login(login_data: LoginRequest, db: Session = Depends(get_db)): detail="Code 2FA invalide." ) + # Création du jeton de session + access_token = create_access_token(data={"sub": user.username}) + + # Injection du jeton dans un cookie sécurisé (invisible pour le JavaScript côté client) + response.set_cookie( + key="session_token", + value=access_token, + httponly=True, + max_age=3600, # 3600 secondes = 60 minutes + samesite="lax" + ) + return {"message": "Connexion réussie"} @app.get("/dashboard") -def dashboard(): - return {"message": "Bienvenue sur le tableau de bord de l'Orchestrateur AETHAS38 ! (Interface à venir)"} \ No newline at end of file +def dashboard(request: Request): + """Route protégée : nécessite un cookie de session valide.""" + token = request.cookies.get("session_token") + if not token: + return RedirectResponse(url="/login") + + payload = verify_token(token) + if not payload: + # Si le token a expiré (60 min) ou est invalide, retour au login + return RedirectResponse(url="/login") + + username = payload.get("sub") + return {"message": f"Bienvenue sur le tableau de bord sécurisé, {username} !"} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bba0705..ab025ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ psutil python-dotenv python-multipart bcrypt -email-validator \ No newline at end of file +email-validator +PyJWT \ No newline at end of file