102 lines
2.6 KiB
Python
102 lines
2.6 KiB
Python
"""
|
|
Authentication utilities - JWT and encryption
|
|
"""
|
|
import os
|
|
import jwt
|
|
import hashlib
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional, Dict, Any
|
|
from cryptography.fernet import Fernet
|
|
import base64
|
|
|
|
# JWT Configuration
|
|
JWT_SECRET = os.getenv("JWT_SECRET", "dev-secret-please-change-in-production")
|
|
JWT_ALGORITHM = "HS256"
|
|
JWT_EXPIRATION_HOURS = 24 * 7 # 7 days
|
|
|
|
# Encryption key derived from JWT_SECRET (for settings encryption)
|
|
def _get_fernet_key() -> bytes:
|
|
"""Generate a Fernet-compatible key from JWT_SECRET"""
|
|
# Use SHA256 to get a consistent 32-byte key, then base64 encode
|
|
key_hash = hashlib.sha256(JWT_SECRET.encode()).digest()
|
|
return base64.urlsafe_b64encode(key_hash)
|
|
|
|
_fernet = Fernet(_get_fernet_key())
|
|
|
|
|
|
def create_access_token(user_data: Dict[str, Any]) -> str:
|
|
"""
|
|
Create a JWT access token for a user
|
|
|
|
Args:
|
|
user_data: Dict containing user info (id, email, name, avatar_url)
|
|
|
|
Returns:
|
|
JWT token string
|
|
"""
|
|
payload = {
|
|
"sub": str(user_data["id"]), # Subject (user ID)
|
|
"email": user_data["email"],
|
|
"name": user_data.get("name"),
|
|
"avatar_url": user_data.get("avatar_url"),
|
|
"exp": datetime.utcnow() + timedelta(hours=JWT_EXPIRATION_HOURS),
|
|
"iat": datetime.utcnow(),
|
|
}
|
|
|
|
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
|
|
|
|
|
|
def verify_access_token(token: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Verify and decode a JWT access token
|
|
|
|
Args:
|
|
token: JWT token string
|
|
|
|
Returns:
|
|
Decoded payload if valid, None if invalid/expired
|
|
"""
|
|
try:
|
|
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
|
|
return payload
|
|
except jwt.ExpiredSignatureError:
|
|
return None
|
|
except jwt.InvalidTokenError:
|
|
return None
|
|
|
|
|
|
def encrypt_settings(settings_json: str) -> str:
|
|
"""
|
|
Encrypt user settings JSON string
|
|
|
|
Args:
|
|
settings_json: JSON string of user settings
|
|
|
|
Returns:
|
|
Encrypted string (base64 encoded)
|
|
"""
|
|
encrypted = _fernet.encrypt(settings_json.encode())
|
|
return encrypted.decode()
|
|
|
|
|
|
def decrypt_settings(encrypted_settings: str) -> str:
|
|
"""
|
|
Decrypt user settings
|
|
|
|
Args:
|
|
encrypted_settings: Encrypted settings string
|
|
|
|
Returns:
|
|
Decrypted JSON string
|
|
"""
|
|
decrypted = _fernet.decrypt(encrypted_settings.encode())
|
|
return decrypted.decode()
|
|
|
|
|
|
def get_user_id_from_token(token: str) -> Optional[str]:
|
|
"""Extract user ID from a valid token"""
|
|
payload = verify_access_token(token)
|
|
if payload:
|
|
return payload.get("sub")
|
|
return None
|