feat(gateway): 为52号接口增加客户端静态Token校验
请求头X-API-Token默认yonghe1999,可通过interface52_token覆盖;缺少或不匹配返回401/code1009,不进入派发队列。客户端Token不转发到worker,其他接口保持原样。 同步Token输入框、配置示例与调用文档。57项网关测试及临时网关连接真实worker的浏览器验证通过。
This commit is contained in:
@@ -15,6 +15,75 @@ from gateway import config, forward
|
||||
|
||||
gateway = importlib.import_module("gateway.app")
|
||||
PATH = "/api/v1/hairline/generate-52"
|
||||
CLIENT_HEADERS = {"X-API-Token": "yonghe1999"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("headers", [
|
||||
{}, {"X-API-Token": ""}, {"X-API-Token": "wrong"},
|
||||
{"X-API-Token": "yonghe1999 "}, {"X-Internal-Token": "yonghe1999"},
|
||||
])
|
||||
def test_invalid_client_token_is_rejected_before_dispatch(monkeypatch, headers):
|
||||
monkeypatch.setattr(config, "get_config", lambda: {})
|
||||
proxy = AsyncMock()
|
||||
monkeypatch.setattr(gateway, "_proxy", proxy)
|
||||
client = TestClient(gateway.app)
|
||||
try:
|
||||
response = client.post(PATH, headers=headers)
|
||||
assert response.status_code == 401
|
||||
body = response.json()
|
||||
assert body["code"] == 1009 and body["data"] is None
|
||||
assert body["request_id"].startswith("gw-")
|
||||
finally:
|
||||
client.close()
|
||||
proxy.assert_not_awaited()
|
||||
|
||||
|
||||
def test_custom_token_replaces_default_only_on_interface52(monkeypatch):
|
||||
monkeypatch.setattr(config, "get_config", lambda: {"interface52_token": "custom-client-token"})
|
||||
proxy = AsyncMock(return_value={"code": 0, "data": None})
|
||||
monkeypatch.setattr(gateway, "_proxy", proxy)
|
||||
client = TestClient(gateway.app)
|
||||
try:
|
||||
assert client.post(PATH, headers=CLIENT_HEADERS).status_code == 401
|
||||
assert client.post(PATH, headers={"X-API-Token": "custom-client-token"}).status_code == 200
|
||||
assert client.post("/api/v1/hairline/generate").status_code == 200
|
||||
finally:
|
||||
client.close()
|
||||
assert proxy.await_count == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token", ["", None, 123, []])
|
||||
def test_invalid_configured_token_cannot_disable_auth(monkeypatch, token):
|
||||
monkeypatch.setattr(config, "get_config", lambda: {"interface52_token": token})
|
||||
proxy = AsyncMock()
|
||||
monkeypatch.setattr(gateway, "_proxy", proxy)
|
||||
client = TestClient(gateway.app)
|
||||
try:
|
||||
assert client.post(PATH, headers=CLIENT_HEADERS).status_code == 401
|
||||
finally:
|
||||
client.close()
|
||||
proxy.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token", ["", None, 123, []])
|
||||
def test_startup_rejects_invalid_client_token_configuration(tmp_path, monkeypatch, token):
|
||||
import json
|
||||
path = tmp_path / "config.json"
|
||||
path.write_text(json.dumps({"interface52_token": token}))
|
||||
monkeypatch.setattr(config, "CONFIG_PATH", path)
|
||||
monkeypatch.setattr(config, "_config_cache", None)
|
||||
with pytest.raises(ValueError, match="interface52_token"):
|
||||
config.load_config()
|
||||
|
||||
|
||||
def test_existing_config_gets_default_client_token(tmp_path, monkeypatch):
|
||||
import json
|
||||
path = tmp_path / "config.json"
|
||||
path.write_text(json.dumps({"workers": ["http://worker"], "shared_password": "test-internal-token",
|
||||
"static_dir": str(tmp_path)}))
|
||||
monkeypatch.setattr(config, "CONFIG_PATH", path)
|
||||
monkeypatch.setattr(config, "_config_cache", None)
|
||||
assert config.load_config()["interface52_token"] == "yonghe1999"
|
||||
|
||||
|
||||
def image_bytes(fmt):
|
||||
@@ -46,11 +115,12 @@ def proxy_setup(tmp_path, monkeypatch):
|
||||
|
||||
@pytest.mark.parametrize("path", ["/api/v1/hairline/generate", PATH])
|
||||
def test_hairline_routes_forward_to_their_own_worker_path(monkeypatch, path):
|
||||
monkeypatch.setattr(config, "get_config", lambda: {})
|
||||
proxy = AsyncMock(return_value={"code": 0, "data": None})
|
||||
monkeypatch.setattr(gateway, "_proxy", proxy)
|
||||
client = TestClient(gateway.app, client=("203.0.113.9", 50000))
|
||||
try:
|
||||
assert client.post(path).json() == {"code": 0, "data": None}
|
||||
assert client.post(path, headers=CLIENT_HEADERS if path == PATH else {}).json() == {"code": 0, "data": None}
|
||||
finally:
|
||||
client.close()
|
||||
proxy.assert_awaited_once()
|
||||
@@ -93,6 +163,7 @@ def test_interface52_raw_forwarding_and_served_image_urls(proxy_setup, monkeypat
|
||||
client = TestClient(gateway.app, client=("203.0.113.9", 50000))
|
||||
try:
|
||||
result = client.post(PATH, content=raw, headers={
|
||||
**CLIENT_HEADERS,
|
||||
"Content-Type": source.headers["content-type"], "X-Internal-Token": "untrusted-client-token"})
|
||||
assert result.status_code == 200
|
||||
data = result.json()["data"]
|
||||
@@ -119,6 +190,7 @@ def test_interface52_raw_forwarding_and_served_image_urls(proxy_setup, monkeypat
|
||||
assert calls[0].headers["content-type"] == source.headers["content-type"]
|
||||
assert calls[0].headers["x-internal-token"] == cfg["shared_password"]
|
||||
assert calls[0].headers.get("x-forwarded-for") is None
|
||||
assert calls[0].headers.get("x-api-token") is None
|
||||
acquire.assert_awaited_once_with(cfg, 0)
|
||||
release.assert_called_once()
|
||||
release_worker.assert_awaited_once()
|
||||
@@ -132,7 +204,7 @@ def test_worker_ip_rejection_is_preserved(proxy_setup, monkeypatch):
|
||||
monkeypatch.setattr(forward, "get_client", lambda: upstream)
|
||||
client = TestClient(gateway.app)
|
||||
try:
|
||||
response = client.post(PATH, data={"gender": "male", "hair_style": "1"})
|
||||
response = client.post(PATH, headers=CLIENT_HEADERS, data={"gender": "male", "hair_style": "1"})
|
||||
assert response.status_code == 403 and response.json() == body
|
||||
finally:
|
||||
client.close()
|
||||
@@ -148,6 +220,7 @@ def test_public_interface52_page_has_no_internal_password():
|
||||
page = client.get("/static/test_interface52.html")
|
||||
assert page.status_code == 200
|
||||
assert PATH in page.text and "X-Internal-Token" not in page.text
|
||||
assert 'id="apiToken"' in page.text and "X-API-Token" in page.text
|
||||
assert "localhost:8187" not in page.text
|
||||
assert "test_interface52_compare.html" not in page.text
|
||||
assert client.get("/static/hairline_reference_box.js").status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user