diff --git a/app/config.py b/app/config.py index 20b9c7f..765338f 100644 --- a/app/config.py +++ b/app/config.py @@ -1,6 +1,7 @@ import os DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_db") +DATABASE_URL_DEMO = os.getenv("DATABASE_URL_DEMO", "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_demo") SECRET_KEY = os.getenv("SECRET_KEY", "slpm-patchcenter-secret-key-2026-change-in-production") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 60 # 8 heures diff --git a/app/database.py b/app/database.py index d3b7c90..2ebbb8a 100644 --- a/app/database.py +++ b/app/database.py @@ -1,7 +1,12 @@ -"""SQLAlchemy engine et session""" +"""SQLAlchemy engine et session — supporte mode reel et demo""" from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from .config import DATABASE_URL +from .config import DATABASE_URL, DATABASE_URL_DEMO +# Production engine engine = create_engine(DATABASE_URL, pool_pre_ping=True, pool_size=10) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# Demo engine +engine_demo = create_engine(DATABASE_URL_DEMO, pool_pre_ping=True, pool_size=5) +SessionLocalDemo = sessionmaker(autocommit=False, autoflush=False, bind=engine_demo) diff --git a/app/dependencies.py b/app/dependencies.py index 4773fbe..8b52545 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -2,11 +2,28 @@ from fastapi import Request from sqlalchemy import text from .auth import decode_token -from .database import SessionLocal +from .database import SessionLocal, SessionLocalDemo -def get_db(): - db = SessionLocal() +def get_db(request: Request = None): + """Retourne la session DB selon le mode (demo/reel) stocke dans le cookie JWT.""" + demo = False + if request: + user = get_current_user(request) + if user and user.get("mode") == "demo": + demo = True + factory = SessionLocalDemo if demo else SessionLocal + db = factory() + try: + yield db + finally: + db.close() + + +def get_db_for_login(demo: bool = False): + """Session DB pour le login (avant que le cookie existe).""" + factory = SessionLocalDemo if demo else SessionLocal + db = factory() try: yield db finally: diff --git a/app/routers/auth.py b/app/routers/auth.py index c024d93..3c8a290 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -2,7 +2,8 @@ from fastapi import APIRouter, Request, Depends, Form from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy import text -from ..dependencies import get_db, get_current_user +from ..dependencies import get_current_user +from ..database import SessionLocal, SessionLocalDemo from ..auth import verify_password, create_access_token, hash_password from ..services.audit_service import log_login, log_logout, log_login_failed from ..config import APP_NAME, APP_VERSION @@ -17,52 +18,63 @@ async def login_page(request: Request): }) @router.post("/login") -async def login(request: Request, username: str = Form(...), password: str = Form(...), db=Depends(get_db)): - row = db.execute(text("SELECT id, username, password_hash, role, is_active FROM users WHERE LOWER(username) = LOWER(:u)"), - {"u": username}).fetchone() - if not row: - log_login_failed(db, request, username) - db.commit() - return templates.TemplateResponse("login.html", { - "request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Utilisateur inconnu" - }) - if not row.is_active: - log_login_failed(db, request, username) - db.commit() - return templates.TemplateResponse("login.html", { - "request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Compte desactive" - }) +async def login(request: Request, username: str = Form(...), password: str = Form(...), + mode: str = Form("reel")): + # Select DB based on mode + factory = SessionLocalDemo if mode == "demo" else SessionLocal + db = factory() try: - ok = verify_password(password, row.password_hash) - except Exception: - ok = False - if not ok: - log_login_failed(db, request, username) + row = db.execute(text("SELECT id, username, password_hash, role, is_active FROM users WHERE LOWER(username) = LOWER(:u)"), + {"u": username}).fetchone() + if not row: + log_login_failed(db, request, username) + db.commit() + return templates.TemplateResponse("login.html", { + "request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Utilisateur inconnu" + }) + if not row.is_active: + log_login_failed(db, request, username) + db.commit() + return templates.TemplateResponse("login.html", { + "request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Compte desactive" + }) + try: + ok = verify_password(password, row.password_hash) + except Exception: + ok = False + if not ok: + log_login_failed(db, request, username) + db.commit() + return templates.TemplateResponse("login.html", { + "request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Mot de passe incorrect" + }) + # Include mode in JWT token + token = create_access_token({"sub": row.username, "role": row.role, "uid": row.id, "mode": mode}) + user = {"sub": row.username, "role": row.role, "uid": row.id, "mode": mode} + log_login(db, request, user) db.commit() - return templates.TemplateResponse("login.html", { - "request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Mot de passe incorrect" - }) - token = create_access_token({"sub": row.username, "role": row.role, "uid": row.id}) - user = {"sub": row.username, "role": row.role, "uid": row.id} - log_login(db, request, user) - db.commit() - # Redirect qw_only users to quickwin - perms = db.execute(text("SELECT module FROM user_permissions WHERE user_id = :uid"), {"uid": row.id}).fetchall() - modules = {r.module for r in perms} - if modules == {"quickwin"}: - redirect_url = "/quickwin" - else: - redirect_url = "/dashboard" - response = RedirectResponse(url=redirect_url, status_code=303) - response.set_cookie(key="access_token", value=token, httponly=True, samesite="lax", max_age=3600) - return response + # Redirect + perms = db.execute(text("SELECT module FROM user_permissions WHERE user_id = :uid"), {"uid": row.id}).fetchall() + modules = {r.module for r in perms} + redirect_url = "/quickwin" if modules == {"quickwin"} else "/dashboard" + response = RedirectResponse(url=redirect_url, status_code=303) + response.set_cookie(key="access_token", value=token, httponly=True, samesite="lax", max_age=3600) + return response + finally: + db.close() @router.get("/logout") -async def logout(request: Request, db=Depends(get_db)): +async def logout(request: Request): user = get_current_user(request) if user: - log_logout(db, request, user) - db.commit() + mode = user.get("mode", "reel") + factory = SessionLocalDemo if mode == "demo" else SessionLocal + db = factory() + try: + log_logout(db, request, user) + db.commit() + finally: + db.close() response = RedirectResponse(url="/login", status_code=302) response.delete_cookie("access_token") return response diff --git a/app/templates/login.html b/app/templates/login.html index 8a484c1..fb27e5c 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -20,6 +20,17 @@ +
SANEF — Direction des Systèmes d'Information