Spaces:
Paused
Paused
| """Simple email + password JWT authentication for ClassLens.""" | |
| from __future__ import annotations | |
| from datetime import datetime, timedelta | |
| from typing import Optional | |
| import bcrypt | |
| from jose import JWTError, jwt | |
| from fastapi import Depends, HTTPException, status | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| from .config import get_settings | |
| from .database import get_user_by_email, get_user_by_id, create_user, update_last_login | |
| security = HTTPBearer(auto_error=False) | |
| def hash_password(password: str) -> str: | |
| return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") | |
| def verify_password(plain: str, hashed: str) -> bool: | |
| return bcrypt.checkpw(plain.encode("utf-8"), hashed.encode("utf-8")) | |
| def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: | |
| settings = get_settings() | |
| to_encode = data.copy() | |
| expire = datetime.utcnow() + (expires_delta or timedelta(minutes=settings.jwt_expire_minutes)) | |
| to_encode.update({"exp": expire}) | |
| return jwt.encode(to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm) | |
| async def get_current_user( | |
| credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), | |
| ) -> dict: | |
| """FastAPI dependency: extract and validate JWT, return user dict.""" | |
| if credentials is None: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") | |
| settings = get_settings() | |
| try: | |
| payload = jwt.decode( | |
| credentials.credentials, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm] | |
| ) | |
| user_id: int = payload.get("sub") | |
| if user_id is None: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") | |
| except JWTError: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") | |
| user = await get_user_by_id(int(user_id)) | |
| if user is None: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found") | |
| return user | |
| async def register_user(email: str, password: str) -> dict: | |
| """Register a new user. Returns user dict.""" | |
| existing = await get_user_by_email(email) | |
| if existing: | |
| raise HTTPException(status_code=400, detail="Email already registered") | |
| hashed = hash_password(password) | |
| user_id = await create_user(email, hashed) | |
| user = await get_user_by_id(user_id) | |
| return user | |
| async def login_user(email: str, password: str) -> dict: | |
| """Validate credentials and return user dict + JWT token.""" | |
| user = await get_user_by_email(email) | |
| if not user or not verify_password(password, user["password_hash"]): | |
| raise HTTPException(status_code=401, detail="Invalid email or password") | |
| await update_last_login(user["id"]) | |
| token = create_access_token({"sub": str(user["id"])}) | |
| return {"token": token, "user": {"id": user["id"], "email": user["email"], "display_name": user["display_name"]}} | |