updates Mar6

This commit is contained in:
belviskhoremk
2026-03-06 22:37:40 +00:00
parent 2ed998058e
commit 9dccc83293
23 changed files with 2257 additions and 74 deletions

View File

@@ -2,15 +2,34 @@ from fastapi import APIRouter, HTTPException, status, Depends
from app.models import UserSignup, UserLogin, UserResponse, TokenResponse
from app.database import get_supabase
from app.dependencies import get_current_user
from app.config import settings
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
import logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/auth", tags=["Authentication"])
class ForgotPasswordRequest(BaseModel):
email: EmailStr
class ResetPasswordRequest(BaseModel):
access_token: str
new_password: str = Field(min_length=8)
class ProfileUpdate(BaseModel):
company_name: Optional[str] = None
current_password: Optional[str] = None
new_password: Optional[str] = Field(default=None, min_length=8)
@router.post("/signup", response_model=TokenResponse)
async def signup(data: UserSignup):
supabase = get_supabase()
user_id = None
try:
# Create auth user
auth_resp = supabase.auth.sign_up(
@@ -20,6 +39,7 @@ async def signup(data: UserSignup):
raise HTTPException(status_code=400, detail="Failed to create account")
user = auth_resp.user
user_id = user.id
# Create company record
supabase.table("companies").insert(
@@ -52,6 +72,12 @@ async def signup(data: UserSignup):
raise
except Exception as e:
logger.error(f"Signup error: {e}")
# Rollback auth user if company/subscription creation failed
if user_id and "already registered" not in str(e).lower():
try:
supabase.auth.admin.delete_user(user_id)
except Exception as rb_err:
logger.error(f"Signup rollback failed: {rb_err}")
if "already registered" in str(e).lower() or "already exists" in str(e).lower():
raise HTTPException(status_code=400, detail="Email already registered")
raise HTTPException(status_code=400, detail=str(e))
@@ -99,6 +125,85 @@ async def login(data: UserLogin):
raise HTTPException(status_code=401, detail="Invalid credentials")
@router.post("/forgot-password")
async def forgot_password(data: ForgotPasswordRequest):
"""Send password reset email via Supabase. Always returns success to prevent email enumeration."""
supabase = get_supabase()
try:
supabase.auth.reset_password_for_email(
data.email,
options={"redirect_to": f"{settings.app_url}/reset-password"},
)
except Exception as e:
logger.warning(f"Forgot password request error (suppressed): {e}")
return {"message": "If that email is registered, a password reset link has been sent."}
@router.post("/reset-password")
async def reset_password(data: ResetPasswordRequest):
"""Reset user password using the recovery token from the reset email."""
supabase = get_supabase()
try:
user_response = supabase.auth.get_user(data.access_token)
if not user_response.user:
raise HTTPException(status_code=400, detail="Invalid or expired reset token")
supabase.auth.admin.update_user_by_id(
user_response.user.id,
{"password": data.new_password},
)
return {"message": "Password updated successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Password reset error: {e}")
raise HTTPException(status_code=400, detail="Invalid or expired reset token")
@router.patch("/profile", response_model=UserResponse)
async def update_profile(data: ProfileUpdate, user=Depends(get_current_user)):
"""Update company name and/or password."""
supabase = get_supabase()
if data.company_name:
supabase.table("companies").update({"name": data.company_name}).eq("owner_id", user.id).execute()
if data.new_password:
if not data.current_password:
raise HTTPException(status_code=400, detail="Current password required to change password")
try:
supabase.auth.sign_in_with_password({"email": user.email, "password": data.current_password})
except Exception:
raise HTTPException(status_code=400, detail="Current password is incorrect")
supabase.auth.admin.update_user_by_id(user.id, {"password": data.new_password})
company = supabase.table("companies").select("name").eq("owner_id", user.id).execute()
company_name = company.data[0]["name"] if company.data else ""
sub = supabase.table("subscriptions").select("plan").eq("user_id", user.id).eq("status", "active").execute()
plan = sub.data[0]["plan"] if sub.data else "free"
return UserResponse(id=user.id, email=user.email, company_name=company_name, plan=plan)
@router.delete("/account")
async def delete_account(user=Depends(get_current_user)):
"""Permanently delete account, company, chatbots, and all data."""
supabase = get_supabase()
company = supabase.table("companies").select("id").eq("owner_id", user.id).execute()
if company.data:
supabase.table("companies").delete().eq("id", company.data[0]["id"]).execute()
supabase.table("subscriptions").delete().eq("user_id", user.id).execute()
try:
supabase.auth.admin.delete_user(user.id)
except Exception as e:
logger.error(f"Failed to delete auth user {user.id}: {e}")
raise HTTPException(status_code=500, detail="Failed to delete account")
return {"message": "Account deleted successfully"}
@router.post("/logout")
async def logout(user=Depends(get_current_user)):
supabase = get_supabase()