from __future__ import annotations import base64 import hashlib import hmac import secrets import time def hash_password(password: str) -> str: salt = secrets.token_bytes(16) digest = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 240_000) return "pbkdf2_sha256$240000$%s$%s" % ( base64.b64encode(salt).decode(), base64.b64encode(digest).decode(), ) def verify_password(password: str, encoded: str) -> bool: try: algo, rounds, salt_b64, digest_b64 = encoded.split("$", 3) if algo != "pbkdf2_sha256": return False salt = base64.b64decode(salt_b64) expected = base64.b64decode(digest_b64) actual = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, int(rounds)) return hmac.compare_digest(actual, expected) except Exception: return False def token_urlsafe() -> str: return secrets.token_urlsafe(32) def sign(value: str, secret: str) -> str: payload = f"{value}|{int(time.time())}" mac = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest() return base64.urlsafe_b64encode(f"{payload}|{mac}".encode()).decode() def unsign(cookie: str, secret: str, max_age: int = 60 * 60 * 24 * 14) -> str | None: try: decoded = base64.urlsafe_b64decode(cookie.encode()).decode() value, timestamp, mac = decoded.rsplit("|", 2) payload = f"{value}|{timestamp}" expected = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(mac, expected): return None if time.time() - int(timestamp) > max_age: return None return value except Exception: return None