tradingview-alert-dispatcher/app/auth.py
2026-05-14 21:40:22 +08:00

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)