stage-exp-b explore: public routes + CORS on existing public endpoints
This commit is contained in:
@@ -34,6 +34,20 @@ _PUBLIC_PEERS_TTL = 60.0
|
||||
_PUBLIC_NETWORK_CACHE: Dict[str, Any] = {"ts": 0.0, "payload": None}
|
||||
_PUBLIC_NETWORK_TTL = 60.0
|
||||
|
||||
# Explore-view cache. The builder fans out to trusted peers' explore feeds
|
||||
# for the distance-2 snapshot, so a polled hit must NEVER trigger that walk.
|
||||
_EXPLORE_CACHE: Dict[str, Any] = {"ts": 0.0, "payload": None, "domain": None}
|
||||
_EXPLORE_TTL = 60.0
|
||||
|
||||
|
||||
# Headers we slap on every public endpoint so other psyc nodes' explore
|
||||
# pages can fetch them cross-origin from the browser.
|
||||
_CORS_HEADERS: Dict[str, str] = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
}
|
||||
|
||||
|
||||
def _admin_ok(request: Request) -> bool:
|
||||
return bool(request.session.get("admin_ok"))
|
||||
@@ -63,6 +77,30 @@ def _cached_public_network() -> Dict[str, Any]:
|
||||
return _PUBLIC_NETWORK_CACHE["payload"]
|
||||
|
||||
|
||||
def _cached_explore(domain: Optional[str]) -> Dict[str, Any]:
|
||||
"""Cached explore payload. Re-uses the cache when the host domain matches.
|
||||
|
||||
Domain is recorded into the payload's `node.domain` field, so a fresh
|
||||
cache slot per host avoids serving the wrong reflected name.
|
||||
"""
|
||||
now = time.time()
|
||||
cached_domain = _EXPLORE_CACHE.get("domain")
|
||||
if (
|
||||
_EXPLORE_CACHE["payload"] is None
|
||||
or (now - _EXPLORE_CACHE["ts"]) > _EXPLORE_TTL
|
||||
or cached_domain != domain
|
||||
):
|
||||
_EXPLORE_CACHE["payload"] = network_view.build_explore_view(node_domain=domain)
|
||||
_EXPLORE_CACHE["ts"] = now
|
||||
_EXPLORE_CACHE["domain"] = domain
|
||||
return _EXPLORE_CACHE["payload"]
|
||||
|
||||
|
||||
def _public_json(payload: Any) -> JSONResponse:
|
||||
"""JSONResponse with the public-CORS header set."""
|
||||
return JSONResponse(payload, headers=_CORS_HEADERS)
|
||||
|
||||
|
||||
def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None:
|
||||
"""Mount all federation routes onto `app`."""
|
||||
|
||||
@@ -180,20 +218,25 @@ def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None:
|
||||
|
||||
@app.get("/federation/info")
|
||||
def federation_info() -> JSONResponse:
|
||||
return JSONResponse({
|
||||
return _public_json({
|
||||
"fingerprint": federation.node_fingerprint(),
|
||||
"version": federation.FEED_VERSION,
|
||||
"feed": federation.FEED_PATH,
|
||||
"key": "/federation/key",
|
||||
"explore": "/federation/explore",
|
||||
})
|
||||
|
||||
@app.get("/federation/key", response_class=PlainTextResponse)
|
||||
def federation_key() -> PlainTextResponse:
|
||||
return PlainTextResponse(federation.public_key_pem(), media_type="text/plain")
|
||||
return PlainTextResponse(
|
||||
federation.public_key_pem(),
|
||||
media_type="text/plain",
|
||||
headers=_CORS_HEADERS,
|
||||
)
|
||||
|
||||
@app.get("/federation/feed")
|
||||
def federation_feed() -> JSONResponse:
|
||||
return JSONResponse(_cached_feed())
|
||||
return _public_json(_cached_feed())
|
||||
|
||||
@app.get("/federation/peers/public")
|
||||
def federation_peers_public() -> JSONResponse:
|
||||
@@ -202,7 +245,7 @@ def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None:
|
||||
Only trusted peers leak; unknown + blocked are internal state and must
|
||||
never appear here.
|
||||
"""
|
||||
return JSONResponse(_cached_public_peers())
|
||||
return _public_json(_cached_public_peers())
|
||||
|
||||
@app.get("/federation/network")
|
||||
def federation_network_public() -> JSONResponse:
|
||||
@@ -212,14 +255,14 @@ def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None:
|
||||
so a fetcher can reconstruct the local web-of-trust shape. Trusted peers
|
||||
only — never unknown or blocked. Signal hashes are deliberately omitted.
|
||||
"""
|
||||
return JSONResponse(_cached_public_network())
|
||||
return _public_json(_cached_public_network())
|
||||
|
||||
# ---------- public vouches + transparency log --------------------
|
||||
|
||||
@app.get("/federation/vouches")
|
||||
def federation_vouches() -> JSONResponse:
|
||||
"""Vouches WE have issued. Peers fetch this to learn who we trust."""
|
||||
return JSONResponse({
|
||||
return _public_json({
|
||||
"fingerprint": federation.node_fingerprint(),
|
||||
"vouches": [v.model_dump(mode="json") for v in federation.our_vouches()],
|
||||
})
|
||||
@@ -228,7 +271,7 @@ def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None:
|
||||
def federation_log() -> JSONResponse:
|
||||
"""Last 100 transparency-log entries, newest first."""
|
||||
entries = translog.recent(limit=100)
|
||||
return JSONResponse({
|
||||
return _public_json({
|
||||
"count": len(entries),
|
||||
"entries": [e.model_dump(mode="json") for e in entries],
|
||||
})
|
||||
@@ -240,8 +283,41 @@ def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None:
|
||||
head = translog.head()
|
||||
head_hash = head.entry_hash if head else None
|
||||
if isinstance(result, Err):
|
||||
return JSONResponse({"error": result.reason, "head_hash": head_hash}, status_code=409)
|
||||
return JSONResponse({"verified": result.value, "head_hash": head_hash})
|
||||
return JSONResponse(
|
||||
{"error": result.reason, "head_hash": head_hash},
|
||||
status_code=409,
|
||||
headers=_CORS_HEADERS,
|
||||
)
|
||||
return _public_json({"verified": result.value, "head_hash": head_hash})
|
||||
|
||||
# ---------- public explore page + data ---------------------------
|
||||
|
||||
@app.get("/federation/explore", response_class=HTMLResponse)
|
||||
def federation_explore_page(request: Request) -> HTMLResponse:
|
||||
"""Public transparency view — anyone can verify this network."""
|
||||
host = request.url.hostname or ""
|
||||
peer_param = request.query_params.get("peer", "").strip()
|
||||
return TEMPLATES.TemplateResponse(
|
||||
request,
|
||||
"federation_explore.html",
|
||||
{
|
||||
"fingerprint": federation.node_fingerprint(),
|
||||
"domain": host,
|
||||
"peer": peer_param,
|
||||
},
|
||||
)
|
||||
|
||||
@app.get("/federation/explore/data")
|
||||
def federation_explore_data(request: Request) -> JSONResponse:
|
||||
"""Signed public explorer payload — peer counts, vouches, transitives.
|
||||
|
||||
Public, no auth, CORS-enabled. Cached so a polled hit never triggers
|
||||
the distance-2 fan-out. See `network_view.build_explore_view` for the
|
||||
no-leak contract.
|
||||
"""
|
||||
host = request.url.hostname or None
|
||||
payload = _cached_explore(host)
|
||||
return _public_json(payload)
|
||||
|
||||
# ---------- admin: vouches page ---------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user