Files
2026-02-25 02:08:34 +07:00

58 lines
1.7 KiB
Python

import secrets
from typing import Optional
import bcrypt
from cryptography.fernet import Fernet, InvalidToken
from app.core.config import settings
def generate_api_key(prefix_len: int = 8, token_bytes: int = 32) -> str:
prefix = secrets.token_urlsafe(prefix_len)[:prefix_len]
token = secrets.token_urlsafe(token_bytes)
return f"{prefix}.{token}"
def get_prefix(api_key: str) -> str:
return api_key.split(".", 1)[0]
def hash_api_key(api_key: str) -> str:
hashed = bcrypt.hashpw(api_key.encode("utf-8"), bcrypt.gensalt())
return hashed.decode("utf-8")
def verify_api_key(api_key: str, api_key_hash: str) -> bool:
return bcrypt.checkpw(api_key.encode("utf-8"), api_key_hash.encode("utf-8"))
def _get_fernet() -> Fernet:
if not settings.API_KEY_ENC_SECRET:
raise ValueError("API_KEY_ENC_SECRET is not configured")
# Expect a base64 urlsafe key; if plaintext is provided, derive a Fernet key
secret = settings.API_KEY_ENC_SECRET
# If length is 44 and endswith '=', assume already a Fernet key
if len(secret) == 44 and secret.endswith("="):
key = secret.encode("utf-8")
else:
# Derive deterministic Fernet key from secret (simple approach)
import base64
import hashlib
digest = hashlib.sha256(secret.encode("utf-8")).digest()
key = base64.urlsafe_b64encode(digest)
return Fernet(key)
def encrypt_api_key(plain_key: str) -> str:
f = _get_fernet()
return f.encrypt(plain_key.encode("utf-8")).decode("utf-8")
def decrypt_api_key(cipher_text: str) -> Optional[str]:
try:
f = _get_fernet()
return f.decrypt(cipher_text.encode("utf-8")).decode("utf-8")
except (InvalidToken, ValueError):
return None