Spaces:
Sleeping
Sleeping
| """ | |
| OTP (One-Time Password) service for email-based authentication. | |
| """ | |
| import random | |
| import string | |
| from datetime import datetime, timedelta | |
| from typing import Dict, Optional | |
| from sqlalchemy.orm import Session | |
| from fastapi import HTTPException | |
| from .models import User | |
| from .brevo_service import send_otp_email | |
| # Store OTPs in memory (in production, use Redis or database) | |
| otp_store: Dict[str, dict] = {} | |
| def generate_otp(length: int = 6) -> str: | |
| """ | |
| Generate a random OTP code. | |
| Args: | |
| length: Length of OTP (default: 6) | |
| Returns: | |
| Random OTP string | |
| """ | |
| return ''.join(random.choices(string.digits, k=length)) | |
| async def request_otp(email: str, db: Session) -> dict: | |
| """ | |
| Generate and send OTP to email using Brevo. | |
| Args: | |
| email: Email address to send OTP to | |
| db: Database session | |
| Returns: | |
| Dictionary with success message | |
| """ | |
| # Generate OTP | |
| otp = generate_otp() | |
| expires_at = datetime.utcnow() + timedelta(minutes=10) | |
| # Store OTP (in production, use Redis or database with TTL) | |
| otp_store[email.lower()] = { | |
| 'otp': otp, | |
| 'expires_at': expires_at, | |
| 'attempts': 0, | |
| 'max_attempts': 5 | |
| } | |
| # Send OTP via Brevo | |
| try: | |
| await send_otp_email(email, otp) | |
| print(f"[INFO] OTP generated and sent to {email}") | |
| except Exception as e: | |
| # Remove OTP from store if email sending failed | |
| if email.lower() in otp_store: | |
| del otp_store[email.lower()] | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Failed to send OTP email: {str(e)}" | |
| ) | |
| return { | |
| "message": "OTP sent to your email address", | |
| "expires_in_minutes": 10 | |
| } | |
| async def verify_otp(email: str, otp: str, db: Session) -> User: | |
| """ | |
| Verify OTP and return/create user. | |
| Args: | |
| email: Email address | |
| otp: OTP code to verify | |
| db: Database session | |
| Returns: | |
| User object | |
| Raises: | |
| HTTPException: If OTP is invalid, expired, or max attempts exceeded | |
| """ | |
| email_lower = email.lower() | |
| stored = otp_store.get(email_lower) | |
| if not stored: | |
| raise HTTPException( | |
| status_code=400, | |
| detail="OTP not found. Please request a new OTP." | |
| ) | |
| # Check if expired | |
| if datetime.utcnow() > stored['expires_at']: | |
| del otp_store[email_lower] | |
| raise HTTPException( | |
| status_code=400, | |
| detail="OTP has expired. Please request a new OTP." | |
| ) | |
| # Check max attempts | |
| if stored['attempts'] >= stored['max_attempts']: | |
| del otp_store[email_lower] | |
| raise HTTPException( | |
| status_code=400, | |
| detail="Maximum verification attempts exceeded. Please request a new OTP." | |
| ) | |
| # Verify OTP | |
| if stored['otp'] != otp: | |
| stored['attempts'] += 1 | |
| remaining_attempts = stored['max_attempts'] - stored['attempts'] | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"Invalid OTP. {remaining_attempts} attempt(s) remaining." | |
| ) | |
| # OTP verified successfully | |
| # Get or create user | |
| user = db.query(User).filter(User.email == email_lower).first() | |
| if not user: | |
| user = User( | |
| email=email_lower, | |
| auth_method='otp', | |
| email_verified=True | |
| ) | |
| db.add(user) | |
| db.commit() | |
| db.refresh(user) | |
| print(f"[INFO] New user created via OTP: {email_lower}") | |
| # Enrich contact data from Apollo.io and update Brevo + Monday.com | |
| try: | |
| from .apollo_service import enrich_contact_by_email | |
| from .brevo_service import create_brevo_contact, BREVO_TRIAL_LIST_ID | |
| from .monday_service import create_monday_lead | |
| # Enrich contact data from Apollo.io | |
| enriched_data = await enrich_contact_by_email(email_lower) | |
| # Use enriched data if available | |
| first_name = enriched_data.get("first_name") if enriched_data else None | |
| last_name = enriched_data.get("last_name") if enriched_data else None | |
| org_name = enriched_data.get("organization_name") if enriched_data else None | |
| # Fallback to email domain if Apollo didn't provide organization | |
| if not org_name: | |
| org_domain = email_lower.split('@')[1] if '@' in email_lower else None | |
| org_name = org_domain.split('.')[0].capitalize() if org_domain else None | |
| # Update Brevo contact with enriched data | |
| await create_brevo_contact( | |
| email=email_lower, | |
| first_name=first_name, | |
| last_name=last_name, | |
| organization_name=org_name or (enriched_data.get("organization_name") if enriched_data else None), | |
| phone_number=enriched_data.get("phone_number") if enriched_data else None, | |
| linkedin_url=enriched_data.get("linkedin_url") if enriched_data else None, | |
| title=enriched_data.get("title") if enriched_data else None, | |
| headline=enriched_data.get("headline") if enriched_data else None, | |
| organization_website=enriched_data.get("organization_website") if enriched_data else None, | |
| organization_address=enriched_data.get("organization_address") if enriched_data else None, | |
| list_id=BREVO_TRIAL_LIST_ID | |
| ) | |
| # Create lead in Monday.com | |
| await create_monday_lead( | |
| email=email_lower, | |
| first_name=first_name, | |
| last_name=last_name, | |
| phone_number=enriched_data.get("phone_number") if enriched_data else None, | |
| linkedin_url=enriched_data.get("linkedin_url") if enriched_data else None, | |
| title=enriched_data.get("title") if enriched_data else None, | |
| headline=enriched_data.get("headline") if enriched_data else None, | |
| organization_name=org_name or (enriched_data.get("organization_name") if enriched_data else None), | |
| organization_website=enriched_data.get("organization_website") if enriched_data else None, | |
| organization_address=enriched_data.get("organization_address") if enriched_data else None, | |
| ) | |
| except Exception as e: | |
| # Don't fail user creation if integrations fail | |
| print(f"[WARNING] Failed to enrich/update contact for {email_lower}: {str(e)}") | |
| else: | |
| user.email_verified = True | |
| if user.auth_method != 'otp': | |
| user.auth_method = 'otp' | |
| db.commit() | |
| print(f"[INFO] User verified via OTP: {email_lower}") | |
| # Remove OTP from store after successful verification | |
| del otp_store[email_lower] | |
| return user | |