diff --git a/api/src/core/public_paths.py b/api/src/core/public_paths.py new file mode 100644 index 0000000..1b671c2 --- /dev/null +++ b/api/src/core/public_paths.py @@ -0,0 +1,58 @@ +"""Публичные пути для AuthorizationMiddleware: SPA и infra, без /api.""" + +ABSOLUTE_PUBLIC_PATHS = ( + "", + "/", + "/healthcheck", + "/healthcheck2", + "/healthz", + "/readyz", +) + +START_PUBLIC_PREFIXES = ( + "/static", + "/back-static", + "/openapi.json", + "/docs", + "/docs-local", + "/login", + "/task", + "/project", + "/forms", + "/table", + "/admin_panel", + "/dicts", + "/svod", + "/index", + "/favicon", + "/assets", + "/images", +) + + +def with_root(path: str, root_path: str) -> tuple[str, ...]: + if not root_path: + return (path,) + root = root_path.rstrip("/") + if path == "": + return (path, root) + return (path, f"{root}{path}") + + +def public_endpoint_specs(root_path: str = "") -> list[tuple[str, str]]: + """Пары (mode, path): mode is 'absolute' or 'start'.""" + specs: list[tuple[str, str]] = [] + seen: set[tuple[str, str]] = set() + for path in ABSOLUTE_PUBLIC_PATHS: + for full in with_root(path, root_path): + item = ("absolute", full) + if item not in seen: + seen.add(item) + specs.append(item) + for path in START_PUBLIC_PREFIXES: + for full in with_root(path, root_path): + item = ("start", full) + if item not in seen: + seen.add(item) + specs.append(item) + return specs diff --git a/api/src/main.py b/api/src/main.py index c6aebda..2687b21 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -18,6 +18,7 @@ from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware from src.api.v1.router import api_router from src.core.config import settings from src.core.exception_handlers import register_exception_handlers +from src.core.public_paths import public_endpoint_specs from src.db.base import engine from src.db.session import create_tables @@ -186,58 +187,12 @@ async def readyz(response: Response): if not settings.DEBUG: + _search = {"absolute": SearchType.ABSOLUTE, "start": SearchType.START} app.add_middleware( AuthorizationMiddleware, open_endpoints=[ - OpenEndpoint(path="", type_search=SearchType.ABSOLUTE), - OpenEndpoint(path=settings.ROOT_PATH + "", type_search=SearchType.ABSOLUTE), - OpenEndpoint(path="/", type_search=SearchType.ABSOLUTE), - OpenEndpoint(path=settings.ROOT_PATH + "/", type_search=SearchType.ABSOLUTE), - OpenEndpoint(path="/healthcheck", type_search=SearchType.ABSOLUTE), - OpenEndpoint( - path=settings.ROOT_PATH + "/healthcheck", - type_search=SearchType.ABSOLUTE, - ), - OpenEndpoint(path="/healthcheck2", type_search=SearchType.ABSOLUTE), - OpenEndpoint( - path=settings.ROOT_PATH + "/healthcheck2", - type_search=SearchType.ABSOLUTE, - ), - OpenEndpoint(path="/healthz", type_search=SearchType.ABSOLUTE), - OpenEndpoint( - path=settings.ROOT_PATH + "/healthz", - type_search=SearchType.ABSOLUTE, - ), - OpenEndpoint(path="/readyz", type_search=SearchType.ABSOLUTE), - OpenEndpoint( - path=settings.ROOT_PATH + "/readyz", - type_search=SearchType.ABSOLUTE, - ), - OpenEndpoint(path="/static", type_search=SearchType.START), - OpenEndpoint( - path=settings.ROOT_PATH + "/static", - type_search=SearchType.START, - ), - OpenEndpoint(path="/back-static", type_search=SearchType.START), - OpenEndpoint( - path=settings.ROOT_PATH + "/back-static", - type_search=SearchType.START, - ), - OpenEndpoint(path="/openapi.json", type_search=SearchType.START), - OpenEndpoint( - path=settings.ROOT_PATH + "/openapi.json", - type_search=SearchType.START, - ), - OpenEndpoint(path="/docs", type_search=SearchType.START), - OpenEndpoint( - path=settings.ROOT_PATH + "/docs", - type_search=SearchType.START, - ), - OpenEndpoint(path="/docs-local", type_search=SearchType.START), - OpenEndpoint( - path=settings.ROOT_PATH + "/docs-local", - type_search=SearchType.START, - ), + OpenEndpoint(path=path, type_search=_search[mode]) + for mode, path in public_endpoint_specs(settings.ROOT_PATH) ], ) diff --git a/api/tests/unit/test_public_paths.py b/api/tests/unit/test_public_paths.py new file mode 100644 index 0000000..1ce4d3a --- /dev/null +++ b/api/tests/unit/test_public_paths.py @@ -0,0 +1,20 @@ +from src.core.public_paths import public_endpoint_specs + + +def test_login_page_is_public_without_root(): + specs = public_endpoint_specs("") + assert ("start", "/login") in specs + assert ("absolute", "/") in specs + + +def test_login_page_is_public_with_root_path(): + specs = public_endpoint_specs("/aurora/apps/fastapi-tsygankov-test") + assert ("start", "/login") in specs + assert ("start", "/aurora/apps/fastapi-tsygankov-test/login") in specs + + +def test_api_routes_are_not_public(): + specs = public_endpoint_specs("/aurora/apps/fastapi-tsygankov-test") + paths = [path for _, path in specs] + assert not any(path == "/api" or path.startswith("/api/") for path in paths) + assert not any("/api/v1" in path for path in paths)