""" Authentication API Endpoints POST /api/v1/auth/login - Returns JWT access token """ from datetime import timedelta from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from pydantic import BaseModel from sqlalchemy.orm import Session from typing import Optional from app.database import get_db from app.models import User from app.auth import hash_password, verify_password, create_access_token, get_current_user router = APIRouter(tags=["auth"]) # ============== REQUEST/RESPONSE SCHEMAS ============== class LoginRequest(BaseModel): """Login request with email and password""" email: str password: str class LoginResponse(BaseModel): """Login response with JWT token""" access_token: str token_type: str = "bearer" role: str school_id: str assigned_sports: list = [] class TokenRefreshRequest(BaseModel): """Token refresh request (placeholder for future use)""" token: str # ============== ENDPOINTS ============== @router.post("/login", response_model=LoginResponse) async def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): """ Authenticate user and return JWT access token Uses OAuth2PasswordRequestForm for standard form-based auth. username field should contain email address. Returns: access_token: JWT token for Authorization header token_type: "bearer" role: User's role (at/coach/parent/admin) school_id: User's school affiliation """ # Find user by email (username field contains email) user = db.query(User).filter(User.email == form_data.username).first() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password", headers={"WWW-Authenticate": "Bearer"}, ) # Verify password if not verify_password(form_data.password, user.hashed_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password", headers={"WWW-Authenticate": "Bearer"}, ) # Check if user is active if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account is inactive" ) # Create JWT token access_token = create_access_token( user_id=user.id, role=user.role, school_id=user.school_id, assigned_sports=user.assigned_sports or [] ) return LoginResponse( access_token=access_token, token_type="bearer", role=user.role, school_id=user.school_id, assigned_sports=user.assigned_sports or [] ) @router.post("/login/json", response_model=LoginResponse) async def login_json( login_data: LoginRequest, db: Session = Depends(get_db) ): """ Authenticate user and return JWT access token (JSON variant) Alternative to form-based auth for JSON clients. Same functionality as /login. """ # Find user by email user = db.query(User).filter(User.email == login_data.email).first() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password", headers={"WWW-Authenticate": "Bearer"}, ) # Verify password if not verify_password(login_data.password, user.hashed_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password", headers={"WWW-Authenticate": "Bearer"}, ) # Check if user is active if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account is inactive" ) # Create JWT token access_token = create_access_token( user_id=user.id, role=user.role, school_id=user.school_id, assigned_sports=user.assigned_sports or [] ) return LoginResponse( access_token=access_token, token_type="bearer", role=user.role, school_id=user.school_id, assigned_sports=user.assigned_sports or [] ) @router.get("/me") async def get_current_user_info( current_user: User = Depends(get_current_user), ): """Get current authenticated user's info""" return { "id": current_user.id, "email": current_user.email, "role": current_user.role, "school_id": current_user.school_id, "first_name": current_user.first_name, "last_name": current_user.last_name, "is_active": current_user.is_active, "assigned_sports": current_user.assigned_sports or [], "assigned_teams": current_user.assigned_teams or [], }