get_current_user навешивается на роутер целиком, публичными остаются только health и auth (список закреплён tests/api/test_route_auth.py). API не стартует с плейсхолдером или коротким JWT_SECRET без ALLOW_DEV_SECRET=1. Лимитер логинов ограничен по числу ключей. Клиентский IP берётся из последнего хопа X-Forwarded-For.
39 lines
1.3 KiB
Python
39 lines
1.3 KiB
Python
"""Every route answers 401 without a token, except a short, explicit list of public ones."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from fintracker.api.app import API_PREFIX
|
|
|
|
PUBLIC = {
|
|
("GET", f"{API_PREFIX}/health"),
|
|
("POST", f"{API_PREFIX}/auth/login"),
|
|
("POST", f"{API_PREFIX}/auth/refresh"),
|
|
("POST", f"{API_PREFIX}/auth/logout"),
|
|
}
|
|
|
|
|
|
def _routes(app) -> list[tuple[str, str]]:
|
|
"""(METHOD, path) for every documented operation. Walks the OpenAPI schema rather than
|
|
`app.routes`: FastAPI nests included routers there in a private wrapper type."""
|
|
return sorted(
|
|
(method.upper(), path) for path, item in app.openapi()["paths"].items() for method in item
|
|
)
|
|
|
|
|
|
async def test_every_route_but_the_public_ones_requires_a_token(app, client):
|
|
routes = _routes(app)
|
|
assert len(routes) > 50 # guards against the walk silently matching nothing
|
|
assert set(routes) >= PUBLIC # a renamed public route must update this list, not vanish
|
|
|
|
open_routes = []
|
|
for method, path in routes:
|
|
if (method, path) in PUBLIC:
|
|
continue
|
|
url = re.sub(r"\{[^}]+\}", "1", path)
|
|
r = await client.request(method, url)
|
|
if r.status_code != 401:
|
|
open_routes.append(f"{method} {path} -> {r.status_code}")
|
|
assert not open_routes, "reachable without a token:\n" + "\n".join(open_routes)
|