Spaces:
Running
Running
| from __future__ import annotations | |
| import base64 | |
| import hashlib | |
| import json | |
| from typing import Any | |
| from app.social.domain.errors import SocialProviderUnavailableError | |
| class TokenCipher: | |
| """Narrow encryption boundary for non-Vault local development. | |
| Imports cryptography lazily so provider discovery and existing media APIs | |
| still start when social token storage is unused. Production should use | |
| Supabase Vault references instead of this encrypted database fallback. | |
| """ | |
| def __init__(self, key: str | None) -> None: | |
| self._key = key | |
| def _fernet(self): | |
| if not self._key: | |
| raise SocialProviderUnavailableError( | |
| "SOCIAL_OAUTH_ENCRYPTION_KEY is required when Supabase Vault is disabled." | |
| ) | |
| try: | |
| from cryptography.fernet import Fernet | |
| except ImportError as exc: | |
| raise SocialProviderUnavailableError("The cryptography package is required for token storage.") from exc | |
| digest = hashlib.sha256(self._key.encode("utf-8")).digest() | |
| return Fernet(base64.urlsafe_b64encode(digest)) | |
| def encrypt(self, value: dict[str, Any]) -> str: | |
| return self._fernet().encrypt(json.dumps(value, separators=(",", ":")).encode()).decode() | |
| def decrypt(self, value: str) -> dict[str, Any]: | |
| result = json.loads(self._fernet().decrypt(value.encode()).decode()) | |
| return result if isinstance(result, dict) else {} | |