feat: add AUTH_DISABLED no-auth fallback mode

- config.py: AUTH_DISABLED: bool = False setting
- middleware/auth.py: bypass all checks when AUTH_DISABLED=True
- security.py: require_admin_auth returns synthetic context when disabled
- endpoints/auth.py: /me returns synthetic admin; /sign-out → / when disabled
- main.py: startup WARNING when disabled; pass auth_disabled to login.html
- templates/login.html: info banner with Go to dashboard link when disabled
- templates/setup.html: document AUTH_DISABLED option with security warning
- tests/test_auth.py: 4 new AUTH_DISABLED tests (445 total, all pass)

Agent-Logs-Url: https://github.com/christianlouis/dmarq/sessions/18f41bf2-0b68-4b7d-afb5-d2894c212a8f

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-30 11:38:52 +00:00
parent 531dc968a8
commit 04931172dd
8 changed files with 163 additions and 10 deletions
+56
View File
@@ -237,3 +237,59 @@ class TestSignOutEndpoint:
set_cookie = res.headers.get("set-cookie", "")
assert SESSION_COOKIE in set_cookie
assert "Max-Age=0" in set_cookie or "max-age=0" in set_cookie
# ── AUTH_DISABLED mode ────────────────────────────────────────────────────────
class TestAuthDisabled:
"""Verify the AUTH_DISABLED=true no-auth fallback mode."""
def test_me_returns_synthetic_admin_when_auth_disabled(self, client: TestClient):
"""With AUTH_DISABLED, /me must return the synthetic admin profile."""
with patch("app.api.api_v1.endpoints.auth.settings") as mock_settings:
mock_settings.AUTH_DISABLED = True
res = client.get("/api/v1/auth/me")
assert res.status_code == 200
data = res.json()
assert data["is_superuser"] is True
assert data["auth_disabled"] is True
assert data["email"] == "admin@localhost"
def test_sign_out_redirects_to_root_when_auth_disabled(self, client: TestClient):
"""With AUTH_DISABLED, sign-out should redirect to / (no Logto session to clear)."""
with patch("app.api.api_v1.endpoints.auth.settings") as mock_settings:
mock_settings.AUTH_DISABLED = True
res = client.get("/api/v1/auth/sign-out", follow_redirects=False)
assert res.status_code == 302
assert res.headers["location"] == "/"
def test_require_admin_auth_passes_when_disabled(self):
"""require_admin_auth must return a synthetic context when AUTH_DISABLED=True."""
import asyncio
from unittest.mock import MagicMock
from app.core.security import require_admin_auth
with patch("app.core.security.settings") as mock_settings:
mock_settings.AUTH_DISABLED = True
mock_req = MagicMock()
mock_req.cookies = {}
result = asyncio.get_event_loop().run_until_complete(
require_admin_auth(request=mock_req, api_key=None, bearer=None)
)
assert result["auth_type"] == "disabled"
def test_middleware_passes_all_requests_when_auth_disabled(self, client: TestClient):
"""The auth middleware must let every request through when AUTH_DISABLED=True."""
# The middleware does `from app.core.config import get_settings` inside dispatch,
# so we patch the canonical location used at call time.
with patch("app.core.config.get_settings") as mock_get_settings:
mock_cfg = MagicMock()
mock_cfg.AUTH_DISABLED = True
mock_get_settings.return_value = mock_cfg
# Even without a session cookie, the middleware lets the request through.
# The endpoint itself then handles auth (API key or 401), but it must
# never be a 302 redirect from the middleware.
res = client.get("/settings", follow_redirects=False)
assert res.status_code != 302