70 lines
2.3 KiB
Python
70 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
import os
|
|
import time
|
|
from http import cookies
|
|
|
|
from app.config import Settings
|
|
|
|
|
|
COOKIE_NAME = "tv_dispatcher_session"
|
|
SESSION_TTL_SECONDS = 60 * 60 * 12
|
|
PASSWORD_ITERATIONS = 200_000
|
|
|
|
|
|
def _sign(secret: str, value: str) -> str:
|
|
return hmac.new(secret.encode(), value.encode(), hashlib.sha256).hexdigest()
|
|
|
|
|
|
def make_session_cookie(settings: Settings) -> str:
|
|
payload = f"{settings.admin_username}:{int(time.time())}"
|
|
encoded = base64.urlsafe_b64encode(payload.encode()).decode()
|
|
return f"{encoded}.{_sign(settings.session_secret, encoded)}"
|
|
|
|
|
|
def is_valid_session(settings: Settings, cookie_header: str | None) -> bool:
|
|
if not cookie_header:
|
|
return False
|
|
jar = cookies.SimpleCookie(cookie_header)
|
|
morsel = jar.get(COOKIE_NAME)
|
|
if not morsel:
|
|
return False
|
|
try:
|
|
encoded, signature = morsel.value.split(".", 1)
|
|
if not hmac.compare_digest(signature, _sign(settings.session_secret, encoded)):
|
|
return False
|
|
raw = base64.urlsafe_b64decode(encoded.encode()).decode()
|
|
username, issued = raw.rsplit(":", 1)
|
|
return username == settings.admin_username and time.time() - int(issued) <= SESSION_TTL_SECONDS
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def hash_password(password: str) -> str:
|
|
salt = os.urandom(16)
|
|
digest = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, PASSWORD_ITERATIONS)
|
|
return f"pbkdf2_sha256${PASSWORD_ITERATIONS}${base64.b64encode(salt).decode()}${base64.b64encode(digest).decode()}"
|
|
|
|
|
|
def verify_password(password: str, password_hash: str) -> bool:
|
|
try:
|
|
algorithm, iterations, salt, digest = password_hash.split("$", 3)
|
|
if algorithm != "pbkdf2_sha256":
|
|
return False
|
|
expected = hashlib.pbkdf2_hmac(
|
|
"sha256",
|
|
password.encode(),
|
|
base64.b64decode(salt.encode()),
|
|
int(iterations),
|
|
)
|
|
return hmac.compare_digest(base64.b64encode(expected).decode(), digest)
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def check_credentials(settings: Settings, username: str, password: str, password_hash: str) -> bool:
|
|
return hmac.compare_digest(username, settings.admin_username) and verify_password(password, password_hash)
|