06/03 Optimize app
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
"""Thin wrapper for writing audit log entries."""
|
||||
import logging
|
||||
from flask import request
|
||||
|
||||
log = logging.getLogger('app.audit')
|
||||
|
||||
|
||||
def audit(action: str, description: str = ''):
|
||||
"""
|
||||
Write one audit log entry. Safe to call from any request context;
|
||||
silently swallows DB errors so it never breaks the main flow.
|
||||
"""
|
||||
try:
|
||||
from app.extensions import db
|
||||
from app.models.audit_log import AuditLog
|
||||
entry = AuditLog(
|
||||
action=action,
|
||||
description=description[:255] if description else '',
|
||||
ip_address=request.remote_addr,
|
||||
)
|
||||
db.session.add(entry)
|
||||
db.session.commit()
|
||||
log.info('[audit] %s — %s (ip=%s)', action, description, request.remote_addr)
|
||||
except Exception as exc:
|
||||
log.warning('[audit] failed to write entry: %s', exc)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user