video_monitor/app/utils/Credentials.py

79 lines
2.4 KiB
Python
Raw Normal View History

2026-08-30 22:22:11 +08:00
"""Encryption and presentation helpers for credentials stored in the database."""
import re
from cryptography.fernet import Fernet, InvalidToken
from app.utils.Secrets import get_runtime_secret
PREFIX = "enc:v1:"
SENSITIVE_KEYS = re.compile(
r"(?:api[_-]?key|password|passwd|secret|token|authorization|credential)", re.I
)
def _fernet():
return Fernet(get_runtime_secret("credential_encryption_key").encode("ascii"))
def is_encrypted(value):
return isinstance(value, str) and value.startswith(PREFIX)
def encrypt_credential(value):
value = "" if value is None else str(value)
if not value or is_encrypted(value):
return value
return PREFIX + _fernet().encrypt(value.encode("utf-8")).decode("ascii")
def decrypt_credential(value):
value = "" if value is None else str(value)
if not is_encrypted(value):
return value
try:
return _fernet().decrypt(value[len(PREFIX):].encode("ascii")).decode("utf-8")
except (InvalidToken, ValueError) as exc:
raise RuntimeError("credential decryption failed; check the configured encryption key") from exc
def mask_credential(value):
plain = decrypt_credential(value)
if not plain:
return ""
if len(plain) <= 4:
return "****"
return plain[:2] + "****" + plain[-2:]
def redact_mapping(value):
if not isinstance(value, dict):
return value
return {key: ("[REDACTED]" if SENSITIVE_KEYS.search(str(key)) else val)
for key, val in value.items()}
def migrate_existing_credentials():
"""Idempotently encrypt legacy plaintext credentials in existing rows."""
from app.models import LLMModel, StreamModel
changed = 0
for item in LLMModel.objects.only("id", "api_key").iterator():
encrypted = encrypt_credential(item.api_key)
if encrypted != item.api_key:
LLMModel.objects.filter(pk=item.pk).update(api_key=encrypted)
changed += 1
for item in StreamModel.objects.only(
"id", "pull_stream_username", "pull_stream_password"
).iterator():
updates = {}
for field in ("pull_stream_username", "pull_stream_password"):
old_value = getattr(item, field)
new_value = encrypt_credential(old_value)
if new_value != old_value:
updates[field] = new_value
if updates:
StreamModel.objects.filter(pk=item.pk).update(**updates)
changed += 1
return changed