""" MAX Mini App (WebApp) auth endpoint. /api/v1/max/auth: - Принимает init_data от MAX Bridge (window.WebApp.initData) - Валидирует init_data и извлекает данные пользователя MAX - Проксирует max_user_id в n8n для получения unified_id/контакта - Создаёт сессию в Redis (аналогично Telegram — без SMS) """ import logging from typing import Optional from fastapi import APIRouter, HTTPException from fastapi.encoders import jsonable_encoder from pydantic import BaseModel from ..services.max_auth import extract_max_user, MaxAuthError from ..config import settings from . import n8n_proxy from . import session as session_api logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/max", tags=["MAX"]) class MaxAuthRequest(BaseModel): init_data: str session_token: Optional[str] = None phone: Optional[str] = None class MaxAuthResponse(BaseModel): success: bool session_token: Optional[str] = None unified_id: Optional[str] = None contact_id: Optional[str] = None phone: Optional[str] = None has_drafts: Optional[bool] = None need_contact: Optional[bool] = None def _generate_session_token() -> str: import uuid return f"sess-{uuid.uuid4()}" @router.post("/auth", response_model=MaxAuthResponse) async def max_auth(request: MaxAuthRequest): """ Авторизация пользователя через MAX WebApp (Mini App). """ init_data = request.init_data or "" phone = (request.phone or "").strip() logger.info( "[MAX] POST /api/v1/max/auth: init_data длина=%s, phone=%s, session_token=%s", len(init_data), bool(phone), bool(request.session_token), ) if not init_data: logger.warning("[MAX] init_data пустой") raise HTTPException(status_code=400, detail="init_data обязателен") bot_configured = bool((getattr(settings, "max_bot_token", None) or "").strip()) webhook_configured = bool((getattr(settings, "n8n_max_auth_webhook", None) or "").strip()) logger.info("[MAX] Конфиг: MAX_BOT_TOKEN=%s, N8N_MAX_AUTH_WEBHOOK=%s", bot_configured, webhook_configured) try: max_user = extract_max_user(request.init_data) except MaxAuthError as e: logger.warning("[MAX] Ошибка валидации initData: %s", e) raise HTTPException(status_code=400, detail=str(e)) max_user_id = max_user["max_user_id"] logger.info("[MAX] MAX user валиден: id=%s, username=%s", max_user_id, max_user.get("username")) session_token = request.session_token or _generate_session_token() n8n_payload = { "max_user_id": max_user_id, "username": max_user.get("username"), "first_name": max_user.get("first_name"), "last_name": max_user.get("last_name"), "session_token": session_token, "form_id": "ticket_form", "init_data": request.init_data, } if phone: n8n_payload["phone"] = phone logger.info("[MAX] Валидация OK → вызов n8n webhook (max_user_id=%s)", max_user_id) try: class _DummyRequest: def __init__(self, payload: dict): self._payload = payload async def json(self): return self._payload n8n_response = await n8n_proxy.proxy_max_auth(_DummyRequest(n8n_payload)) # type: ignore[arg-type] n8n_data = jsonable_encoder(n8n_response) except HTTPException: raise except Exception as e: logger.exception("[MAX] Ошибка вызова n8n MAX auth webhook: %s", e) raise HTTPException(status_code=500, detail=f"Ошибка обращения к n8n: {str(e)}") logger.info("[MAX] n8n ответ (ключи): %s", list(n8n_data.keys()) if isinstance(n8n_data, dict) else type(n8n_data).__name__) _result = n8n_data.get("result") _result_dict = _result if isinstance(_result, dict) else {} _raw = ( n8n_data.get("need_contact") or _result_dict.get("need_contact") or n8n_data.get("needContact") or _result_dict.get("needContact") ) need_contact = _raw is True or _raw == 1 or (isinstance(_raw, str) and str(_raw).strip().lower() in ("true", "1")) if need_contact: logger.info("[MAX] n8n: need_contact=true — возвращаем need_contact, фронт закроет приложение") return MaxAuthResponse(success=False, need_contact=True) unified_id = n8n_data.get("unified_id") or _result_dict.get("unified_id") or n8n_data.get("unifiedId") contact_id = n8n_data.get("contact_id") or _result_dict.get("contact_id") or n8n_data.get("contactId") phone_res = n8n_data.get("phone") or _result_dict.get("phone") has_drafts = n8n_data.get("has_drafts") or _result_dict.get("has_drafts") if not unified_id: logger.info("[MAX] n8n не вернул unified_id (юзер не в базе) — возвращаем need_contact=true. Ответ: %s", n8n_data) return MaxAuthResponse(success=False, need_contact=True) session_request = session_api.SessionCreateRequest( session_token=session_token, unified_id=unified_id, phone=phone_res or phone or "", contact_id=contact_id or "", ttl_hours=24, chat_id=str(max_user_id) if max_user_id else None, ) try: await session_api.create_session(session_request) except HTTPException: raise except Exception as e: logger.exception("[MAX] Ошибка создания сессии в Redis") raise HTTPException(status_code=500, detail=f"Ошибка создания сессии: {str(e)}") return MaxAuthResponse( success=True, session_token=session_token, unified_id=unified_id, contact_id=contact_id, phone=phone_res or phone, has_drafts=has_drafts, )