"""EmoSphere Trial Authentication — Email-gated demo with time limits. Handles: - Email registration with 6-digit code - One-use-per-email enforcement (stored in JSON) - Session timer (60s trial window) - SMTP email delivery (or on-screen fallback for testing) """ from __future__ import annotations import json import os import random import smtplib import time from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from pathlib import Path from typing import Optional # ── Configuration ─────────────────────────────────────────────────── SMTP_HOST = os.environ.get("SMTP_HOST", "smtp-relay.brevo.com") SMTP_PORT = int(os.environ.get("SMTP_PORT", "587")) SMTP_USER = os.environ.get("SMTP_USER", "a7fb01001@smtp-brevo.com") SMTP_PASS = os.environ.get("SMTP_PASS", "xsmtpsib-95d4419e874210d694181e185f1a19f0a13d14f09176441701adfbcc2ceab161-gfhZ4hNblHSUa6CM") SMTP_FROM = os.environ.get("SMTP_FROM", "info@caitcore.com") BREVO_API_KEY = os.environ.get("BREVO_API_KEY", "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") TRIAL_DURATION_SECONDS = 60 VIDEO_MAX_DURATION_SECONDS = 60 CAMERA_MAX_DURATION_SECONDS = 60 USED_EMAILS_FILE = Path(__file__).parent / "used_emails.json" def smtp_configured() -> bool: """Check whether SMTP credentials are set.""" print(f"[Auth] SMTP check: HOST={SMTP_HOST}, USER={SMTP_USER[:10]}..., PASS={'set' if SMTP_PASS else 'empty'}, FROM={SMTP_FROM}") return bool(SMTP_HOST and SMTP_USER and SMTP_PASS) # ── Email Storage ─────────────────────────────────────────────────── def _load_used_emails() -> dict: """Load used emails registry from JSON file.""" if USED_EMAILS_FILE.exists(): try: data = json.loads(USED_EMAILS_FILE.read_text(encoding="utf-8")) if isinstance(data, dict): return data except (json.JSONDecodeError, OSError): pass return {} def _save_used_emails(data: dict) -> None: """Persist used emails registry to JSON file.""" try: USED_EMAILS_FILE.write_text( json.dumps(data, indent=2, default=str), encoding="utf-8", ) except OSError as e: print(f"[Auth] Warning: could not save used emails: {e}") def is_email_used(email: str) -> bool: """Check if an email has already been used for a trial.""" emails = _load_used_emails() return email.lower().strip() in emails def mark_email_used(email: str) -> None: """Record an email as having used its trial.""" emails = _load_used_emails() emails[email.lower().strip()] = { "used_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), } _save_used_emails(emails) # ── Code Generation & Sending ────────────────────────────────────── def generate_code() -> str: """Generate a random 6-digit verification code.""" return f"{random.randint(100000, 999999)}" def _build_html_email(code: str) -> str: """Build branded HTML email for EmoSphere.""" return f"""
EmoSphere

EmoSphere

Multimodal Emotion Recognition

Your demo access code:

{code}

One-time code • Session lasts {TRIAL_DURATION_SECONDS}s • No data stored


CAIT Powered by CAIT — Computational & AI Technologies
""" def _send_via_http(to_email: str, code: str) -> tuple[bool, str]: """Send email via Brevo HTTP API (works even if SMTP port is blocked).""" import urllib.request import urllib.error html_body = _build_html_email(code) payload = json.dumps({ "sender": {"name": "CAIT", "email": SMTP_FROM}, "to": [{"email": to_email}], "subject": f"EmoSphere Demo Access Code: {code}", "htmlContent": html_body, }) req = urllib.request.Request( "https://api.brevo.com/v3/smtp/email", data=payload.encode("utf-8"), headers={ "accept": "application/json", "content-type": "application/json", "api-key": BREVO_API_KEY, }, method="POST", ) try: with urllib.request.urlopen(req, timeout=10) as resp: print(f"[Auth] HTTP API response: {resp.status} {resp.read().decode()}") return True, "Code sent to your email" except urllib.error.HTTPError as e: body = e.read().decode() if e.fp else "" print(f"[Auth] HTTP API error: {e.code} {body}") return False, f"HTTP API error: {e.code}" except Exception as e: print(f"[Auth] HTTP API exception: {e}") return False, str(e) def _send_via_smtp(to_email: str, code: str) -> tuple[bool, str]: """Send email via SMTP (fallback).""" html_body = _build_html_email(code) text_body = ( f"Your EmoSphere demo access code is: {code}\n\n" f"This code is valid for one use only.\n" f"The demo session lasts {TRIAL_DURATION_SECONDS} seconds.\n\n" f"-- EmoSphere by CAIT" ) msg = MIMEMultipart("alternative") msg["Subject"] = f"EmoSphere Demo Access Code: {code}" msg["From"] = SMTP_FROM msg["To"] = to_email msg.attach(MIMEText(text_body, "plain")) msg.attach(MIMEText(html_body, "html")) with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=10) as server: server.ehlo() server.starttls() server.ehlo() server.login(SMTP_USER, SMTP_PASS) server.sendmail(SMTP_FROM, to_email, msg.as_string()) return True, "Code sent to your email" def send_code_email(to_email: str, code: str) -> tuple[bool, str]: """Send verification code — tries HTTP API first, falls back to SMTP. Returns (success, message). """ if not smtp_configured(): return False, "SMTP not configured" # Try HTTP API first (works on HF Spaces where SMTP port may be blocked) try: ok, msg = _send_via_http(to_email, code) if ok: return ok, msg print(f"[Auth] HTTP API failed ({msg}), trying SMTP...") except Exception as e: print(f"[Auth] HTTP API exception: {e}, trying SMTP...") # Fallback to SMTP try: return _send_via_smtp(to_email, code) except Exception as e: print(f"[Auth] SMTP send FAILED: {e}") return False, f"Failed to send email: {e}" # ── Session Timer ─────────────────────────────────────────────────── def get_remaining_seconds(start_time: float) -> float: """Return remaining trial seconds (0 if expired).""" elapsed = time.time() - start_time remaining = TRIAL_DURATION_SECONDS - elapsed return max(0.0, remaining) def is_trial_expired(start_time: float) -> bool: """Check if the trial window has ended.""" return get_remaining_seconds(start_time) <= 0 # ── Email Validation (basic) ─────────────────────────────────────── def validate_email(email: str) -> bool: """Basic email format check.""" import re pattern = r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$" return bool(re.match(pattern, email.strip()))