dpv007's picture
HF Deploy: strict external origin
af61076
Raw
History Blame Contribute Delete
4.75 kB
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}")
@router.post("/send-otp")
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"}
@router.post("/verify-otp")
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"}
@router.post("/reset-password")
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"}