"""Token encryption helpers for SocialAccount / SocialAppCredentials.""" from cryptography.fernet import Fernet, InvalidToken from django.conf import settings # Copied from .env.prod.example / deploy templates — not valid Fernet material. _PLACEHOLDER_KEYS = frozenset( { "replace-with-fernet-key", "changeme", "change-me", "todo", } ) class SocialCryptoError(ValueError): """Raised when SOCIAL_TOKEN_ENCRYPTION_KEY is missing or not a Fernet key.""" def _normalize_key(raw: str | bytes | None) -> bytes | None: if raw is None: return None if isinstance(raw, bytes): text = raw.decode("utf-8", errors="ignore").strip() else: text = str(raw).strip() if not text or text.lower() in _PLACEHOLDER_KEYS: return None return text.encode() def _fernet() -> Fernet: key = _normalize_key(settings.SOCIAL_TOKEN_ENCRYPTION_KEY) if key is None: if settings.DEBUG: # Dev-only unstable fallback — set SOCIAL_TOKEN_ENCRYPTION_KEY for real use. return Fernet(Fernet.generate_key()) raise SocialCryptoError( "SOCIAL_TOKEN_ENCRYPTION_KEY is not set. Generate one with: " 'python -c "from cryptography.fernet import Fernet; ' 'print(Fernet.generate_key().decode())" ' "and add it to the beta/prod app env, then redeploy/restart." ) try: return Fernet(key) except (ValueError, TypeError) as exc: raise SocialCryptoError( "SOCIAL_TOKEN_ENCRYPTION_KEY is invalid (must be 32 url-safe " "base64-encoded bytes from Fernet.generate_key()). " "Replace the placeholder in the beta/prod env and restart." ) from exc def encrypt_tokens(plaintext: str) -> str: return _fernet().encrypt(plaintext.encode()).decode() def decrypt_tokens(ciphertext: str) -> str: if not ciphertext: return "" try: return _fernet().decrypt(ciphertext.encode()).decode() except InvalidToken as exc: raise ValueError("Unable to decrypt social tokens") from exc except SocialCryptoError: raise