from passlib.context import CryptContext from datetime import datetime, timedelta from typing import Optional, Dict import jwt import secrets import hashlib import os SECRET_KEY = os.getenv("JWT_SECRET") ALGORITHM = os.getenv("JWT_ALGORITHM") ACCESS_TOKEN_EXPIRE_MINUTES = os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES") REFRESH_TOKEN_EXPIRE_DAYS = os.getenv("REFRESH_TOKEN_EXPIRE_DAYS") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) def generate_verification_token() -> str: return secrets.token_urlsafe(32) def generate_reset_token() -> str: return secrets.token_urlsafe(32) def hash_token(token: str) -> str: return hashlib.sha256(token.encode()).hexdigest() def create_access_token(data: Dict, expires_delta: Optional[timedelta] = None) -> str: to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire, "iat": datetime.utcnow(), "type": "access"}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt def create_refresh_token(user_id: str) -> str: expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS) to_encode = { "sub": user_id, "exp": expire, "iat": datetime.utcnow(), "type": "refresh", "jti": secrets.token_urlsafe(16), # Unique ID } encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt def decode_token(token: str) -> Optional[Dict]: try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) return payload except jwt.ExpiredSignatureError: raise jwt.InvalidTokenError("Token expiré") except jwt.DecodeError: raise jwt.InvalidTokenError("Token invalide (format incorrect)") except jwt.InvalidTokenError as e: raise jwt.InvalidTokenError(f"Token invalide: {str(e)}") except Exception as e: raise jwt.InvalidTokenError(f"Erreur lors du décodage du token: {str(e)}") def validate_password_strength(password: str) -> tuple[bool, str]: if len(password) < 8: return False, "Le mot de passe doit contenir au moins 8 caractères" if not any(c.isupper() for c in password): return False, "Le mot de passe doit contenir au moins une majuscule" if not any(c.islower() for c in password): return False, "Le mot de passe doit contenir au moins une minuscule" if not any(c.isdigit() for c in password): return False, "Le mot de passe doit contenir au moins un chiffre" special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?" if not any(c in special_chars for c in password): return False, "Le mot de passe doit contenir au moins un caractère spécial" return True, ""