mirror of
https://github.com/ruvnet/RuView
synced 2026-06-19 11:53:19 +00:00
9d52d49c0b
A1 (CRITICAL): the /api/websocket handshake accepted any non-empty token, ignoring the LongLivedTokenStore whitelist the REST path enforces — a full WS auth bypass. Now validates via state.tokens().is_valid() before auth_ok; wrong tokens get auth_invalid + close. A2 (HIGH): WS command replies were pushed into an mpsc whose only consumer logged and discarded them — no result/pong/event reached the client. Split the socket with futures StreamExt::split; a dedicated writer task drains the response channel onto the wire. A8 (HIGH): the homecore-api dev bin bound 0.0.0.0 with unconditional allow-any auth and no env path. Wired the HOMECORE_TOKENS env path (dev fallback warn-logged when unset) and defaulted the bind to 127.0.0.1 (HOMECORE_BIND to opt into LAN). Tests (fail on old source): - ws_handshake::wrong_token_is_rejected (old → auth_ok) - ws_handshake::result_reply_is_received / ping_pong_reply_is_received (old → timeout) - server_bin_auth::provisioned_bin_rejects_wrong_bearer / from_env_path_enforces_whitelist Co-Authored-By: claude-flow <ruv@ruv.net>
74 lines
2.8 KiB
Rust
74 lines
2.8 KiB
Rust
//! `homecore-api-server` binary. Boots a HomeCore runtime and serves
|
|
//! the HA-compat REST + WS API.
|
|
//!
|
|
//! ## Auth (ADR-161, HC-WS-08)
|
|
//!
|
|
//! Token provisioning matches `homecore-server`: if `HOMECORE_TOKENS`
|
|
//! is set (comma-separated bearer tokens) the API enforces that
|
|
//! whitelist on both the REST and WS paths. If it is **unset**, the
|
|
//! binary falls back to an explicitly-logged DEV mode (any non-empty
|
|
//! bearer accepted) — before this fix the bin unconditionally used
|
|
//! `allow_any_non_empty()` with no env path, so a provisioned operator
|
|
//! had no way to lock it down.
|
|
//!
|
|
//! ## Bind address
|
|
//!
|
|
//! Defaults to `127.0.0.1` (loopback only) so a bare `cargo run` of
|
|
//! this dev binary is not network-exposed. Override with
|
|
//! `HOMECORE_BIND=0.0.0.0:8123` for a LAN deployment (and provision
|
|
//! `HOMECORE_TOKENS` when you do).
|
|
//!
|
|
//! cargo run -p homecore-api --bin homecore-api-server
|
|
//! HOMECORE_TOKENS=secret curl -H "Authorization: Bearer secret" \
|
|
//! http://127.0.0.1:8123/api/
|
|
|
|
use std::net::SocketAddr;
|
|
|
|
use homecore::HomeCore;
|
|
use homecore_api::{router, LongLivedTokenStore, SharedState, DEFAULT_PORT};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "info,tower_http=debug,homecore_api=debug".into()),
|
|
)
|
|
.init();
|
|
|
|
let homecore = HomeCore::new();
|
|
|
|
// Token provisioning (HC-WS-08). Prefer the HOMECORE_TOKENS env
|
|
// whitelist; fall back to DEV mode (warn-logged) only when unset.
|
|
let tokens = if std::env::var("HOMECORE_TOKENS")
|
|
.map(|v| !v.trim().is_empty())
|
|
.unwrap_or(false)
|
|
{
|
|
let s = LongLivedTokenStore::from_env();
|
|
let n = s.len().await;
|
|
tracing::info!("LongLivedTokenStore provisioned with {n} bearer token(s) from HOMECORE_TOKENS");
|
|
s
|
|
} else {
|
|
tracing::warn!(
|
|
"HOMECORE_TOKENS not set — token store in DEV mode (any non-empty bearer \
|
|
accepted). Set HOMECORE_TOKENS before exposing this binary to the network."
|
|
);
|
|
LongLivedTokenStore::allow_any_non_empty()
|
|
};
|
|
|
|
let state = SharedState::with_tokens(homecore, "Home", env!("CARGO_PKG_VERSION"), tokens);
|
|
let app = router(state);
|
|
|
|
// Default to loopback so `cargo run` is not network-exposed; allow
|
|
// an explicit HOMECORE_BIND override for LAN deployments.
|
|
let addr: SocketAddr = match std::env::var("HOMECORE_BIND") {
|
|
Ok(v) if !v.trim().is_empty() => v.parse()?,
|
|
_ => SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)),
|
|
};
|
|
tracing::info!("HOMECORE-API listening on http://{addr} (HA-compat /api + /api/websocket)");
|
|
|
|
let listener = tokio::net::TcpListener::bind(addr).await?;
|
|
axum::serve(listener, app).await?;
|
|
Ok(())
|
|
}
|