""" Authentication routes for user login, registration, and token management """ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from sqlalchemy.orm import Session from pydantic import BaseModel, EmailStr from typing import Optional import bcrypt from jose import jwt from datetime import datetime, timedelta from app.database import get_db from app.models.user import User router = APIRouter() # JWT Configuration SECRET_KEY = "your-secret-key-here" # In production, use environment variable ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token") # Pydantic models class UserCreate(BaseModel): email: EmailStr username: str password: str full_name: Optional[str] = None class UserResponse(BaseModel): id: int email: str username: str full_name: Optional[str] is_active: bool is_verified: bool class Config: from_attributes = True class Token(BaseModel): access_token: str token_type: str # Authentication utilities def hash_password(password: str) -> str: """Hash a password using bcrypt""" return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') def verify_password(password: str, hashed_password: str) -> bool: """Verify a password against its hash""" return bcrypt.checkpw(password.encode('utf-8'), hashed_password.encode('utf-8')) def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): """Create a JWT access token""" 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), db: Session = Depends(get_db)): """Get current authenticated user""" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", 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 jwt.JWTError: raise credentials_exception user = db.query(User).filter(User.username == username).first() if user is None: raise credentials_exception return user # Routes @router.post("/register", response_model=UserResponse) async def register_user(user_data: UserCreate, db: Session = Depends(get_db)): """Register a new user""" # Check if user already exists if db.query(User).filter(User.email == user_data.email).first(): raise HTTPException(status_code=400, detail="Email already registered") if db.query(User).filter(User.username == user_data.username).first(): raise HTTPException(status_code=400, detail="Username already taken") # Create new user hashed_password = hash_password(user_data.password) new_user = User( email=user_data.email, username=user_data.username, hashed_password=hashed_password, full_name=user_data.full_name ) db.add(new_user) db.commit() db.refresh(new_user) return new_user @router.post("/token", response_model=Token) async def login_for_access_token( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): """Login and get access token""" user = db.query(User).filter(User.username == form_data.username).first() if not user or not verify_password(form_data.password, user.hashed_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", 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"} @router.get("/me", response_model=UserResponse) async def read_users_me(current_user: User = Depends(get_current_user)): """Get current user info""" return current_user