"""Lightweight admin gate for the web UI. A single shared PIN (`ADMIN_PIN` in .env) unlocks destructive actions (deleting projects/competitors/employees, editing employees) for the browser session via an HMAC-signed cookie. No per-user accounts — this is an internal localhost tool, not a public app. If `ADMIN_PIN` is empty the gate is OPEN (nothing restricted) so the tool is never bricked by a missing setting; set a PIN to actually restrict. """ from __future__ import annotations import hashlib import hmac from starlette.requests import Request from app.config import settings ADMIN_COOKIE = "dld_admin" COOKIE_MAX_AGE = 60 * 60 * 8 # 8 hours def admin_configured() -> bool: return bool(settings.admin_pin) def admin_token() -> str | None: """The cookie value that proves admin: HMAC(pin, marker). None if no PIN.""" if not settings.admin_pin: return None return hmac.new(settings.admin_pin.encode(), b"dld-admin-v1", hashlib.sha256).hexdigest() def pin_ok(pin: str) -> bool: """Constant-time check of a submitted PIN against the configured one.""" return bool(settings.admin_pin) and hmac.compare_digest((pin or "").strip(), settings.admin_pin) def request_is_admin(request: Request) -> bool: if not settings.admin_pin: return True # gate not configured → open token = request.cookies.get(ADMIN_COOKIE) expected = admin_token() return bool(token and expected and hmac.compare_digest(token, expected))