- FastAPI skeleton, SQLAlchemy models (§13), Alembic initial migration - SchedulingProvider interface with google_calendar (free/busy read-only), partner_api (Appendix B client) and mock implementations - Proposal engine: create → provider-routed delivery → owner actions (resolve/confirm+SMS/reject) → expiry + reminders (§9) - Signed single-use action links, .ics METHOD:REQUEST attachment - Partner outcome webhook with HMAC verification + polling fallback - SmsProvider (console) with Bosnian templates (§5.5), EmailProvider (console/SMTP) - Fake partner API server in tests/ — Appendix B reference implementation - 43 tests: slot math, proposal lifecycle, action links, partner contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
25 lines
612 B
Python
25 lines
612 B
Python
"""Symmetric encryption for secrets at rest (OAuth tokens, partner API keys).
|
|
|
|
Key is derived from GOGO_SECRET_KEY; rotate by re-encrypting after a key change.
|
|
"""
|
|
|
|
import base64
|
|
import hashlib
|
|
|
|
from cryptography.fernet import Fernet
|
|
|
|
from gogo.config import get_settings
|
|
|
|
|
|
def _fernet() -> Fernet:
|
|
key = hashlib.sha256(get_settings().secret_key.encode()).digest()
|
|
return Fernet(base64.urlsafe_b64encode(key))
|
|
|
|
|
|
def encrypt(plaintext: str) -> str:
|
|
return _fernet().encrypt(plaintext.encode()).decode()
|
|
|
|
|
|
def decrypt(ciphertext: str) -> str:
|
|
return _fernet().decrypt(ciphertext.encode()).decode()
|