import hashlib
import secrets
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from app.config import get_settings
_bearer = HTTPBearer(auto_error=False)
def generate_server_key() -> str:
"""Plaintext-ключ агента. Показывается один раз при создании сервера."""
return "ghm_" + secrets.token_urlsafe(24)
def hash_key(key: str) -> str:
return hashlib.sha256(key.encode()).hexdigest()
def auth_mode_error() -> HTTPException:
"""401/403, когда браузер не залогинен в gnexus-auth (GHARD_AUTH_CLIENT_ID задан)."""
return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="not authenticated")
async def require_admin(
request: Request,
creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
) -> None:
"""Доступ к API панели (данные, CRUD, MCP).
Порядок:
1. Bearer GHARD_ADMIN_TOKEN (если задан) — скрипты, MCP, curl.
2. Агенты: заголовок X-Server-Key проверяется только в ingest-роутере.
3. GHARD_AUTH_CLIENT_ID задан → браузер с валидной сессионной cookie.
4. auth выключен и admin_token пуст → API открыт (личная панель).
"""
settings = get_settings()
token = settings.admin_token
if token and creds is not None and creds.scheme.lower() == "bearer" and creds.credentials == token:
return
if settings.auth_client_id:
from app.auth import SESSION_COOKIE, get_session
session = await get_session(request.cookies.get(SESSION_COOKIE))
if session is None:
raise auth_mode_error()
return
if not token:
return
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="invalid admin token",
)