Merge pull request #65 from Fr1ngg/dev

X-Api-key заголовки + TinyAuth
This commit is contained in:
Egor
2025-09-13 07:55:55 +03:00
committed by GitHub
6 changed files with 83 additions and 27 deletions

View File

@@ -34,9 +34,17 @@ REDIS_URL=redis://redis:6379/0
# ===== REMNAWAVE API =====
REMNAWAVE_API_URL=https://panel.example.com
REMNAWAVE_API_KEY=
# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD - https://panel.example.com/auth/login?XXXXXXX=DDDDDDDD
REMNAWAVE_SECRET_KEY=your_secret_key_here
REMNAWAVE_API_KEY=your_api_key_here
# Тип авторизации: "api_key", "basic_auth"
REMNAWAVE_AUTH_TYPE=api_key
# Для панелей с Basic Auth (опционально)
REMNAWAVE_USERNAME=
REMNAWAVE_PASSWORD=
# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD
REMNAWAVE_SECRET_KEY=
# ========= ПОДПИСКИ =========
# ===== ТРИАЛ ПОДПИСКА =====

View File

@@ -32,6 +32,10 @@ class Settings(BaseSettings):
REMNAWAVE_API_URL: str
REMNAWAVE_API_KEY: str
REMNAWAVE_SECRET_KEY: Optional[str] = None
REMNAWAVE_USERNAME: Optional[str] = None
REMNAWAVE_PASSWORD: Optional[str] = None
REMNAWAVE_AUTH_TYPE: str = "api_key"
TRIAL_DURATION_DAYS: int = 3
TRIAL_TRAFFIC_LIMIT_GB: int = 10
@@ -238,6 +242,16 @@ class Settings(BaseSettings):
except (ValueError, AttributeError):
return []
def get_remnawave_auth_params(self) -> Dict[str, Optional[str]]:
return {
"base_url": self.REMNAWAVE_API_URL,
"api_key": self.REMNAWAVE_API_KEY,
"secret_key": self.REMNAWAVE_SECRET_KEY,
"username": self.REMNAWAVE_USERNAME,
"password": self.REMNAWAVE_PASSWORD,
"auth_type": self.REMNAWAVE_AUTH_TYPE
}
def get_autopay_warning_days(self) -> List[int]:
try:

View File

@@ -1,6 +1,7 @@
import asyncio
import json
import ssl
import base64
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Union, Any
from urllib.parse import urlparse
@@ -103,12 +104,15 @@ class RemnaWaveAPIError(Exception):
class RemnaWaveAPI:
def __init__(self, base_url: str, api_key: str, secret_key: Optional[str] = None):
def __init__(self, base_url: str, api_key: str, secret_key: Optional[str] = None,
username: Optional[str] = None, password: Optional[str] = None):
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.secret_key = secret_key
self.secret_key = secret_key
self.username = username
self.password = password
self.session: Optional[aiohttp.ClientSession] = None
self.authenticated = False
self.authenticated = False
def _detect_connection_type(self) -> str:
parsed = urlparse(self.base_url)
@@ -129,14 +133,9 @@ class RemnaWaveAPI:
return "local"
return "external"
async def __aenter__(self):
conn_type = self._detect_connection_type()
logger.info(f"Подключение к Remnawave: {self.base_url} (тип: {conn_type})")
def _prepare_auth_headers(self) -> Dict[str, str]:
headers = {
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Forwarded-Proto': 'https',
@@ -144,6 +143,27 @@ class RemnaWaveAPI:
'X-Real-IP': '127.0.0.1'
}
if self.username and self.password:
import base64
credentials = f"{self.username}:{self.password}"
encoded_credentials = base64.b64encode(credentials.encode()).decode()
headers['X-Api-Key'] = f"Basic {encoded_credentials}"
logger.debug("Используем Basic Auth в X-Api-Key заголовке")
else:
headers['X-Api-Key'] = self.api_key
logger.debug("Используем API ключ в X-Api-Key заголовке")
headers['Authorization'] = f'Bearer {self.api_key}'
return headers
async def __aenter__(self):
conn_type = self._detect_connection_type()
logger.info(f"Подключение к Remnawave: {self.base_url} (тип: {conn_type})")
headers = self._prepare_auth_headers()
cookies = None
if self.secret_key:
if ':' in self.secret_key:

View File

@@ -264,7 +264,14 @@ class MaintenanceService:
self._is_checking = True
self._status.last_check = datetime.utcnow()
api = RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY)
auth_params = settings.get_remnawave_auth_params()
api = RemnaWaveAPI(
base_url=auth_params["base_url"],
api_key=auth_params["api_key"],
secret_key=auth_params["secret_key"],
username=auth_params["username"],
password=auth_params["password"]
)
async with api:
is_connected = await test_api_connection(api)

View File

@@ -23,10 +23,13 @@ logger = logging.getLogger(__name__)
class RemnaWaveService:
def __init__(self):
auth_params = settings.get_remnawave_auth_params()
self.api = RemnaWaveAPI(
base_url=settings.REMNAWAVE_API_URL,
api_key=settings.REMNAWAVE_API_KEY,
secret_key=settings.REMNAWAVE_SECRET_KEY
base_url=auth_params["base_url"],
api_key=auth_params["api_key"],
secret_key=auth_params["secret_key"],
username=auth_params["username"],
password=auth_params["password"]
)
def _parse_remnawave_date(self, date_str: str) -> datetime:
@@ -351,7 +354,7 @@ class RemnaWaveService:
async def update_squad_inbounds(self, squad_uuid: str, inbound_uuids: List[str]) -> bool:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
data = {
'uuid': squad_uuid,
'inbounds': inbound_uuids
@@ -873,7 +876,7 @@ class RemnaWaveService:
async def get_squad_details(self, squad_uuid: str) -> Optional[Dict]:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
squad = await api.get_internal_squad_by_uuid(squad_uuid)
if squad:
return {
@@ -890,7 +893,7 @@ class RemnaWaveService:
async def add_all_users_to_squad(self, squad_uuid: str) -> bool:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
response = await api._make_request('POST', f'/api/internal-squads/{squad_uuid}/bulk-actions/add-users')
return response.get('response', {}).get('eventSent', False)
except Exception as e:
@@ -899,7 +902,7 @@ class RemnaWaveService:
async def remove_all_users_from_squad(self, squad_uuid: str) -> bool:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
response = await api._make_request('DELETE', f'/api/internal-squads/{squad_uuid}/bulk-actions/remove-users')
return response.get('response', {}).get('eventSent', False)
except Exception as e:
@@ -908,7 +911,7 @@ class RemnaWaveService:
async def delete_squad(self, squad_uuid: str) -> bool:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
response = await api.delete_internal_squad(squad_uuid)
return response
except Exception as e:
@@ -917,7 +920,7 @@ class RemnaWaveService:
async def get_all_inbounds(self) -> List[Dict]:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
response = await api._make_request('GET', '/api/config-profiles/inbounds')
inbounds_data = response.get('response', {}).get('inbounds', [])
@@ -938,7 +941,7 @@ class RemnaWaveService:
async def rename_squad(self, squad_uuid: str, new_name: str) -> bool:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
data = {
'uuid': squad_uuid,
'name': new_name
@@ -951,7 +954,7 @@ class RemnaWaveService:
async def create_squad(self, name: str, inbound_uuids: List[str]) -> bool:
try:
async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api:
async with self.api as api:
squad = await api.create_internal_squad(name, inbound_uuids)
return squad is not None
except Exception as e:

View File

@@ -23,9 +23,13 @@ logger = logging.getLogger(__name__)
class SubscriptionService:
def __init__(self):
auth_params = settings.get_remnawave_auth_params()
self.api = RemnaWaveAPI(
base_url=settings.REMNAWAVE_API_URL,
api_key=settings.REMNAWAVE_API_KEY
base_url=auth_params["base_url"],
api_key=auth_params["api_key"],
secret_key=auth_params["secret_key"],
username=auth_params["username"],
password=auth_params["password"]
)
async def create_remnawave_user(