feat(auth): gate existing endpoints with matrix_gate dependency
This commit is contained in:
@@ -10,26 +10,51 @@ requires the engine to be initialized before any DB access, whereas the
|
||||
old in-memory store did not. Adding the init here keeps existing test
|
||||
modules (test_api.py, test_api_835.py, test_api_gets.py,
|
||||
test_api_parse_persists.py) working unchanged.
|
||||
|
||||
Auth posture: ``CYCLONE_AUTH_DISABLED`` defaults to ``"1"`` here so the
|
||||
existing test suite (700+ tests that don't log in) continues to pass
|
||||
unmodified. Tests in ``test_auth_*`` modules explicitly re-enable auth
|
||||
so they exercise the real ``get_current_user`` / ``matrix_gate`` paths.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
# Must be set before ``cyclone.api`` is imported so ``get_current_user``
|
||||
# / ``matrix_gate`` see the env var. The module-level ``AUTH_DISABLED``
|
||||
# flag in ``cyclone.auth.deps`` is flipped per-test below.
|
||||
os.environ.setdefault("CYCLONE_AUTH_DISABLED", "1")
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _auto_init_db(tmp_path, monkeypatch):
|
||||
def _auto_init_db(tmp_path, monkeypatch, request):
|
||||
"""Point CYCLONE_DB_URL at a per-test SQLite file and init the schema.
|
||||
|
||||
Also wires a fresh ``EventBus`` onto ``app.state`` because ``TestClient``
|
||||
does not invoke the FastAPI lifespan handler unless used as a context
|
||||
manager. The bus is reset between tests so subscribers don't leak.
|
||||
|
||||
Auth posture is set per-test from the test module's name: legacy
|
||||
tests (any module not starting with ``test_auth``) run with
|
||||
``AUTH_DISABLED=True`` so they hit the API without logging in;
|
||||
``test_auth_*`` modules run with ``AUTH_DISABLED=False`` so they
|
||||
exercise the real ``get_current_user`` / ``matrix_gate`` paths.
|
||||
"""
|
||||
monkeypatch.setenv("CYCLONE_DB_URL", f"sqlite:///{tmp_path}/test.db")
|
||||
from cyclone import db
|
||||
from cyclone.api import app
|
||||
from cyclone.auth import deps as _auth_deps
|
||||
from cyclone.pubsub import EventBus
|
||||
|
||||
test_module = request.node.module.__name__
|
||||
if test_module.startswith("test_auth"):
|
||||
_auth_deps.AUTH_DISABLED = False
|
||||
else:
|
||||
_auth_deps.AUTH_DISABLED = True
|
||||
|
||||
db._reset_for_tests()
|
||||
db.init_db()
|
||||
app.state.event_bus = EventBus()
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Spot-check that existing endpoints now require auth (when AUTH_DISABLED is not set).
|
||||
|
||||
The conftest in ``tests/conftest.py`` flips ``AUTH_DISABLED=True`` for
|
||||
every test module that does NOT start with ``test_auth`` — this module
|
||||
deliberately is NOT named ``test_auth_*`` so it inherits the default
|
||||
disabled posture... wait, that's the opposite of what we want.
|
||||
|
||||
This module is named ``test_existing_endpoints_require_auth`` so the
|
||||
conftest will treat it as a legacy test and set ``AUTH_DISABLED=True``,
|
||||
bypassing the auth check entirely. To actually verify the gate, this
|
||||
test's ``client`` fixture flips ``AUTH_DISABLED`` to ``False``
|
||||
*just for this test*, then restores it afterwards. This way the
|
||||
rest of the suite still sees the disabled posture and existing
|
||||
tests keep passing.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import delete
|
||||
|
||||
from cyclone.api import app
|
||||
from cyclone.auth import deps as _auth_deps
|
||||
from cyclone.auth.deps import AUTH_DISABLED
|
||||
from cyclone.db import Session as DbSession
|
||||
from cyclone.db import SessionLocal, User
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear():
|
||||
with SessionLocal()() as db:
|
||||
db.execute(delete(DbSession))
|
||||
db.execute(delete(User))
|
||||
db.commit()
|
||||
yield
|
||||
with SessionLocal()() as db:
|
||||
db.execute(delete(DbSession))
|
||||
db.execute(delete(User))
|
||||
db.commit()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
# Force AUTH_DISABLED=False for these tests so the dep actually checks the cookie.
|
||||
original = AUTH_DISABLED
|
||||
_auth_deps.AUTH_DISABLED = False
|
||||
try:
|
||||
yield TestClient(app)
|
||||
finally:
|
||||
_auth_deps.AUTH_DISABLED = original
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method,path", [
|
||||
("GET", "/api/claims"),
|
||||
("GET", "/api/remittances"),
|
||||
("GET", "/api/providers"),
|
||||
("GET", "/api/batches"),
|
||||
("GET", "/api/dashboard/summary"),
|
||||
("GET", "/api/activity"),
|
||||
])
|
||||
def test_existing_get_endpoints_require_auth(client, method, path):
|
||||
resp = client.request(method, path)
|
||||
assert resp.status_code == 401, f"{method} {path} returned {resp.status_code}"
|
||||
|
||||
|
||||
def test_health_is_public(client):
|
||||
"""``/api/health`` is the public healthcheck and must remain reachable."""
|
||||
resp = client.get("/api/health")
|
||||
assert resp.status_code != 401, f"/api/health returned {resp.status_code}"
|
||||
Reference in New Issue
Block a user