60 lines
2.0 KiB
Python
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
|