from __future__ import annotations from cryptography.fernet import Fernet, InvalidToken from sqlalchemy import select from app.core.config import settings from app.db.base import SessionLocal from app.db.models.opencloud_token import OpenCloudToken class OpenCloudTokenStoreError(RuntimeError): """Ошибка при работе с таблицей токенов OpenCloud (шифрование/БД).""" def _get_fernet() -> Fernet: if not settings.FERNET_KEY: raise OpenCloudTokenStoreError( "Не задан OPENBAO__SETTINGS__FERNET_KEY — шифрование токенов " "OpenCloud недоступно." ) try: return Fernet(settings.FERNET_KEY.encode()) except (ValueError, TypeError) as exc: raise OpenCloudTokenStoreError( f"OPENBAO__SETTINGS__FERNET_KEY некорректен: {exc}" ) from exc async def add_token(username: str, token: str, added_by: str | None = None) -> None: """Сохраняет (или перезаписывает) App Token заказчика, зашифровав его.""" if not username: raise OpenCloudTokenStoreError("Не задан username для сохранения токена.") if not token: raise OpenCloudTokenStoreError("Не задан токен для сохранения.") encrypted = _get_fernet().encrypt(token.encode()).decode() async with SessionLocal() as session: obj = await session.get(OpenCloudToken, username) if obj is not None: obj.encrypted_token = encrypted if added_by: obj.added_by = added_by else: session.add( OpenCloudToken( username=username, encrypted_token=encrypted, added_by=added_by ) ) await session.commit() async def get_token(username: str) -> str | None: """username -> расшифрованный App Token, либо None, если записи нет.""" if not username: return None async with SessionLocal() as session: obj = await session.get(OpenCloudToken, username) if obj is None: return None try: return _get_fernet().decrypt(obj.encrypted_token.encode()).decode() except InvalidToken as exc: raise OpenCloudTokenStoreError( f"Не удалось расшифровать токен для '{username}' — " "проверьте OPENBAO__SETTINGS__FERNET_KEY." ) from exc async def remove_token(username: str) -> bool: """Удаляет запись пользователя. Возвращает True, если запись была найдена.""" async with SessionLocal() as session: obj = await session.get(OpenCloudToken, username) if obj is None: return False await session.delete(obj) await session.commit() return True async def list_usernames() -> list[str]: """Список username, для которых сохранён токен (без расшифровки).""" async with SessionLocal() as session: result = await session.execute(select(OpenCloudToken.username)) return list(result.scalars().all())