security: encrypt OAuth tokens at rest (Fernet) + docs
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
|
||||
KEY_ENV = 'LIFERPG_DATA_KEY'
|
||||
FALLBACK_KEY_PATH = os.path.join(os.path.dirname(__file__), '.dev_liferpg_key')
|
||||
|
||||
|
||||
def _load_key_from_env():
|
||||
v = os.getenv(KEY_ENV)
|
||||
if v:
|
||||
return v.encode()
|
||||
return None
|
||||
|
||||
|
||||
def _load_or_create_fallback_key():
|
||||
# Try to read an existing key file (dev convenience). Create with restrictive perms if missing.
|
||||
try:
|
||||
if os.path.exists(FALLBACK_KEY_PATH):
|
||||
with open(FALLBACK_KEY_PATH, 'rb') as f:
|
||||
return f.read().strip()
|
||||
# generate and persist locally (dev only)
|
||||
key = Fernet.generate_key()
|
||||
# write file with 0600 perms
|
||||
fd = os.open(FALLBACK_KEY_PATH, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
f.write(key)
|
||||
return key
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def get_fernet():
|
||||
key = _load_key_from_env() or _load_or_create_fallback_key()
|
||||
if not key:
|
||||
raise RuntimeError('Encryption key not available. Set env var LIFERPG_DATA_KEY or allow creating a dev key file.')
|
||||
return Fernet(key)
|
||||
|
||||
|
||||
def encrypt_text(plaintext: str) -> str:
|
||||
if plaintext is None:
|
||||
return ''
|
||||
f = get_fernet()
|
||||
token = f.encrypt(plaintext.encode('utf-8'))
|
||||
return token.decode('utf-8')
|
||||
|
||||
|
||||
def decrypt_text(token_text: str) -> str:
|
||||
if not token_text:
|
||||
return ''
|
||||
f = get_fernet()
|
||||
try:
|
||||
out = f.decrypt(token_text.encode('utf-8'))
|
||||
return out.decode('utf-8')
|
||||
except InvalidToken:
|
||||
# Token can't be decrypted — likely different key; surface empty
|
||||
return ''
|
||||
Reference in New Issue
Block a user