First Build alpha 0.1
This commit is contained in:
91
app/utils/security.py
Normal file
91
app/utils/security.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""Security utilities — password hashing (bcrypt) and token encryption (Fernet)."""
|
||||
|
||||
import os
|
||||
import secrets
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
from passlib.context import CryptContext
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Password hashing (bcrypt)
|
||||
# ---------------------------------------------------------------------------
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def hash_password(plain: str) -> str:
|
||||
"""Hash a plaintext password with bcrypt.
|
||||
|
||||
Args:
|
||||
plain: The plaintext password.
|
||||
|
||||
Returns:
|
||||
Bcrypt hash string.
|
||||
"""
|
||||
return pwd_context.hash(plain)
|
||||
|
||||
|
||||
def verify_password(plain: str, hashed: str) -> bool:
|
||||
"""Verify a plaintext password against a bcrypt hash.
|
||||
|
||||
Args:
|
||||
plain: The plaintext password to check.
|
||||
hashed: The stored bcrypt hash.
|
||||
|
||||
Returns:
|
||||
True if the password matches.
|
||||
"""
|
||||
return pwd_context.verify(plain, hashed)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fernet encryption for secrets (NPM token, relay secrets, etc.)
|
||||
# ---------------------------------------------------------------------------
|
||||
def _get_fernet() -> Fernet:
|
||||
"""Derive a Fernet key from the application SECRET_KEY.
|
||||
|
||||
The SECRET_KEY from the environment is used as the basis. We pad/truncate
|
||||
it to produce a valid 32-byte URL-safe-base64 key that Fernet requires.
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
|
||||
secret = os.environ.get("SECRET_KEY", "change-me-in-production")
|
||||
# Derive a stable 32-byte key via SHA-256
|
||||
key_bytes = hashlib.sha256(secret.encode()).digest()
|
||||
fernet_key = base64.urlsafe_b64encode(key_bytes)
|
||||
return Fernet(fernet_key)
|
||||
|
||||
|
||||
def encrypt_value(plaintext: str) -> str:
|
||||
"""Encrypt a string value with Fernet.
|
||||
|
||||
Args:
|
||||
plaintext: Value to encrypt.
|
||||
|
||||
Returns:
|
||||
Encrypted string (base64-encoded Fernet token).
|
||||
"""
|
||||
f = _get_fernet()
|
||||
return f.encrypt(plaintext.encode()).decode()
|
||||
|
||||
|
||||
def decrypt_value(ciphertext: str) -> str:
|
||||
"""Decrypt a Fernet-encrypted string.
|
||||
|
||||
Args:
|
||||
ciphertext: Encrypted value.
|
||||
|
||||
Returns:
|
||||
Original plaintext string.
|
||||
"""
|
||||
f = _get_fernet()
|
||||
return f.decrypt(ciphertext.encode()).decode()
|
||||
|
||||
|
||||
def generate_relay_secret() -> str:
|
||||
"""Generate a cryptographically secure relay secret.
|
||||
|
||||
Returns:
|
||||
A 32-character hex string.
|
||||
"""
|
||||
return secrets.token_hex(16)
|
||||
Reference in New Issue
Block a user