Документация выключена по умолчанию и закрыта токеном
Маршруты /docs и /openapi.json проверяли только адрес. С выключенным списком адресов это означало открытый доступ к описанию API из интернета. Теперь они отвечают 404, пока docs не включён явно, а включённые требуют адрес из списка и токен - заголовком либо ссылкой /docs?token=ЗНАЧЕНИЕ. Попутно исправлено сравнение токена: compare_digest на строках с не-ASCII бросает TypeError, и токен с кириллицей давал 500 вместо 401. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2039d95a86
commit
e7deb47196
+33
-7
@@ -9,6 +9,7 @@ import threading
|
||||
import time
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
from fastapi import Depends, FastAPI, File, HTTPException, Query, Request, UploadFile
|
||||
from fastapi.openapi.docs import get_swagger_ui_html
|
||||
@@ -21,7 +22,8 @@ from app.console import setup_logging
|
||||
from app.logbuffer import get as log_buffer
|
||||
from app.logbuffer import install as install_log_buffer
|
||||
from app.pipeline import ModelsMissing, Pipeline, to_wav16k
|
||||
from app.security import check_token, client_address, ip_allowed, parse_allowlist
|
||||
from app.security import (check_token, client_address, ip_allowed,
|
||||
parse_allowlist, token_matches)
|
||||
from app.store import JobStatus, JobStore
|
||||
from app.webhook import deliver_async
|
||||
from app.version import __version__
|
||||
@@ -181,8 +183,8 @@ async def lifespan(app: FastAPI):
|
||||
pool.shutdown(wait=False, cancel_futures=True)
|
||||
|
||||
|
||||
# Штатные /docs и /openapi.json отключены: они не требуют токена. Вместо них
|
||||
# ниже свои маршруты, закрытые тем же списком адресов, что и остальной сервис.
|
||||
# Штатные /docs и /openapi.json отключены: они никого не проверяют. Вместо них
|
||||
# ниже свои маршруты - выключенные по умолчанию и закрытые адресом и токеном.
|
||||
app = FastAPI(
|
||||
title="talkscore-asr",
|
||||
version=__version__,
|
||||
@@ -222,16 +224,40 @@ def guard(request: Request) -> None:
|
||||
raise HTTPException(status_code=401, detail="неверный или отсутствующий токен")
|
||||
|
||||
|
||||
def docs_guard(request: Request) -> str:
|
||||
"""Пускает к документации и возвращает токен для ссылки на схему.
|
||||
|
||||
Токен принимается и заголовком, и параметром ?token=. Браузер, открывая
|
||||
страницу по ссылке, заголовок не подставит, а документация без браузера
|
||||
теряет смысл. Раньше здесь проверялся только адрес - с выключенным
|
||||
списком адресов это означало открытый доступ.
|
||||
"""
|
||||
if not settings.docs:
|
||||
# 404, а не 403: подтверждать существование страницы незачем.
|
||||
raise HTTPException(status_code=404, detail="Not Found")
|
||||
ip_guard(request)
|
||||
supplied = request.query_params.get("token")
|
||||
if check_token(request.headers.get("authorization"), settings.token):
|
||||
return supplied or ""
|
||||
if token_matches(supplied, settings.token):
|
||||
return supplied or ""
|
||||
raise HTTPException(status_code=401, detail="неверный или отсутствующий токен")
|
||||
|
||||
|
||||
@app.get("/docs", include_in_schema=False)
|
||||
def docs_page(request: Request):
|
||||
"""Описание методов для браузера. Открывается только с разрешённых адресов."""
|
||||
ip_guard(request)
|
||||
return get_swagger_ui_html(openapi_url="openapi.json", title="talkscore-asr")
|
||||
"""Описание методов для браузера. Требует адрес из списка и токен."""
|
||||
token = docs_guard(request)
|
||||
# Схему Swagger запрашивает сам, уже без заголовка - протаскиваем токен
|
||||
# в адрес, иначе страница откроется и тут же покажет ошибку доступа.
|
||||
suffix = f"?token={quote(token)}" if token else ""
|
||||
return get_swagger_ui_html(openapi_url=f"openapi.json{suffix}",
|
||||
title="talkscore-asr")
|
||||
|
||||
|
||||
@app.get("/openapi.json", include_in_schema=False)
|
||||
def openapi_schema(request: Request) -> JSONResponse:
|
||||
ip_guard(request)
|
||||
docs_guard(request)
|
||||
schema = get_openapi(title=app.title, version=app.version,
|
||||
description=app.description, routes=app.routes)
|
||||
schema["components"] = schema.get("components", {})
|
||||
|
||||
Reference in New Issue
Block a user