M1: backend core + proposal engine
- 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>
This commit is contained in:
24
gogo/crypto.py
Normal file
24
gogo/crypto.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user