mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
Merge pull request #253 from MervinPraison/claude/issue-251-20260728-000000
fix: make URL-token auth opt-in so env token no longer forces 401
This commit is contained in:
commit
557852b287
2 files changed
+91
-4
No files matched your search
@@ -2860,7 +2860,14 @@ def create_app(
|
||||
require_auth: bool = False,
|
||||
config_path: Optional[Path] = None,
|
||||
) -> Starlette:
|
||||
"""Create the Starlette application."""
|
||||
"""Create the Starlette application.
|
||||
|
||||
URL-token auth (``TokenQueryMiddleware``) is opt-in: it activates only
|
||||
when ``AIUI_REQUIRE_TOKEN`` is truthy, YAML ``auth.requireToken`` is set,
|
||||
or ``require_auth=True``. A bare ``GATEWAY_AUTH_TOKEN`` /
|
||||
``AIUI_URL_TOKEN`` env var (e.g. leaked into a developer shell after a
|
||||
gateway run) no longer forces 401 on standalone ``/api/*`` (#238, #251).
|
||||
"""
|
||||
from praisonaiui.auth import (
|
||||
AuthMiddleware,
|
||||
login_handler,
|
||||
@@ -2874,10 +2881,13 @@ def create_app(
|
||||
config = load_config_from_yaml(config_path)
|
||||
|
||||
# Extract auth settings from config
|
||||
_config_requires_token = False
|
||||
if config:
|
||||
auth_config = config.get("auth", {})
|
||||
if auth_config.get("requireAuth") or auth_config.get("require_auth"):
|
||||
require_auth = True
|
||||
if auth_config.get("requireToken") or auth_config.get("require_token"):
|
||||
_config_requires_token = True
|
||||
|
||||
middleware = [
|
||||
Middleware(
|
||||
@@ -2891,10 +2901,20 @@ def create_app(
|
||||
# Add optional AUTH_ENFORCE middleware (env var driven)
|
||||
middleware.append(Middleware(AuthEnforcementMiddleware))
|
||||
|
||||
# URL-token auth — opt-in via env. Enables query-param / cookie auth so
|
||||
# dashboards can be shared by URL (Jupyter model).
|
||||
# URL-token auth — opt-in. Enables query-param / cookie auth so dashboards
|
||||
# can be shared by URL (Jupyter model). The mere presence of a token env
|
||||
# var (e.g. GATEWAY_AUTH_TOKEN leaking into a developer shell after a
|
||||
# gateway run) must NOT force auth on standalone create_app() — that broke
|
||||
# ad-hoc TestClient scripts (#238, #251). Activation now requires an
|
||||
# explicit opt-in: AIUI_REQUIRE_TOKEN=1, YAML auth.requireToken, or the
|
||||
# require_auth flag.
|
||||
_tok = os.environ.get("AIUI_URL_TOKEN") or os.environ.get("GATEWAY_AUTH_TOKEN")
|
||||
if _tok:
|
||||
_token_auth_opt_in = (
|
||||
os.environ.get("AIUI_REQUIRE_TOKEN", "").lower() in ("true", "1", "yes")
|
||||
or _config_requires_token
|
||||
or require_auth
|
||||
)
|
||||
if _tok and _token_auth_opt_in:
|
||||
from praisonaiui.auth import TokenQueryMiddleware
|
||||
middleware.append(
|
||||
Middleware(TokenQueryMiddleware, expected_token=_tok)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Regression tests for opt-in token auth activation (#238, #251).
|
||||
|
||||
The mere presence of GATEWAY_AUTH_TOKEN / AIUI_URL_TOKEN in the environment
|
||||
(e.g. leaking into a developer shell after a gateway run) must not force 401
|
||||
on standalone create_app() + TestClient. Token auth activates only when
|
||||
explicitly opted in via AIUI_REQUIRE_TOKEN, YAML auth.requireToken, or
|
||||
require_auth=True.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from praisonaiui import server
|
||||
from praisonaiui.server import create_app
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_state():
|
||||
server.reset_state()
|
||||
yield
|
||||
server.reset_state()
|
||||
|
||||
|
||||
def test_env_token_alone_does_not_force_401(monkeypatch):
|
||||
monkeypatch.setenv("GATEWAY_AUTH_TOKEN", "test-secret-token")
|
||||
monkeypatch.delenv("AIUI_REQUIRE_TOKEN", raising=False)
|
||||
client = TestClient(create_app())
|
||||
assert client.get("/api/agents").status_code == 200
|
||||
|
||||
|
||||
def test_aiui_url_token_alone_does_not_force_401(monkeypatch):
|
||||
monkeypatch.setenv("AIUI_URL_TOKEN", "test-secret-token")
|
||||
monkeypatch.delenv("AIUI_REQUIRE_TOKEN", raising=False)
|
||||
client = TestClient(create_app())
|
||||
assert client.get("/api/agents").status_code == 200
|
||||
|
||||
|
||||
def test_require_token_env_opt_in_enforces_401(monkeypatch):
|
||||
monkeypatch.setenv("GATEWAY_AUTH_TOKEN", "test-secret-token")
|
||||
monkeypatch.setenv("AIUI_REQUIRE_TOKEN", "1")
|
||||
client = TestClient(create_app())
|
||||
assert client.get("/api/agents").status_code == 401
|
||||
|
||||
|
||||
def test_require_token_env_opt_in_allows_bearer(monkeypatch):
|
||||
monkeypatch.setenv("GATEWAY_AUTH_TOKEN", "test-secret-token")
|
||||
monkeypatch.setenv("AIUI_REQUIRE_TOKEN", "1")
|
||||
client = TestClient(create_app())
|
||||
resp = client.get(
|
||||
"/api/agents",
|
||||
headers={"Authorization": "Bearer test-secret-token"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
def test_config_require_token_opt_in_enforces_401(monkeypatch):
|
||||
monkeypatch.setenv("AIUI_URL_TOKEN", "test-secret-token")
|
||||
monkeypatch.delenv("AIUI_REQUIRE_TOKEN", raising=False)
|
||||
client = TestClient(create_app(config={"auth": {"requireToken": True}}))
|
||||
assert client.get("/api/agents").status_code == 401
|
||||
|
||||
|
||||
def test_require_auth_opt_in_enforces_401(monkeypatch):
|
||||
monkeypatch.setenv("AIUI_URL_TOKEN", "test-secret-token")
|
||||
monkeypatch.delenv("AIUI_REQUIRE_TOKEN", raising=False)
|
||||
client = TestClient(create_app(require_auth=True))
|
||||
assert client.get("/api/agents").status_code == 401
|
||||
Reference in new issue
Block a user