diff --git a/faucet_service/faucet_service.py b/faucet_service/faucet_service.py index 9b41f18a0..7fe15083e 100644 --- a/faucet_service/faucet_service.py +++ b/faucet_service/faucet_service.py @@ -1595,7 +1595,7 @@ def get_template_vars(config: Dict) -> Dict: submitBtn.disabled = true; submitBtn.textContent = 'Processing...'; result.className = 'result'; - result.innerHTML = ''; + result.textContent = ''; const wallet = walletInput.value.trim(); @@ -1611,7 +1611,7 @@ def get_template_vars(config: Dict) -> Dict: result.className = 'result show ' + (data.ok ? 'success' : 'error'); if (data.ok) { - result.innerHTML = ''; + result.textContent = ''; const strong = document.createElement('strong'); strong.textContent = '✅ Success!'; result.appendChild(strong); @@ -1627,7 +1627,7 @@ def get_template_vars(config: Dict) -> Dict: walletInput.value = ''; loadStats(); } else { - result.innerHTML = ''; + result.textContent = ''; const strong = document.createElement('strong'); strong.textContent = `❌ ${data.error}`; result.appendChild(strong); @@ -1640,7 +1640,7 @@ def get_template_vars(config: Dict) -> Dict: } } catch (err) { result.className = 'result show error'; - result.innerHTML = ''; + result.textContent = ''; const strong = document.createElement('strong'); strong.textContent = '❌ Error: '; result.appendChild(strong); diff --git a/node/beacon_api.py b/node/beacon_api.py index 95c0d0920..7c2f3fb41 100644 --- a/node/beacon_api.py +++ b/node/beacon_api.py @@ -1368,9 +1368,11 @@ def chat(): @beacon_api.route('/relay/discover', methods=['GET']) def relay_discover(): - """Discover relay agents (for 3D visualization).""" - # In production, query the relay registry - # For demo, return empty array + """Discover relay agents (for 3D visualization). + + .. deprecated:: + Use /beacon/atlas instead. This endpoint returns an empty array. + """ return jsonify([]) diff --git a/site/beacon/advertise.js b/site/beacon/advertise.js index 4f7b1d964..1e4910476 100644 --- a/site/beacon/advertise.js +++ b/site/beacon/advertise.js @@ -46,7 +46,7 @@ const LISTING_TIERS = [ benefits: [ 'Your agent appears as a permanent node on the 3D Atlas', 'Custom city placement based on your agent capabilities', - 'Listed in /relay/discover API for cross-agent collaboration', + 'Listed in /beacon/atlas for cross-agent collaboration', 'Reputation score tracking and bounty eligibility', 'Featured in "Integrated Partners" section', 'Access to Beacon contract and mayday systems', diff --git a/site/beacon/data.js b/site/beacon/data.js index e8da82227..a3815a0c2 100644 --- a/site/beacon/data.js +++ b/site/beacon/data.js @@ -584,9 +584,10 @@ export async function fetchAllAgents(apiBase) { // --- 2. Beacon relay agents --- try { - const resp = await fetch(`${apiBase}/relay/discover`); + const resp = await fetch(`${apiBase}/beacon/atlas`); if (resp.ok) { - const relays = await resp.json(); + const atlasData = await resp.json(); + const relays = atlasData.agents || atlasData; for (const ra of relays) { const canonicalId = resolveFromAliasMap(aliasMap, ra.name, ra.model_id, ra.agent_id); if (canonicalId && agentMap.has(canonicalId)) { diff --git a/tests/test_vintage_ai_rustchain_client.py b/tests/test_vintage_ai_rustchain_client.py index 2e24128a3..1fea0d27b 100644 --- a/tests/test_vintage_ai_rustchain_client.py +++ b/tests/test_vintage_ai_rustchain_client.py @@ -20,8 +20,8 @@ def test_get_miners_accepts_envelope_payloads(monkeypatch): monkeypatch.setattr( client, - "_get", - lambda endpoint: { + "_get_public", + lambda endpoint, params=None: { "items": [ {"miner": "alice", "hardware_type": "PowerPC G4"}, {"miner": "bob", "hardware_type": "x86-64"}, @@ -40,7 +40,7 @@ def test_get_miners_returns_empty_list_for_unexpected_payload(monkeypatch): module = load_client_module() client = module.RustChainClient(base_url="https://node.example") - monkeypatch.setattr(client, "_get", lambda endpoint: {"pagination": {"total": 0}}) + monkeypatch.setattr(client, "_get_public", lambda endpoint, params=None: {"pagination": {"total": 0}}) assert client.get_miners() == [] @@ -138,3 +138,161 @@ def read(self): return body return FakeResp() + + +# --- Issue #6624: _request_public must NOT include admin key headers --- + + +def test_request_public_uses_public_headers(monkeypatch): + """_request_public must use _get_public_headers (no admin key).""" + module = load_client_module() + client = module.RustChainClient( + base_url="https://node.example", admin_key="secret-admin-key-123" + ) + + captured_headers = {} + + class FakeResp: + def __enter__(self): + return self + def __exit__(self, *args): + return False + def read(self): + return b'{"ok": true}' + + def fake_urlopen(req, **kwargs): + captured_headers.update(req.headers) + return FakeResp() + + monkeypatch.setattr("urllib.request.urlopen", fake_urlopen) + + result = client._request_public("GET", "/health") + assert result == {"ok": True} + assert "X-Admin-Key" not in captured_headers, ( + f"_request_public must not send admin key; got headers: {captured_headers}" + ) + assert captured_headers.get("Accept") == "application/json" + + +def test_request_with_admin_key_sends_header(monkeypatch): + """_request (authenticated) must include X-Admin-Key when configured.""" + module = load_client_module() + client = module.RustChainClient( + base_url="https://node.example", admin_key="secret-admin-key-123" + ) + + captured_headers = {} + + class FakeResp: + def __enter__(self): + return self + def __exit__(self, *args): + return False + def read(self): + return b'{"ok": true}' + + def fake_urlopen(req, **kwargs): + captured_headers.update(req.headers) + return FakeResp() + + monkeypatch.setattr("urllib.request.urlopen", fake_urlopen) + + result = client._request("POST", "/api/submit", data={"key": "val"}) + assert result == {"ok": True} + headers_lower = {k.lower(): v for k, v in captured_headers.items()} + assert headers_lower.get("x-admin-key") == "secret-admin-key-123" + + +def test_read_methods_use_public_no_admin_key(monkeypatch): + """Read methods (health, get_epoch, get_miners, etc.) must not send admin key.""" + module = load_client_module() + client = module.RustChainClient( + base_url="https://node.example", admin_key="secret-admin-key-123" + ) + + captured_headers = {} + + class FakeResp: + def __enter__(self): + return self + def __exit__(self, *args): + return False + def read(self): + return b'{"result": "ok"}' + + def fake_urlopen(req, **kwargs): + captured_headers.clear() + captured_headers.update(req.headers) + return FakeResp() + + monkeypatch.setattr("urllib.request.urlopen", fake_urlopen) + + read_endpoints = [ + lambda: client.health(), + lambda: client.get_epoch(), + lambda: client.get_wallet_balance("miner_123"), + lambda: client.get_wallet_history("miner_123"), + lambda: client.get_stats(), + lambda: client.get_hall_of_fame(), + lambda: client.get_miner_eligibility("miner_123"), + ] + + for call in read_endpoints: + call() + assert "X-Admin-Key" not in captured_headers, ( + f"Read method must not send admin key; got: {captured_headers}" + ) + + +def test_admin_key_not_set_no_header_sent(monkeypatch): + """When admin_key is None, no X-Admin-Key header is sent even on write requests.""" + module = load_client_module() + client = module.RustChainClient(base_url="https://node.example") + + captured_headers = {} + + class FakeResp: + def __enter__(self): + return self + def __exit__(self, *args): + return False + def read(self): + return b'{"ok": true}' + + def fake_urlopen(req, **kwargs): + captured_headers.update(req.headers) + return FakeResp() + + monkeypatch.setattr("urllib.request.urlopen", fake_urlopen) + + client._request("POST", "/api/submit", data={"key": "val"}) + assert "X-Admin-Key" not in captured_headers + + +def test_get_public_headers_never_includes_admin_key(): + """_get_public_headers() must never include admin key regardless of config.""" + module = load_client_module() + client = module.RustChainClient( + base_url="https://node.example", admin_key="super-secret" + ) + headers = client._get_public_headers() + assert "X-Admin-Key" not in headers + assert "Accept" in headers + + +def test_get_headers_includes_admin_key_when_set(): + """_get_headers() includes admin key when configured.""" + module = load_client_module() + client = module.RustChainClient( + base_url="https://node.example", admin_key="my-admin-key" + ) + headers = client._get_headers() + assert headers["X-Admin-Key"] == "my-admin-key" + + +def test_get_headers_no_admin_key_when_unset(): + """_get_headers() omits admin key when not configured.""" + module = load_client_module() + client = module.RustChainClient(base_url="https://node.example") + headers = client._get_headers() + assert "X-Admin-Key" not in headers diff --git a/tools/bcos-badge-generator/index.html b/tools/bcos-badge-generator/index.html index f11d8bb0e..1b31f6a8f 100644 --- a/tools/bcos-badge-generator/index.html +++ b/tools/bcos-badge-generator/index.html @@ -732,14 +732,14 @@