"""Authentication API routes.""" from fastapi import APIRouter, HTTPException, status from app.api.dependencies import AuthServiceDep, CurrentUser, DBSession from app.schemas.auth import ( LoginRequest, RefreshTokenRequest, Token, UserCreate, UserResponse, UserUpdate, ) from app.services.auth_service import AuthService router = APIRouter(prefix="/auth", tags=["authentication"]) @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def register( user_data: UserCreate, auth_service: AuthServiceDep, ): """ Register a new user. - **email**: Valid email address - **username**: 3-50 characters, unique - **password**: Min 8 characters - **display_name**: Optional display name """ try: user = await auth_service.register( email=user_data.email, username=user_data.username, password=user_data.password, display_name=user_data.display_name, ) return UserResponse.model_validate(user) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) @router.post("/login", response_model=Token) async def login( credentials: LoginRequest, auth_service: AuthServiceDep, ): """ Login with email and password. Returns access and refresh tokens. """ try: user = await auth_service.login( email=credentials.email, password=credentials.password, ) access_token, refresh_token = auth_service.create_tokens(user.id) return Token( access_token=access_token, refresh_token=refresh_token, expires_in=15 * 60, # 15 minutes ) except ValueError as e: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e), headers={"WWW-Authenticate": "Bearer"}, ) @router.post("/refresh", response_model=Token) async def refresh_token( token_data: RefreshTokenRequest, auth_service: AuthServiceDep, ): """ Refresh access token using refresh token. Returns new access and refresh tokens. """ from app.core.security import decode_token try: payload = decode_token(token_data.refresh_token) user_id = payload.get("sub") token_type = payload.get("type") if user_id is None or token_type != "refresh": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token", ) # Verify user still exists user = await auth_service.get_user_by_id(user_id) if user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found", ) # Create new tokens access_token, refresh_token = auth_service.create_tokens(user.id) return Token( access_token=access_token, refresh_token=refresh_token, expires_in=15 * 60, ) except Exception: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token", ) @router.get("/me", response_model=UserResponse) async def get_current_user( current_user: CurrentUser, ): """ Get current authenticated user profile. Requires authentication. """ return UserResponse.model_validate(current_user) @router.put("/me", response_model=UserResponse) async def update_current_user( user_data: UserUpdate, current_user: CurrentUser, auth_service: AuthServiceDep, ): """ Update current user profile. Requires authentication. """ try: updated_user = await auth_service.update_user( user_id=current_user.id, display_name=user_data.display_name, avatar_url=user_data.avatar_url, date_of_birth=user_data.date_of_birth, country=user_data.country, ) return UserResponse.model_validate(updated_user) except ValueError as e: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e), ) @router.post("/logout", status_code=status.HTTP_204_NO_CONTENT) async def logout( current_user: CurrentUser, ): """ Logout current user. In a stateless JWT setup, this is mainly for client-side cleanup. The token will expire automatically. Requires authentication. """ # In production, you might want to: # - Add token to blacklist (Redis) # - Remove refresh token from database # - Log the logout event return None