Files
remnawave-minishop/backend/bot/app/web/webapp/_runtime.py
T
2026-05-19 15:21:02 +03:00

114 lines
4.2 KiB
Python

# ruff: noqa: F401,F403,F405,I001
import asyncio
import base64
import hashlib
import html
import hmac
import io
import ipaddress
import json
import logging
import os
import re
import secrets
import socket
import subprocess
import time
from collections import deque
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import parse_qsl, quote, urlencode, urlsplit, urlunsplit
from aiogram import Bot, Dispatcher
from aiohttp import ClientSession, ClientTimeout, web
from pydantic import BaseModel, ConfigDict, EmailStr, ValidationError, constr, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import sessionmaker
from bot.app.web.admin_api import (
admin_auth_middleware,
setup_admin_routes,
)
from bot.app.web.webapp_auth import (
create_signed_telegram_oauth_state,
create_telegram_oauth_nonce,
create_webapp_session_token,
validate_telegram_login_widget_data,
validate_telegram_oauth_id_token,
validate_telegram_webapp_init_data,
verify_signed_telegram_oauth_state,
verify_telegram_oauth_nonce,
verify_webapp_session_token,
)
from bot.infra.redis import cache_delete, cache_get_json, cache_set_json, get_redis, redis_key
from bot.services.email_auth_service import EmailAuthService, normalize_email
from bot.services.email_templates import render_account_merged
from bot.services.promo_code_service import PromoCodeService
from bot.services.referral_service import ReferralService
from bot.services.subscription_service import SubscriptionService
from bot.utils.config_link import prepare_config_links
from bot.utils.request_security import parse_ip_entries, request_client_ip
from bot.utils.text_sanitizer import sanitize_display_name, sanitize_username
from config.settings import Settings
from db.dal import payment_dal, security_dal, subscription_dal, user_dal
from db.dal.user_dal import UserMergeConflictError
from db.models import Payment, User, UserTelegramAvatar
logger = logging.getLogger(__name__)
TEMPLATE_PATH = Path(__file__).resolve().parents[1] / "templates" / "subscription_webapp.html"
ASSET_DIR = TEMPLATE_PATH.parent
APP_ROOT = Path(__file__).resolve().parents[5]
WEBAPP_LOGO_PROXY_PATH = "/webapp-logo"
WEBAPP_LOGO_CACHE_DIR = APP_ROOT / "data" / "webapp-logo"
WEBAPP_UPLOADED_LOGO_DIR = WEBAPP_LOGO_CACHE_DIR / "uploads"
WEBAPP_UPLOADED_LOGO_PATH = "/webapp-uploaded-logo"
WEBAPP_FAVICON_DIR = WEBAPP_LOGO_CACHE_DIR / "favicons"
WEBAPP_FAVICON_PATH = "/webapp-favicon"
WEBAPP_EMOJI_CACHE_DIR = APP_ROOT / "data" / "webapp-emoji"
WEBAPP_CONFIG_PLACEHOLDER = "<!-- WEBAPP_CONFIG_SCRIPT -->"
WEBAPP_I18N_PLACEHOLDER = "<!-- WEBAPP_I18N_SCRIPT -->"
WEBAPP_JS_PLACEHOLDER = "<!-- WEBAPP_JS_SCRIPT -->"
APP_REPOSITORY_URL = "https://github.com/3252a8/remnawave-minishop"
DEV_MOCK_START_MARKER = "<!-- WEBAPP_DEV_MOCK_START -->"
DEV_MOCK_END_MARKER = "<!-- WEBAPP_DEV_MOCK_END -->"
WEBAPP_RATE_LIMIT_WINDOW_SECONDS = 60
WEBAPP_RATE_LIMIT_MAX_REQUESTS = 30
WEBAPP_LOGO_MAX_BYTES = 2 * 1024 * 1024
WEBAPP_EMOJI_MAX_BYTES = 4 * 1024 * 1024
WEBAPP_THEME_CSS_MAX_BYTES = 512 * 1024
WEBAPP_THEME_ASSET_MAX_BYTES = 1024 * 1024
WEBAPP_THEME_ASSET_CONTENT_TYPES = {
".gif": "image/gif",
".ico": "image/x-icon",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".svg": "image/svg+xml",
".webp": "image/webp",
}
WEBAPP_TELEGRAM_AVATAR_MAX_BYTES = 128 * 1024
WEBAPP_TELEGRAM_AVATAR_REFRESH_SECONDS = 24 * 60 * 60
WEBAPP_TELEGRAM_AVATAR_FETCH_TIMEOUT_SECONDS = 4
WEBAPP_SESSION_COOKIE_NAME = "rw_webapp_session"
WEBAPP_CSRF_COOKIE_NAME = "rw_webapp_csrf"
WEBAPP_TELEGRAM_OAUTH_STATE_COOKIE_NAME = "rw_tg_oauth_state"
WEBAPP_CSRF_HEADER_NAME = "X-CSRF-Token"
WEBAPP_STATE_CHANGING_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
_APP_VERSION_CACHE: Optional[str] = None
WEBAPP_CSRF_EXEMPT_PATHS = {
"/api/auth/telegram/nonce",
"/api/auth/token",
"/api/auth/email/request",
"/api/auth/email/verify",
"/api/auth/email/magic",
"/api/auth/email/password",
"/api/auth/logout",
}
_SHARED_HTTP_SESSION: Optional[ClientSession] = None
_SHARED_HTTP_SESSION_LOCK = asyncio.Lock()
__all__ = [name for name in globals() if not name.startswith("__")]