from fastapi import APIRouter, HTTPException, status, Depends, Request, Query from pydantic import BaseModel, EmailStr from app.auth import get_user_manager, AuthenticationBackendWithRefresh, bearer_transport, get_jwt_strategy, get_refresh_jwt_strategy from fastapi.responses import JSONResponse router = APIRouter() class MagicLinkRequest(BaseModel): email: EmailStr # Path: POST /api/v1/auth/magic-link @router.post('/magic-link', status_code=status.HTTP_200_OK) async def send_magic_link(payload: MagicLinkRequest, request: Request, user_manager=Depends(get_user_manager)): """Generate a one-time magic-link token and *log* it for now. In production this should email the user. For Phase-4 backend milestone we simply issue the verification token and return it in the response so the frontend can test the flow without an email provider. """ # Ensure user exists (create guest if not) user = await user_manager.get_by_email(payload.email) if user is None: # Auto-register guest account (inactive until verified) user_in = { 'email': payload.email, 'password': '', # FastAPI Users requires but we will bypass login } # Using UserCreate model generically – relies on fastapi-users internals try: user = await user_manager.create(user_in, safe=True, request=request) except Exception: raise HTTPException(status_code=400, detail='Unable to create account') verification_token = await user_manager.generate_verification_token(user) # TODO: send email instead of returning token return {'detail': 'Magic link generated (token returned for dev)', 'token': verification_token} @router.get('/magic-link/verify') async def verify_magic_link(token: str = Query(...), request: Request = None, user_manager=Depends(get_user_manager)): """Verify incoming token and issue standard JWT + refresh tokens.""" try: user = await user_manager.verify(token, request) except Exception: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid or expired token') # Issue JWT + refresh using existing backend routines access_strategy = get_jwt_strategy() refresh_strategy = get_refresh_jwt_strategy() access_token = await access_strategy.write_token(user) refresh_token = await refresh_strategy.write_token(user) return JSONResponse( { 'access_token': access_token, 'refresh_token': refresh_token, 'token_type': 'bearer', } )