Spaces:
Running
Running
| from fastapi import APIRouter, HTTPException, BackgroundTasks | |
| from pydantic import BaseModel, EmailStr | |
| import random | |
| import string | |
| import smtplib | |
| from email.message import EmailMessage | |
| import redis | |
| from app.core.config import settings | |
| router = APIRouter() | |
| redis_client = redis.from_url(settings.REDIS_URL, decode_responses=True) | |
| class OTPRequest(BaseModel): | |
| email: str | |
| from supabase import create_client | |
| # Initialize Supabase Admin Client | |
| # We use the service_role key to allow administrative actions like resetting passwords | |
| supabase_admin = None | |
| if settings.SUPABASE_URL and settings.SUPABASE_KEY: | |
| _clean_url = settings.SUPABASE_URL.strip().strip('"').strip("'") | |
| _clean_key = settings.SUPABASE_KEY.strip().strip('"').strip("'") | |
| try: | |
| supabase_admin = create_client(_clean_url, _clean_key) | |
| except Exception as e: | |
| print(f"Failed to initialize Supabase admin client: {e}") | |
| class OTPVerify(BaseModel): | |
| email: str | |
| otp: str | |
| class PasswordReset(BaseModel): | |
| email: str | |
| otp: str | |
| new_password: str | |
| import httpx | |
| def send_otp_email(to_email: str, otp: str): | |
| email_service_url = settings.EMAIL_SERVICE_URL | |
| api_key = settings.EMAIL_SERVICE_API_KEY | |
| if not email_service_url or not api_key: | |
| print(f"WARNING: Email microservice not configured. OTP for {to_email} is {otp}") | |
| return | |
| try: | |
| response = httpx.post( | |
| email_service_url, | |
| json={"to_email": to_email, "otp": otp, "apiKey": api_key}, | |
| timeout=60.0, | |
| follow_redirects=True | |
| ) | |
| response.raise_for_status() | |
| except Exception as e: | |
| print(f"Failed to trigger email microservice: {e}") | |
| def send_otp(req: OTPRequest, background_tasks: BackgroundTasks): | |
| email = req.email.strip().lower() | |
| if not email.endswith("@iitrpr.ac.in"): | |
| raise HTTPException(status_code=400, detail="Only @iitrpr.ac.in accounts are permitted.") | |
| # Generate 6 digit OTP | |
| otp = str(random.randint(100000, 999999)) | |
| # Store in Redis with 5 minutes (300 seconds) expiration | |
| redis_client.setex(f"otp:{email}", 300, otp) | |
| # Send email in background to avoid blocking the HTTP response | |
| background_tasks.add_task(send_otp_email, email, otp) | |
| return {"message": "OTP sent successfully"} | |
| def verify_otp(req: OTPVerify): | |
| email = req.email.strip().lower() | |
| stored_otp = redis_client.get(f"otp:{email}") | |
| if not stored_otp: | |
| raise HTTPException(status_code=400, detail="OTP expired or not found. Please request a new one.") | |
| if stored_otp != req.otp.strip(): | |
| raise HTTPException(status_code=400, detail="Invalid OTP.") | |
| # Delete OTP to prevent reuse | |
| redis_client.delete(f"otp:{email}") | |
| return {"message": "OTP verified successfully"} | |
| def reset_password(req: PasswordReset): | |
| email = req.email.strip().lower() | |
| # 1. Verify OTP | |
| stored_otp = redis_client.get(f"otp:{email}") | |
| if not stored_otp: | |
| raise HTTPException(status_code=400, detail="OTP expired or not found. Please request a new one.") | |
| if stored_otp != req.otp.strip(): | |
| raise HTTPException(status_code=400, detail="Invalid OTP.") | |
| # 2. Check Supabase Admin config | |
| if not supabase_admin: | |
| raise HTTPException(status_code=500, detail="Supabase Admin not configured on backend.") | |
| # 3. Find the user's UUID in Supabase | |
| target_uid = None | |
| try: | |
| # Note: In a massive app, list_users() needs pagination. | |
| # For a campus app, this is fine to find the user. | |
| users_resp = supabase_admin.auth.admin.list_users() | |
| for u in users_resp: | |
| if u.email.lower() == email: | |
| target_uid = u.id | |
| break | |
| except Exception as e: | |
| print(f"Error fetching users: {e}") | |
| raise HTTPException(status_code=500, detail="Error communicating with authentication server.") | |
| if not target_uid: | |
| raise HTTPException(status_code=404, detail="No account found with this email address.") | |
| # 4. Update the password | |
| try: | |
| supabase_admin.auth.admin.update_user_by_id(target_uid, {"password": req.new_password}) | |
| except Exception as e: | |
| print(f"Error updating password: {e}") | |
| # Strip any internal traceback if present, but pass the Supabase API error message | |
| error_msg = str(e) | |
| raise HTTPException(status_code=400, detail=f"Failed to reset password: {error_msg}") | |
| # 5. Clean up OTP on success | |
| redis_client.delete(f"otp:{email}") | |
| return {"message": "Password updated successfully"} | |