wendajia/backend/app/security.py

60 lines
2.0 KiB
Python

import base64
import hashlib
import hmac
import json
import secrets
import time
from .config import settings
def hash_password(password: str) -> str:
salt = secrets.token_bytes(16)
digest = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 210_000)
return "pbkdf2_sha256$210000$%s$%s" % (
base64.urlsafe_b64encode(salt).decode(),
base64.urlsafe_b64encode(digest).decode(),
)
def verify_password(password: str, encoded: str) -> bool:
try:
scheme, rounds, salt_text, digest_text = encoded.split("$")
if scheme != "pbkdf2_sha256":
return False
salt = base64.urlsafe_b64decode(salt_text.encode())
expected = base64.urlsafe_b64decode(digest_text.encode())
actual = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, int(rounds))
return hmac.compare_digest(actual, expected)
except (ValueError, TypeError):
return False
def _b64(value: bytes) -> str:
return base64.urlsafe_b64encode(value).decode().rstrip("=")
def _unb64(value: str) -> bytes:
return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))
def create_session_token(user_id: int, role_code: str) -> str:
payload = {"sub": user_id, "role": role_code, "exp": int(time.time()) + settings.session_hours * 3600}
body = _b64(json.dumps(payload, separators=(",", ":")).encode())
signature = hmac.new(settings.secret_key.encode(), body.encode(), hashlib.sha256).digest()
return f"{body}.{_b64(signature)}"
def decode_session_token(token: str) -> dict | None:
try:
body, signature = token.split(".", 1)
expected = hmac.new(settings.secret_key.encode(), body.encode(), hashlib.sha256).digest()
if not hmac.compare_digest(_unb64(signature), expected):
return None
payload = json.loads(_unb64(body))
if int(payload.get("exp", 0)) <= int(time.time()):
return None
return payload
except (ValueError, TypeError, json.JSONDecodeError):
return None