# backend/operator_auth.py """Operator authentication: the deny-by-default gate, session cookie, login + lockout, and the small data helpers shared by the routes and the CLI. Reuses the argon2 hasher (auth_passwords) and the HMAC signer (auth_cookies). The flag and SessionLocal are read as module globals at call time so tests can monkeypatch them.""" import os import time import uuid from datetime import datetime, timedelta from backend.models import OperatorUser from backend.auth_passwords import hash_password, verify_password, generate_password # Feature flag — OFF by default. When off, the gate and require_role both pass # everything through and the app behaves exactly as it does today. OPERATOR_AUTH_ENABLED = os.getenv("OPERATOR_AUTH_ENABLED", "false").lower() in ("1", "true", "yes") COOKIE_NAME = "tv_session" COOKIE_MAX_AGE = 60 * 60 * 24 * 30 # 30 days ("remember this device") MAX_LOGIN_FAILURES = 5 LOCK_MINUTES = 15 # Role ladder — a rank map so checks read naturally and 'operator' slots in later. _ROLE_RANK = {"operator": 10, "admin": 20, "superadmin": 30} def role_at_least(role: str, minimum: str) -> bool: """True iff `role` ranks at or above `minimum`. Unknown roles rank as 0.""" return _ROLE_RANK.get(role, 0) >= _ROLE_RANK[minimum] def _norm_email(email: str) -> str: return (email or "").strip().lower()