mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Improvements for DevUI (#5840)
This commit is contained in:
committed by
GitHub
Unverified
parent
ae666a4887
commit
0e12640c70
@@ -34,6 +34,12 @@ devui ./agents
|
||||
devui --entities my_agent.py
|
||||
```
|
||||
|
||||
## Security Posture
|
||||
|
||||
DevUI is a development-only sample app, not a production hosting surface. Authentication is enabled by default.
|
||||
Unauthenticated mode is allowed only on `localhost` / `127.0.0.1`; `0.0.0.0`, LAN IPs, and hostnames require
|
||||
`DEVUI_AUTH_TOKEN` or `--auth-token`.
|
||||
|
||||
## Import Path
|
||||
|
||||
```python
|
||||
|
||||
@@ -47,6 +47,9 @@ devui ./agents --port 8080
|
||||
# → API: http://localhost:8080/v1/*
|
||||
```
|
||||
|
||||
DevUI is auth-enabled by default. Localhost starts with a generated development token logged at startup; pass it as
|
||||
`Authorization: Bearer <token>` for direct API calls.
|
||||
|
||||
When DevUI starts with no discovered entities, it displays a **sample entity gallery** with curated examples from the Agent Framework repository. You can download these samples, review them, and run them locally to get started quickly.
|
||||
|
||||
## Using MCP Tools
|
||||
@@ -137,12 +140,14 @@ For convenience, DevUI provides an OpenAI Responses backend API. This means you
|
||||
```bash
|
||||
# Simple - use your entity name as the entity_id in metadata
|
||||
curl -X POST http://localhost:8080/v1/responses \
|
||||
-H "Authorization: Bearer <devui-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @- << 'EOF'
|
||||
{
|
||||
"metadata": {"entity_id": "weather_agent"},
|
||||
"input": "Hello world"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
Or use the OpenAI Python SDK:
|
||||
@@ -152,7 +157,7 @@ from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
base_url="http://localhost:8080/v1",
|
||||
api_key="not-needed" # API key not required for local DevUI
|
||||
api_key="<devui-token>"
|
||||
)
|
||||
|
||||
response = client.responses.create(
|
||||
@@ -201,6 +206,7 @@ DevUI provides an **OpenAI Proxy** feature for testing OpenAI models directly th
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/v1/responses \
|
||||
-H "Authorization: Bearer <devui-token>" \
|
||||
-H "X-Proxy-Backend: openai" \
|
||||
-d '{"model": "gpt-4.1-mini", "input": "Hello"}'
|
||||
```
|
||||
@@ -214,14 +220,14 @@ devui [directory] [options]
|
||||
|
||||
Options:
|
||||
--port, -p Port (default: 8080)
|
||||
--host Host (default: 127.0.0.1)
|
||||
--host Host (default: 127.0.0.1; non-loopback hosts require auth)
|
||||
--headless API only, no UI
|
||||
--no-open Don't automatically open browser
|
||||
--instrumentation Enable OpenTelemetry instrumentation
|
||||
--reload Enable auto-reload
|
||||
--mode developer|user (default: developer)
|
||||
--auth Enable Bearer token authentication
|
||||
--auth-token Custom authentication token
|
||||
--no-auth Disable auth for loopback-only local development
|
||||
--auth-token Custom authentication token (required for non-loopback hosts unless DEVUI_AUTH_TOKEN is set)
|
||||
```
|
||||
|
||||
### UI Modes
|
||||
@@ -233,8 +239,8 @@ Options:
|
||||
# Development
|
||||
devui ./agents
|
||||
|
||||
# Production (user-facing)
|
||||
devui ./agents --mode user --auth
|
||||
# Local-only no-auth development
|
||||
devui ./agents --no-auth
|
||||
```
|
||||
|
||||
## Key Endpoints
|
||||
@@ -336,28 +342,39 @@ These custom extensions are clearly namespaced and can be safely ignored by stan
|
||||
|
||||
## Security
|
||||
|
||||
DevUI is designed as a **sample application for local development** and should not be exposed to untrusted networks without proper authentication.
|
||||
DevUI is designed as a **sample application for local development** and is not intended for production use. For
|
||||
production, or for features beyond this sample app, build a custom interface and API server using the Agent Framework SDK.
|
||||
|
||||
**For production deployments:**
|
||||
Auth is enabled by default. Unauthenticated mode is allowed only when DevUI is bound to `localhost` or `127.0.0.1`.
|
||||
Network-reachable binds such as `0.0.0.0`, LAN IPs, and hostnames require Bearer token authentication with an explicit
|
||||
token.
|
||||
|
||||
**For shared development hosts:**
|
||||
|
||||
```bash
|
||||
# User mode with authentication (recommended)
|
||||
devui ./agents --mode user --auth --host 0.0.0.0
|
||||
# Set a token explicitly before binding beyond loopback
|
||||
DEVUI_AUTH_TOKEN="<secure-dev-token>" devui ./agents --mode user --host 0.0.0.0
|
||||
|
||||
# Or pass the token on the command line
|
||||
devui ./agents --mode user --host 0.0.0.0 --auth-token "<secure-dev-token>"
|
||||
```
|
||||
|
||||
This restricts developer APIs (reload, deployment, entity details) and requires Bearer token authentication.
|
||||
Do not use `--no-auth` with `0.0.0.0`, LAN IPs, or hostnames. That configuration fails closed before startup.
|
||||
|
||||
**Security features:**
|
||||
|
||||
- User mode restricts developer-facing APIs
|
||||
- Optional Bearer token authentication via `--auth`
|
||||
- Bearer token authentication is enabled by default
|
||||
- Unauthenticated mode is loopback-only (`localhost` / `127.0.0.1`)
|
||||
- Non-loopback binds require `DEVUI_AUTH_TOKEN` or `--auth-token`
|
||||
- Only loads entities from local directories or in-memory registration
|
||||
- No remote code execution capabilities
|
||||
- Binds to localhost (127.0.0.1) by default
|
||||
|
||||
**Best practices:**
|
||||
|
||||
- Use `--mode user --auth` for any deployment exposed to end users
|
||||
- Do not use DevUI as a production deployment surface
|
||||
- Use `--mode user` plus `DEVUI_AUTH_TOKEN` or `--auth-token` for shared development hosts
|
||||
- Review all agent/workflow code before running
|
||||
- Only load entities from trusted sources
|
||||
- Use `.env` files for sensitive credentials (never commit them)
|
||||
|
||||
@@ -126,29 +126,6 @@ def serve(
|
||||
if not isinstance(port, int) or not (1 <= port <= 65535):
|
||||
raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535")
|
||||
|
||||
# Security check: warn loudly when network-exposed without authentication.
|
||||
if host not in ("127.0.0.1", "localhost") and not auth_enabled:
|
||||
logger.warning("WARNING: Exposing DevUI to the network with --no-auth.")
|
||||
logger.warning("Anyone on your network can read agent metadata and trigger requests.")
|
||||
logger.warning("Drop --no-auth and DevUI will require Bearer tokens.")
|
||||
|
||||
# Refuse to auto-generate a token for network-exposed binds. Auto-generated tokens
|
||||
# are fine for localhost convenience; for anything else, require an explicit token.
|
||||
if auth_enabled and not auth_token:
|
||||
import os
|
||||
|
||||
env_token = os.environ.get("DEVUI_AUTH_TOKEN")
|
||||
if not env_token:
|
||||
is_production = (
|
||||
host not in ("127.0.0.1", "localhost")
|
||||
or os.environ.get("CI") == "true"
|
||||
or os.environ.get("KUBERNETES_SERVICE_HOST")
|
||||
)
|
||||
if is_production:
|
||||
logger.error("Authentication required but no token provided.")
|
||||
logger.error("Set DEVUI_AUTH_TOKEN env var or pass auth_token='...' to serve().")
|
||||
raise ValueError("DEVUI_AUTH_TOKEN required when host is not localhost")
|
||||
|
||||
# Enable instrumentation if requested
|
||||
if instrumentation_enabled:
|
||||
from agent_framework.observability import enable_instrumentation
|
||||
|
||||
@@ -81,13 +81,15 @@ Examples:
|
||||
parser.add_argument(
|
||||
"--no-auth",
|
||||
action="store_true",
|
||||
help="Disable Bearer token authentication. DevUI is auth-enabled by default; use this to opt out.",
|
||||
help=(
|
||||
"Disable Bearer token authentication for loopback-only local development. Non-loopback hosts require auth."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--auth-token",
|
||||
type=str,
|
||||
help="Custom Bearer token. Auto-generated and logged at startup when omitted.",
|
||||
help="Custom Bearer token. Required for non-loopback hosts when DEVUI_AUTH_TOKEN is not set.",
|
||||
)
|
||||
|
||||
parser.add_argument("--version", action="version", version=f"Agent Framework DevUI {get_version()}")
|
||||
|
||||
@@ -89,7 +89,7 @@ class DevServer:
|
||||
mode: Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors)
|
||||
auth_enabled: Whether to require Bearer token auth on /v1/* endpoints. Defaults to True.
|
||||
auth_token: Bearer token. If None and auth_enabled, falls back to the DEVUI_AUTH_TOKEN
|
||||
environment variable, then to an auto-generated token (logged at startup).
|
||||
environment variable. Loopback binds may use an auto-generated token logged at startup.
|
||||
"""
|
||||
self.entities_dir = entities_dir
|
||||
self.port = port
|
||||
@@ -106,7 +106,7 @@ class DevServer:
|
||||
self.ui_enabled = ui_enabled
|
||||
self.mode = mode
|
||||
self.auth_enabled = auth_enabled
|
||||
self.auth_token = self._resolve_auth_token(auth_enabled, auth_token)
|
||||
self.auth_token = self._resolve_auth_token(host, auth_enabled, auth_token)
|
||||
self.executor: AgentFrameworkExecutor | None = None
|
||||
self.openai_executor: OpenAIExecutor | None = None
|
||||
self.deployment_manager = DeploymentManager()
|
||||
@@ -118,8 +118,14 @@ class DevServer:
|
||||
"""Set in-memory entities to register on startup."""
|
||||
self._pending_entities = entities
|
||||
|
||||
_AUTH_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost"})
|
||||
_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "[::1]", "::1"})
|
||||
|
||||
@classmethod
|
||||
def _is_auth_loopback_host(cls, host: str) -> bool:
|
||||
"""Return True when unauthenticated DevUI may be limited to local loopback."""
|
||||
return host.lower() in cls._AUTH_LOOPBACK_HOSTS
|
||||
|
||||
def _loopback_allowed_hosts(self) -> frozenset[str] | None:
|
||||
"""Return the Host-header allowlist when bound to a loopback interface, else None.
|
||||
|
||||
@@ -131,16 +137,25 @@ class DevServer:
|
||||
return None
|
||||
return self._LOOPBACK_HOSTS
|
||||
|
||||
@staticmethod
|
||||
def _resolve_auth_token(auth_enabled: bool, auth_token: str | None) -> str | None:
|
||||
@classmethod
|
||||
def _resolve_auth_token(cls, host: str, auth_enabled: bool, auth_token: str | None) -> str | None:
|
||||
"""Resolve the active Bearer token. Returns None when auth is disabled."""
|
||||
is_loopback = cls._is_auth_loopback_host(host)
|
||||
if not auth_enabled:
|
||||
if not is_loopback:
|
||||
raise ValueError(
|
||||
"DevUI authentication cannot be disabled for non-loopback hosts. "
|
||||
"Bind to 127.0.0.1/localhost for no-auth local development, or enable auth and provide "
|
||||
"DEVUI_AUTH_TOKEN or auth_token for network-reachable binds."
|
||||
)
|
||||
return None
|
||||
if auth_token:
|
||||
return auth_token
|
||||
env_token = os.getenv("DEVUI_AUTH_TOKEN")
|
||||
if env_token:
|
||||
return env_token
|
||||
if not is_loopback:
|
||||
raise ValueError("DEVUI_AUTH_TOKEN or auth_token is required when DevUI is bound to a non-loopback host.")
|
||||
generated = secrets.token_urlsafe(32)
|
||||
logger.info("=" * 70)
|
||||
logger.info("DevUI authentication enabled with auto-generated token:")
|
||||
|
||||
@@ -60,6 +60,9 @@ devui
|
||||
|
||||
This launches the UI with all example agents/workflows at http://localhost:8080
|
||||
|
||||
DevUI is auth-enabled by default. Copy the generated token from startup logs and pass it as
|
||||
`Authorization: Bearer <token>` for direct API calls. Use `--no-auth` only for loopback-only local testing.
|
||||
|
||||
## 5. What You'll See
|
||||
|
||||
- A web interface for testing agents interactively
|
||||
@@ -74,6 +77,7 @@ You can also test via API calls:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/v1/responses \
|
||||
-H "Authorization: Bearer <devui-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "weather_agent",
|
||||
@@ -86,6 +90,7 @@ curl -X POST http://localhost:8080/v1/responses \
|
||||
```bash
|
||||
# Create a conversation
|
||||
curl -X POST http://localhost:8080/v1/conversations \
|
||||
-H "Authorization: Bearer <devui-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"metadata": {"agent_id": "weather_agent"}}'
|
||||
|
||||
@@ -93,6 +98,7 @@ curl -X POST http://localhost:8080/v1/conversations \
|
||||
|
||||
# Use conversation ID in requests
|
||||
curl -X POST http://localhost:8080/v1/responses \
|
||||
-H "Authorization: Bearer <devui-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "weather_agent",
|
||||
@@ -102,6 +108,7 @@ curl -X POST http://localhost:8080/v1/responses \
|
||||
|
||||
# Continue the conversation
|
||||
curl -X POST http://localhost:8080/v1/responses \
|
||||
-H "Authorization: Bearer <devui-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "weather_agent",
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from conftest import MockAgent
|
||||
@@ -492,7 +494,7 @@ def test_devserver_requires_auth_by_default(monkeypatch):
|
||||
|
||||
|
||||
def test_devserver_auth_can_be_explicitly_disabled(monkeypatch):
|
||||
"""Callers can opt out of auth with auth_enabled=False (escape hatch for tests / trusted hosts)."""
|
||||
"""Callers can opt out of auth on loopback (escape hatch for tests / trusted local hosts)."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
server = _server_with_mock_agent(auth_enabled=False)
|
||||
@@ -504,6 +506,106 @@ def test_devserver_auth_can_be_explicitly_disabled(monkeypatch):
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_devserver_rejects_non_loopback_no_auth(monkeypatch):
|
||||
"""Non-loopback binds must not be network-reachable without authentication."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
with pytest.raises(ValueError, match="authentication cannot be disabled"):
|
||||
DevServer(host="0.0.0.0", auth_enabled=False)
|
||||
|
||||
with pytest.raises(ValueError, match="authentication cannot be disabled"):
|
||||
DevServer(host="devui.example", auth_enabled=False)
|
||||
|
||||
|
||||
def test_devserver_rejects_non_loopback_without_explicit_token(monkeypatch):
|
||||
"""Network-reachable auth requires an operator-provided token, not a generated token."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
with pytest.raises(ValueError, match="DEVUI_AUTH_TOKEN or auth_token"):
|
||||
DevServer(host="0.0.0.0")
|
||||
|
||||
|
||||
def test_devserver_allows_non_loopback_with_explicit_token(monkeypatch):
|
||||
"""A network-reachable bind is allowed when auth has an explicit token."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
server = DevServer(host="0.0.0.0", auth_token="s3cret")
|
||||
|
||||
assert server.auth_enabled is True
|
||||
assert server.auth_token == "s3cret"
|
||||
|
||||
|
||||
def test_devserver_allows_non_loopback_with_env_token(monkeypatch):
|
||||
"""A network-reachable bind is allowed when auth uses DEVUI_AUTH_TOKEN."""
|
||||
monkeypatch.setenv("DEVUI_AUTH_TOKEN", "env-s3cret")
|
||||
|
||||
server = DevServer(host="0.0.0.0")
|
||||
|
||||
assert server.auth_enabled is True
|
||||
assert server.auth_token == "env-s3cret"
|
||||
|
||||
|
||||
def test_devserver_allows_loopback_no_auth(monkeypatch):
|
||||
"""Unauthenticated DevUI remains available for local-only development and tests."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
for host in ("127.0.0.1", "localhost"):
|
||||
server = DevServer(host=host, auth_enabled=False)
|
||||
assert server.auth_enabled is False
|
||||
assert server.auth_token is None
|
||||
|
||||
|
||||
def test_devserver_loopback_auth_auto_generates_token(monkeypatch):
|
||||
"""Loopback auth-enabled usage may still use a generated development token."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
server = DevServer(host="127.0.0.1")
|
||||
|
||||
assert server.auth_enabled is True
|
||||
assert server.auth_token
|
||||
|
||||
|
||||
def test_serve_rejects_non_loopback_no_auth(monkeypatch):
|
||||
"""The public serve() helper must inherit the DevServer network-auth invariant."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
with pytest.raises(ValueError, match="authentication cannot be disabled"):
|
||||
agent_framework_devui.serve(entities=[], host="0.0.0.0", auth_enabled=False, ui_enabled=False)
|
||||
|
||||
|
||||
def test_serve_rejects_non_loopback_without_explicit_token(monkeypatch):
|
||||
"""serve() must not maintain a weaker generated-token path for network binds."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
with pytest.raises(ValueError, match="DEVUI_AUTH_TOKEN or auth_token"):
|
||||
agent_framework_devui.serve(entities=[], host="0.0.0.0", ui_enabled=False)
|
||||
|
||||
|
||||
def test_serve_allows_non_loopback_with_explicit_token(monkeypatch):
|
||||
"""serve() accepts a network bind when an explicit token is provided."""
|
||||
import uvicorn
|
||||
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
run_args = {}
|
||||
|
||||
def fake_run(_app, *, host, port, **_kwargs):
|
||||
run_args["host"] = host
|
||||
run_args["port"] = port
|
||||
|
||||
monkeypatch.setattr(uvicorn, "run", fake_run)
|
||||
|
||||
agent_framework_devui.serve(
|
||||
entities=[],
|
||||
host="0.0.0.0",
|
||||
port=9090,
|
||||
auth_token="s3cret",
|
||||
auto_open=False,
|
||||
ui_enabled=False,
|
||||
)
|
||||
|
||||
assert run_args == {"host": "0.0.0.0", "port": 9090}
|
||||
|
||||
|
||||
def test_devserver_accepts_request_with_valid_bearer_token(monkeypatch):
|
||||
"""When auth is on, supplying the configured Bearer token grants access."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
@@ -567,8 +669,8 @@ def test_serve_defaults_to_auth_enabled():
|
||||
)
|
||||
|
||||
|
||||
def test_cli_enables_auth_by_default_and_supports_no_auth_optout():
|
||||
"""`devui ./agents` must produce auth-enabled config; `--no-auth` is the explicit escape hatch."""
|
||||
def test_cli_enables_auth_by_default_and_supports_loopback_no_auth_optout():
|
||||
"""`devui ./agents` must produce auth-enabled config; `--no-auth` is the loopback-only escape hatch."""
|
||||
from agent_framework_devui._cli import create_cli_parser
|
||||
|
||||
parser = create_cli_parser()
|
||||
@@ -578,3 +680,76 @@ def test_cli_enables_auth_by_default_and_supports_no_auth_optout():
|
||||
|
||||
optout_args = parser.parse_args(["--no-auth"])
|
||||
assert optout_args.no_auth is True
|
||||
|
||||
help_text = parser.format_help()
|
||||
assert "loopback-only" in help_text
|
||||
assert "Non-loopback hosts require auth" in help_text
|
||||
|
||||
|
||||
def _run_cli_with_fake_uvicorn(monkeypatch, tmp_path: Path, *args: str) -> dict[str, Any]:
|
||||
"""Run the DevUI CLI without binding a socket."""
|
||||
import uvicorn
|
||||
|
||||
from agent_framework_devui import _cli
|
||||
|
||||
run_args: dict[str, Any] = {}
|
||||
|
||||
def fake_run(_app, *, host, port, **_kwargs):
|
||||
run_args["host"] = host
|
||||
run_args["port"] = port
|
||||
|
||||
monkeypatch.setattr(uvicorn, "run", fake_run)
|
||||
monkeypatch.setattr(sys, "argv", ["devui", str(tmp_path), "--no-open", "--headless", *args])
|
||||
|
||||
_cli.main()
|
||||
|
||||
return run_args
|
||||
|
||||
|
||||
def test_cli_allows_loopback_no_auth_without_binding_socket(monkeypatch, tmp_path):
|
||||
"""`devui --no-auth` remains valid on the default loopback host."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
run_args = _run_cli_with_fake_uvicorn(monkeypatch, tmp_path, "--no-auth")
|
||||
|
||||
assert run_args == {"host": "127.0.0.1", "port": 8080}
|
||||
|
||||
|
||||
def test_cli_rejects_non_loopback_no_auth_before_binding_socket(monkeypatch, tmp_path, capsys):
|
||||
"""`devui --host 0.0.0.0 --no-auth` must fail through shared server validation."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
_run_cli_with_fake_uvicorn(monkeypatch, tmp_path, "--host", "0.0.0.0", "--no-auth")
|
||||
|
||||
assert exc_info.value.code == 1
|
||||
assert "authentication cannot be disabled" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_cli_rejects_non_loopback_without_explicit_token_before_binding_socket(monkeypatch, tmp_path, capsys):
|
||||
"""`devui --host 0.0.0.0` must fail when neither --auth-token nor DEVUI_AUTH_TOKEN is set."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
_run_cli_with_fake_uvicorn(monkeypatch, tmp_path, "--host", "0.0.0.0")
|
||||
|
||||
assert exc_info.value.code == 1
|
||||
assert "DEVUI_AUTH_TOKEN or auth_token" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_cli_allows_non_loopback_with_auth_token_without_binding_socket(monkeypatch, tmp_path):
|
||||
"""`devui --host 0.0.0.0 --auth-token ...` starts with token auth enabled."""
|
||||
monkeypatch.delenv("DEVUI_AUTH_TOKEN", raising=False)
|
||||
|
||||
run_args = _run_cli_with_fake_uvicorn(monkeypatch, tmp_path, "--host", "0.0.0.0", "--auth-token", "s3cret")
|
||||
|
||||
assert run_args == {"host": "0.0.0.0", "port": 8080}
|
||||
|
||||
|
||||
def test_cli_allows_non_loopback_with_env_token_without_binding_socket(monkeypatch, tmp_path):
|
||||
"""`DEVUI_AUTH_TOKEN=... devui --host 0.0.0.0` starts with token auth enabled."""
|
||||
monkeypatch.setenv("DEVUI_AUTH_TOKEN", "env-s3cret")
|
||||
|
||||
run_args = _run_cli_with_fake_uvicorn(monkeypatch, tmp_path, "--host", "0.0.0.0")
|
||||
|
||||
assert run_args == {"host": "0.0.0.0", "port": 8080}
|
||||
|
||||
Reference in New Issue
Block a user