06/03 Optimize app

This commit is contained in:
2026-06-03 16:22:02 -04:00
parent ec1c0fc7c1
commit eb85e70793
16 changed files with 406 additions and 5 deletions
+51
View File
@@ -0,0 +1,51 @@
"""
Transparent at-rest encryption for sensitive DB columns (OAuth tokens).
Uses a SQLAlchemy TypeDecorator so existing code needs zero changes —
values are encrypted on write and decrypted on read automatically.
Key derivation: SHA-256(SECRET_KEY) → 32 bytes → Fernet-compatible base64 key.
Migration: the decryptor falls back to returning the raw value when
decryption fails, so existing plaintext tokens keep working until
they are re-written (e.g. next token refresh or reconnect).
"""
import base64
import hashlib
import logging
from sqlalchemy import types
log = logging.getLogger('app.crypto')
def _fernet():
from flask import current_app
from cryptography.fernet import Fernet
secret = current_app.config['SECRET_KEY']
key = base64.urlsafe_b64encode(hashlib.sha256(secret.encode()).digest())
return Fernet(key)
class EncryptedText(types.TypeDecorator):
"""Store text columns encrypted at rest using Fernet symmetric encryption."""
impl = types.Text
cache_ok = True
def process_bind_param(self, value, dialect):
if value is None:
return None
try:
return _fernet().encrypt(value.encode()).decode()
except Exception as exc:
log.warning('[crypto] encrypt failed: %s', exc)
return value # store plaintext rather than lose data
def process_result_value(self, value, dialect):
if value is None:
return None
try:
return _fernet().decrypt(value.encode()).decode()
except Exception:
# Value is plaintext (pre-migration) — return as-is
return value