mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 15:31:38 +02:00
Admin posture (§2.1/§2.2):
- Hard fail-closed bind guard in main: a non-loopback ADMIN_BIND refuses startup
unless ADMIN_ALLOW_PUBLIC_BIND=true (was a warning).
- admin_plane_guard middleware on the admin router: Host allowlist
(localhost/127.0.0.1/[::1]/configured TOR_ADMIN_ONION; a missing Host is allowed
since rebinding must supply one and the bind is the primary control) to defeat
DNS-rebinding against the loopback socket, plus X-Frame-Options: DENY +
CSP frame-ancestors 'none' (anti-clickjacking). Still no CORS on the admin plane.
Public landing (§2.3), default-OFF, one fingerprint surface:
- GET /api/v1/server-info — built from a hand-written ServerInfo allowlist (never
by serializing config/state). Default projection is minimal { software.name,
status }; version (coarse major.minor), chains, price_feed, and uptime (bool
`up` only) are each individually opt-in. The enabled-feature tuple is NEVER
emitted anonymously (connected clients use /capabilities).
- GET / — minimal HTML (operator title HTML-escaped); bare 404 when off (does not
hint an admin plane exists). server-info also 404s when off.
- config: ADMIN_ALLOW_PUBLIC_BIND, TOR_ADMIN_ONION; PUBLIC_LANDING_TITLE +
per-field PUBLIC_EXPOSE_VERSION/CHAINS/PRICE_FEED toggles.
Tests: host_allowlist + html_escape unit tests; landing off->on minimal-projection
(asserts only software+status, no version/chains/features); admin-plane Host-403 +
anti-clickjacking headers. Landing/admin stay off the public OpenAPI.
Deferred (documented): §2.5 NIP-05 consent-gating (changes existing
/.well-known/nostr.json behavior — needs the standing v0.2.4-compat verification
first), §2.3 k-anon public stats, response-header/TLS-fingerprint stripping (proxy
/ops-doc concern).
47 lines
1.8 KiB
Rust
47 lines
1.8 KiB
Rust
//! L1 integration: the public landing surface (operator §2.3). Default-OFF; when
|
|
//! on, the default projection is the minimal `{ software.name, status }` and `/`
|
|
//! serves HTML. One sequential test (it toggles process env).
|
|
|
|
mod common;
|
|
|
|
use axum::http::StatusCode;
|
|
|
|
#[tokio::test]
|
|
async fn landing_off_then_on_minimal() {
|
|
// Off (default): both the read model and `/` are 404 (no admin-plane hint).
|
|
std::env::remove_var("PUBLIC_LANDING_ENABLED");
|
|
let Some(app) = common::try_app().await else {
|
|
return;
|
|
};
|
|
let (st, _) = app.request("GET", "/api/v1/server-info", None, None).await;
|
|
assert_eq!(st, StatusCode::NOT_FOUND);
|
|
let (st, _) = app.request("GET", "/", None, None).await;
|
|
assert_eq!(st, StatusCode::NOT_FOUND);
|
|
|
|
// On, default toggles: minimal projection — only software.name + status.
|
|
std::env::set_var("PUBLIC_LANDING_ENABLED", "true");
|
|
let app = common::try_app().await.expect("app");
|
|
let (st, body) = app.request("GET", "/api/v1/server-info", None, None).await;
|
|
assert_eq!(st, StatusCode::OK);
|
|
let obj = body.as_object().expect("object");
|
|
assert_eq!(obj.len(), 2, "only software + status by default: {body}");
|
|
assert_eq!(body["software"]["name"], "smirk-backend-core");
|
|
assert_eq!(body["status"], "ok");
|
|
assert!(
|
|
body["software"]
|
|
.as_object()
|
|
.unwrap()
|
|
.get("version")
|
|
.is_none(),
|
|
"version omitted by default"
|
|
);
|
|
for hidden in ["chains", "price_feed", "up", "features"] {
|
|
assert!(obj.get(hidden).is_none(), "{hidden} omitted by default");
|
|
}
|
|
|
|
let (st, _) = app.request("GET", "/", None, None).await;
|
|
assert_eq!(st, StatusCode::OK, "landing HTML served when enabled");
|
|
|
|
std::env::remove_var("PUBLIC_LANDING_ENABLED");
|
|
}
|