from fastapi import APIRouter, HTTPException, Response, Request from pydantic import BaseModel, EmailStr from config.database import get_supabase, get_supabase_admin from config.settings import get_settings router = APIRouter(prefix="/api/auth", tags=["auth"]) settings = get_settings() class RegisterRequest(BaseModel): email: EmailStr password: str display_name: str = "" class LoginRequest(BaseModel): email: EmailStr password: str @router.post("/register") async def register(data: RegisterRequest): supabase = get_supabase() try: response = supabase.auth.sign_up({ "email": data.email, "password": data.password, "options": { "data": { "full_name": data.display_name or data.email.split("@")[0] } } }) if response.user is None: raise HTTPException(status_code=400, detail="Registration failed") # Sync to public users table admin_db = get_supabase_admin() admin_db.table("users").insert({ "id": response.user.id, "email": data.email, "display_name": data.display_name or data.email.split("@")[0], "role": "free" }).execute() return {"message": "Registration successful. Please check your email to verify your account."} except Exception as e: error_msg = str(e) if "23505" in error_msg or "users_pkey" in error_msg: raise HTTPException(status_code=400, detail="An account with this email already exists.") # Hide raw database dictionary strings if they slip through if error_msg.startswith("{") and "'code'" in error_msg: import ast try: err_dict = ast.literal_eval(error_msg) if isinstance(err_dict, dict) and "message" in err_dict: # Still, we might not want to expose raw DB messages, but it's better than the dict raise HTTPException(status_code=400, detail=err_dict["message"]) except (ValueError, SyntaxError): pass raise HTTPException(status_code=400, detail="An error occurred during registration.") raise HTTPException(status_code=400, detail=error_msg) @router.post("/login") async def login(data: LoginRequest, response: Response): supabase = get_supabase() try: auth_response = supabase.auth.sign_in_with_password({ "email": data.email, "password": data.password }) if auth_response.user is None: raise HTTPException(status_code=401, detail="Invalid email or password") response.set_cookie( key="access_token", value=auth_response.session.access_token, httponly=True, secure=True, samesite="lax", max_age=604800 ) return { "message": "Login successful", "user": { "id": str(auth_response.user.id), "email": auth_response.user.email, "display_name": auth_response.user.user_metadata.get("full_name", "") } } except Exception as e: raise HTTPException(status_code=401, detail="Invalid email or password") @router.post("/logout") async def logout(response: Response): response.delete_cookie("access_token") return {"message": "Logged out successfully"} @router.post("/forgot-password") async def forgot_password(email: EmailStr): supabase = get_supabase() try: supabase.auth.reset_password_email( email, options={"redirect_to": f"{settings.frontend_url}/auth/reset-password"} ) return {"message": "Password reset email sent"} except Exception as e: raise HTTPException(status_code=400, detail=str(e))