1c0d7e9eb6
So str(exc) passthrough on scripts, batch, sync, and schedule routes returns Chinese without relying only on the HTTP detail translation layer. Co-authored-by: Cursor <cursoragent@cursor.com>
54 lines
1.6 KiB
Python
54 lines
1.6 KiB
Python
"""Nexus — Encryption Utilities (Fernet)
|
|
Shared crypto module — used by SSH pool, DB credentials, password presets.
|
|
"""
|
|
|
|
import logging
|
|
from cryptography.fernet import Fernet
|
|
|
|
from server.config import settings
|
|
|
|
logger = logging.getLogger("nexus.crypto")
|
|
|
|
|
|
def _fernet() -> Fernet:
|
|
"""Create Fernet instance from ENCRYPTION_KEY.
|
|
|
|
ENCRYPTION_KEY is mandatory — main.py validates it at startup.
|
|
No silent fallback: if ENCRYPTION_KEY is empty, that's a configuration
|
|
error that must be surfaced, not masked.
|
|
"""
|
|
key = settings.ENCRYPTION_KEY
|
|
if not key:
|
|
raise RuntimeError(
|
|
"ENCRYPTION_KEY is empty — cannot initialize Fernet encryption. "
|
|
"Set NEXUS_ENCRYPTION_KEY in .env (generated by install wizard)."
|
|
)
|
|
return Fernet(key.encode() if isinstance(key, str) else key)
|
|
|
|
|
|
_fernet_instance: Fernet | None = None
|
|
|
|
|
|
def get_fernet() -> Fernet:
|
|
global _fernet_instance
|
|
if _fernet_instance is None:
|
|
_fernet_instance = _fernet()
|
|
return _fernet_instance
|
|
|
|
|
|
def encrypt_value(plaintext: str) -> str:
|
|
"""Encrypt a string using Fernet"""
|
|
if not plaintext:
|
|
return plaintext
|
|
return get_fernet().encrypt(plaintext.encode()).decode()
|
|
|
|
|
|
def decrypt_value(ciphertext: str) -> str:
|
|
"""Decrypt a Fernet-encrypted string"""
|
|
if not ciphertext:
|
|
return ciphertext
|
|
try:
|
|
return get_fernet().decrypt(ciphertext.encode()).decode()
|
|
except Exception as e:
|
|
logger.warning(f"Fernet decryption failed (key mismatch?): {e}")
|
|
raise ValueError("凭据解密失败") from e |