Files
TimeTracker/app/utils/secret_crypto.py
T
Dries Peeters 786d88bdba style: apply black 24.8.0 and isort across app/
Pure formatting pass to satisfy ``./scripts/run-ci-local.sh code-quality``:
no behavioural changes, just consistent line wrapping, import ordering,
and trailing-newline normalization across routes, models, services, and
utility modules.
2026-05-13 10:31:39 +02:00

80 lines
2.1 KiB
Python

from __future__ import annotations
import base64
import os
from typing import Optional
_FERNET = None
def _load_key_from_env() -> str:
"""
Return a base64 urlsafe-encoded 32-byte key suitable for cryptography.fernet.Fernet.
Supported env vars:
- SETTINGS_ENCRYPTION_KEY: full key string
- SETTINGS_ENCRYPTION_KEY_FILE: file containing the key on first line
"""
key = (os.getenv("SETTINGS_ENCRYPTION_KEY") or "").strip()
if not key:
key_file = (os.getenv("SETTINGS_ENCRYPTION_KEY_FILE") or "").strip()
if key_file:
try:
with open(key_file, "r", encoding="utf-8") as f:
key = (f.read().strip().split("\n")[0] or "").strip()
except OSError:
key = ""
return key
def is_configured() -> bool:
return bool(_load_key_from_env())
def get_fernet():
global _FERNET
if _FERNET is not None:
return _FERNET
from cryptography.fernet import Fernet
key = _load_key_from_env()
if not key:
raise RuntimeError("SETTINGS_ENCRYPTION_KEY is not configured")
# Validate it looks like a Fernet key.
try:
raw = base64.urlsafe_b64decode(key.encode("utf-8"))
if len(raw) != 32:
raise ValueError("wrong key length")
except Exception as e:
raise RuntimeError("SETTINGS_ENCRYPTION_KEY is invalid (must be Fernet key)") from e
_FERNET = Fernet(key.encode("utf-8"))
return _FERNET
ENC_PREFIX = "enc:v1:"
def encrypt_if_possible(value: str) -> str:
value = (value or "").strip()
if not value:
return ""
if value.startswith(ENC_PREFIX):
return value
f = get_fernet()
token = f.encrypt(value.encode("utf-8")).decode("utf-8")
return f"{ENC_PREFIX}{token}"
def decrypt_if_needed(value: Optional[str]) -> str:
value = (value or "").strip()
if not value:
return ""
if not value.startswith(ENC_PREFIX):
return value
token = value[len(ENC_PREFIX) :]
f = get_fernet()
return f.decrypt(token.encode("utf-8")).decode("utf-8")