| import base64 | |
| import hashlib | |
| import hmac | |
| import json | |
| import os | |
| import time | |
| from typing import Optional | |
| COOKIE_NAME = "converta_session" | |
| INTERNAL_HEADER = "x-internal-auth" | |
| def get_app_password() -> Optional[str]: | |
| value = os.environ.get("APP_PASSWORD") | |
| if value is None: | |
| return None | |
| value = value.strip() | |
| return value or None | |
| def _b64url_encode(data: bytes) -> str: | |
| return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=") | |
| def _b64url_decode(data: str) -> bytes: | |
| padded = data + "=" * (-len(data) % 4) | |
| return base64.urlsafe_b64decode(padded.encode("ascii")) | |
| def _sign(payload_b64: str, secret: str) -> str: | |
| sig = hmac.new(secret.encode("utf-8"), payload_b64.encode("ascii"), hashlib.sha256).digest() | |
| return _b64url_encode(sig) | |
| def create_session_token(secret: str, ttl_seconds: int = 7 * 24 * 60 * 60) -> str: | |
| now = int(time.time()) | |
| payload = {"iat": now, "exp": now + ttl_seconds} | |
| payload_b64 = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode("utf-8")) | |
| sig_b64 = _sign(payload_b64, secret) | |
| return f"{payload_b64}.{sig_b64}" | |
| def verify_session_token(token: str, secret: str) -> bool: | |
| try: | |
| payload_b64, sig_b64 = token.split(".", 1) | |
| except ValueError: | |
| return False | |
| expected = _sign(payload_b64, secret) | |
| if not hmac.compare_digest(expected, sig_b64): | |
| return False | |
| try: | |
| payload = json.loads(_b64url_decode(payload_b64).decode("utf-8")) | |
| except Exception: | |
| return False | |
| exp = payload.get("exp") | |
| if not isinstance(exp, int): | |
| return False | |
| if exp < int(time.time()): | |
| return False | |
| return True | |
| def constant_time_equals(a: str, b: str) -> bool: | |
| return hmac.compare_digest(a.encode("utf-8"), b.encode("utf-8")) | |