Merge pull request #2197 from BEDOLAGA-DEV/dev5

Caddy Token auth
This commit is contained in:
Egor
2025-12-22 15:33:36 +03:00
committed by GitHub
7 changed files with 57 additions and 21 deletions

View File

@@ -64,8 +64,9 @@ REDIS_URL=redis://redis:6379/0
REMNAWAVE_API_URL=https://panel.example.com
REMNAWAVE_API_KEY=your_api_key_here
# Тип авторизации: "api_key", "basic_auth"
# Тип авторизации: "api_key", "basic_auth", "caddy"
REMNAWAVE_AUTH_TYPE=api_key
REMNAWAVE_CADDY_TOKEN=YWRtaW46cGFzc3dvcmQ=
# Для панелей с Basic Auth (опционально)
REMNAWAVE_USERNAME=

View File

@@ -61,12 +61,12 @@
### 📚 Поддерживаемые методы авторизации
| Метод | Заголовок | Описание |
|-------|-----------|----------|
| API Key | X-Api-Key: your_api_key | Стандартный API ключ |
| Bearer Token | Authorization: Bearer token | Классический Bearer token |
| Basic Auth | X-Api-Key: Basic base64(user:pass) | Basic Authentication |
| eGames Cookies | Cookies в формате key:value | Для панелей eGames |
| Конфигурация | Authorization | X-Api-Key |
|------------------|---------------------|---------------------|
| Только API Key | Bearer <api_key> | <api_key> |
| Basic Auth | Bearer <api_key> | Basic <user:pass> |
| Caddy + API Key | Basic <caddy_token> | <api_key> |
| Cookies (eGames) | Bearer <api_key> | <api_key> + cookies |
---

View File

@@ -79,7 +79,8 @@ class Settings(BaseSettings):
REMNAWAVE_USERNAME: Optional[str] = None
REMNAWAVE_PASSWORD: Optional[str] = None
REMNAWAVE_AUTH_TYPE: str = "api_key"
REMNAWAVE_CADDY_TOKEN: Optional[str] = None
REMNAWAVE_AUTH_TYPE: str = "api_key" # api_key, basic, bearer, cookies, caddy
REMNAWAVE_USER_DESCRIPTION_TEMPLATE: str = "Bot user: {full_name} {username}"
REMNAWAVE_USER_USERNAME_TEMPLATE: str = "user_{telegram_id}"
REMNAWAVE_USER_DELETE_MODE: str = "delete" # "delete" или "disable"
@@ -575,6 +576,7 @@ class Settings(BaseSettings):
"secret_key": self.REMNAWAVE_SECRET_KEY,
"username": self.REMNAWAVE_USERNAME,
"password": self.REMNAWAVE_PASSWORD,
"caddy_token": self.REMNAWAVE_CADDY_TOKEN,
"auth_type": self.REMNAWAVE_AUTH_TYPE
}

View File

@@ -225,14 +225,24 @@ class RemnaWaveAPIError(Exception):
class RemnaWaveAPI:
def __init__(self, base_url: str, api_key: str, secret_key: Optional[str] = None,
username: Optional[str] = None, password: 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,
caddy_token: Optional[str] = None,
auth_type: str = "api_key",
):
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.secret_key = secret_key
self.username = username
self.password = password
self.caddy_token = caddy_token
self.auth_type = auth_type.lower() if auth_type else "api_key"
self.session: Optional[aiohttp.ClientSession] = None
self.authenticated = False
@@ -264,19 +274,32 @@ class RemnaWaveAPI:
'X-Forwarded-For': '127.0.0.1',
'X-Real-IP': '127.0.0.1'
}
if self.username and self.password:
import base64
# Caddy авторизация — добавляется поверх основной
if self.caddy_token:
# Caddy Security: готовый base64 токен используется как есть
headers['Authorization'] = f'Basic {self.caddy_token}'
logger.debug("Используем Caddy Basic Auth")
# Основная авторизация RemnaWave API
if self.auth_type == "basic" and self.username and self.password:
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 заголовке")
elif self.auth_type == "caddy":
# Для caddy auth_type основная авторизация уже в Authorization header
# Но API ключ всё равно нужен для RemnaWave
if self.api_key:
headers['X-Api-Key'] = self.api_key
logger.debug("Используем API ключ для RemnaWave + Caddy авторизацию")
else:
# api_key или bearer — стандартный режим
headers['X-Api-Key'] = self.api_key
if not self.caddy_token:
headers['Authorization'] = f'Bearer {self.api_key}'
logger.debug("Используем API ключ в X-Api-Key заголовке")
headers['Authorization'] = f'Bearer {self.api_key}'
return headers
async def __aenter__(self):

View File

@@ -293,6 +293,8 @@ class MaintenanceService:
secret_key = (auth_params.get("secret_key") or "").strip() or None
username = (auth_params.get("username") or "").strip() or None
password = (auth_params.get("password") or "").strip() or None
caddy_token = (auth_params.get("caddy_token") or "").strip() or None
auth_type = (auth_params.get("auth_type") or "api_key").strip()
if not base_url:
logger.error("REMNAWAVE_API_URL не настроен, пропускаем проверку API")
@@ -311,7 +313,9 @@ class MaintenanceService:
api_key=api_key,
secret_key=secret_key,
username=username,
password=password
password=password,
caddy_token=caddy_token,
auth_type=auth_type,
)
attempts = settings.get_maintenance_retry_attempts()

View File

@@ -165,7 +165,9 @@ class RemnaWaveService:
api_key=api_key,
secret_key=auth_params.get("secret_key"),
username=auth_params.get("username"),
password=auth_params.get("password")
password=auth_params.get("password"),
caddy_token=auth_params.get("caddy_token"),
auth_type=auth_params.get("auth_type") or "api_key",
)
@property

View File

@@ -94,7 +94,8 @@ class SubscriptionService:
secret_key = (auth_params.get("secret_key") or "").strip() or None
username = (auth_params.get("username") or "").strip() or None
password = (auth_params.get("password") or "").strip() or None
auth_type = (auth_params.get("auth_type") or "").strip() or None
caddy_token = (auth_params.get("caddy_token") or "").strip() or None
auth_type = (auth_params.get("auth_type") or "api_key").strip()
config_signature = (
base_url,
@@ -102,7 +103,8 @@ class SubscriptionService:
secret_key or "",
username or "",
password or "",
auth_type or "",
caddy_token or "",
auth_type,
)
if config_signature == self._last_config_signature:
@@ -122,6 +124,8 @@ class SubscriptionService:
secret_key=secret_key,
username=username,
password=password,
caddy_token=caddy_token,
auth_type=auth_type,
)
if self._config_error: