From e6976b52622e6a9095d75debb15033a273eff0a6 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 28 Sep 2025 06:24:13 +0300 Subject: [PATCH] Handle bootstrap API token fallback --- app/services/web_api_token_service.py | 68 +++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/app/services/web_api_token_service.py b/app/services/web_api_token_service.py index dd6d83f3..3f2513e7 100644 --- a/app/services/web_api_token_service.py +++ b/app/services/web_api_token_service.py @@ -1,6 +1,7 @@ from __future__ import annotations from datetime import datetime +from secrets import compare_digest from typing import Optional, Tuple from sqlalchemy.ext.asyncio import AsyncSession @@ -27,21 +28,82 @@ class WebApiTokenService: *, remote_ip: Optional[str] = None, ) -> Optional[WebApiToken]: - token_hash = self.hash_token(token_value) + normalized_value = token_value.strip() + token_hash = self.hash_token(normalized_value) token = await crud.get_token_by_hash(db, token_hash) + token = await self._ensure_bootstrap_token_if_needed( + db, + token, + normalized_value, + token_hash, + ) + if not token or not token.is_active: return None - if token.expires_at and token.expires_at < datetime.utcnow(): + now = datetime.utcnow() + if token.expires_at and token.expires_at < now: return None - token.last_used_at = datetime.utcnow() + token.last_used_at = now if remote_ip: token.last_used_ip = remote_ip await db.flush() return token + async def _ensure_bootstrap_token_if_needed( + self, + db: AsyncSession, + token: Optional[WebApiToken], + provided_value: str, + token_hash: str, + ) -> Optional[WebApiToken]: + """Гарантирует работу бутстрап-токена из настроек даже без миграции.""" + + if token and token.is_active: + return token + + default_token = (settings.WEB_API_DEFAULT_TOKEN or "").strip() + if not default_token: + return token + + if not compare_digest(default_token, provided_value): + return token + + token_name = (settings.WEB_API_DEFAULT_TOKEN_NAME or "Bootstrap Token").strip() or "Bootstrap Token" + now = datetime.utcnow() + + if token: + updated = False + + if not token.is_active: + token.is_active = True + updated = True + + if token.name != token_name: + token.name = token_name + updated = True + + if updated: + token.updated_at = now + await db.flush() + + return token + + token = await crud.create_token( + db, + name=token_name, + token_hash=token_hash, + token_prefix=default_token[:12], + description="Автоматически создан при авторизации бутстрап-токеном", + created_by="bootstrap", + ) + + token.created_at = token.created_at or now + token.updated_at = now + return token + async def create_token( self, db: AsyncSession,