document-processor/app/core/auth.py

68 lines
1.8 KiB
Python

import base64
import hashlib
import hmac
import json
import os
import secrets
from pathlib import Path
from typing import Optional
USERS_FILE = Path(os.getenv("DOCUMENT_PROCESSOR_USERS_FILE", "data/users.json"))
def _b64e(b: bytes) -> str:
return base64.b64encode(b).decode("utf-8")
def _b64d(s: str) -> bytes:
return base64.b64decode(s.encode("utf-8"))
def hash_password(password: str) -> str:
salt = secrets.token_bytes(16)
digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 200_000)
return f"pbkdf2_sha256$200000${_b64e(salt)}${_b64e(digest)}"
def verify_password(password: str, stored: str) -> bool:
try:
scheme, iterations, salt_b64, digest_b64 = stored.split("$", 3)
if scheme != "pbkdf2_sha256":
return False
digest = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
_b64d(salt_b64),
int(iterations),
)
return hmac.compare_digest(digest, _b64d(digest_b64))
except Exception:
return False
def load_users() -> list[dict]:
if not USERS_FILE.exists():
return []
return json.loads(USERS_FILE.read_text())
def get_user_by_username(username: str) -> Optional[dict]:
username = username.strip().lower()
for user in load_users():
if user.get("username", "").lower() == username:
return user
return None
def authenticate_user(username: str, password: str) -> Optional[dict]:
user = get_user_by_username(username)
if not user:
return None
if not verify_password(password, user.get("password_hash", "")):
return None
return {
"username": user["username"],
"display_name": user.get("display_name") or user["username"],
"is_admin": bool(user.get("is_admin", False)),
}