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