"""Shared dependencies: the database session and the two ways into the API.""" from __future__ import annotations from typing import Annotated from fastapi import Cookie, Depends, Header, HTTPException, status from sqlalchemy.orm import Session from app.config import env_settings from app.db import get_session from app.services import auth_service from app.services.settings_service import load_settings from app.settings_schema import AppSettings DbSession = Annotated[Session, Depends(get_session)] def require_auth( session: DbSession, x_api_token: Annotated[str | None, Header()] = None, kadence_session: Annotated[str | None, Cookie()] = None, ) -> None: """Guard every API call. Two independent gates, either of which lets a request through: * `X-API-Token` against `API_TOKEN` — the desktop app and scripts, off by default (friction-free on a laptop; set the moment the backend leaves it). * a `kadence_session` cookie from `/api/auth/login*` — the browser, closed the moment a password or Telegram login is configured in Settings → Access. With neither `API_TOKEN` nor a login configured, the API stays open, exactly as before this existed. """ expected = env_settings().api_token if expected and x_api_token == expected: return if auth_service.valid_session(session, kadence_session): return settings = load_settings(session) login_required = bool(settings.auth.password_hash) or ( settings.auth.telegram_login_enabled and bool(settings.auth.telegram_owner_id) ) if not expected and not login_required: return raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Sign in, or send a valid X-API-Token.", ) def get_settings(session: DbSession) -> AppSettings: return load_settings(session) Settings = Annotated[AppSettings, Depends(get_settings)]