80 lines
2.3 KiB
Python
80 lines
2.3 KiB
Python
import logging
|
|
import random
|
|
import time
|
|
|
|
import httpx
|
|
|
|
from app.config import settings
|
|
from app.utils.redis import get_redis
|
|
|
|
logger = logging.getLogger("videogen")
|
|
|
|
# In-memory fallback for verification codes
|
|
_sms_code_store: dict[str, tuple[str, float]] = {}
|
|
|
|
|
|
def _generate_code(length: int = 6) -> str:
|
|
return "".join(random.choices("0123456789", k=length))
|
|
|
|
|
|
async def send_sms(phone: str, code: str) -> bool:
|
|
"""Send SMS verification code. Supports mock mode and real HTTP gateway."""
|
|
if settings.SMS_MOCK or not settings.SMS_API_URL:
|
|
logger.info(f"[SMS MOCK] To={phone}, Code={code}")
|
|
return True
|
|
|
|
try:
|
|
payload = {
|
|
"phone": phone,
|
|
"code": code,
|
|
"sign_name": settings.SMS_SIGN_NAME,
|
|
"template_code": settings.SMS_TEMPLATE_CODE,
|
|
}
|
|
headers = {
|
|
"Authorization": f"Bearer {settings.SMS_API_KEY}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
resp = await client.post(settings.SMS_API_URL, json=payload, headers=headers)
|
|
resp.raise_for_status()
|
|
return True
|
|
except Exception:
|
|
logger.exception(f"SMS send failed for {phone}")
|
|
return False
|
|
|
|
|
|
async def store_sms_code(phone: str, code: str, ttl: int = 300) -> None:
|
|
"""Store SMS verification code with TTL (default 5 minutes)."""
|
|
redis = get_redis()
|
|
if redis:
|
|
await redis.setex(f"sms_code:{phone}", ttl, code)
|
|
else:
|
|
_sms_code_store[phone] = (code, time.time() + ttl)
|
|
|
|
|
|
async def generate_and_send_sms(phone: str) -> bool:
|
|
"""Generate a code, store it, and send it via SMS."""
|
|
code = _generate_code()
|
|
ok = await send_sms(phone, code)
|
|
if ok:
|
|
await store_sms_code(phone, code)
|
|
return ok
|
|
|
|
|
|
async def verify_sms_code(phone: str, code: str) -> bool:
|
|
"""Verify an SMS verification code."""
|
|
redis = get_redis()
|
|
if redis:
|
|
stored = await redis.get(f"sms_code:{phone}")
|
|
if stored and stored == code:
|
|
await redis.delete(f"sms_code:{phone}")
|
|
return True
|
|
return False
|
|
else:
|
|
entry = _sms_code_store.pop(phone, None)
|
|
if entry:
|
|
stored_code, expires = entry
|
|
if time.time() < expires and stored_code == code:
|
|
return True
|
|
return False
|