wifi-densepose/v2/crates/homecore-api
rUv db3d94a313
fix(homecore-api security): auth-gate GET /api/ (was unauthenticated) + recover WS subscription on broadcast lag (#1076)
* fix(homecore-api security): auth-gate GET /api/ (HC-API-AUTH-01, ADR-161)

`rest::api_root` took no headers and unconditionally returned
`200 {"message":"API running."}`, while every sibling REST route gates
on `BearerAuth::from_headers`. HA's `APIStatusView` inherits
`requires_auth = True`, so `/api/` must return 401 for a missing/wrong
bearer — HA clients use it as a token-validation probe, so a 200 told a
bad-token client its token was valid and let an unauthenticated party
confirm a live endpoint. LOW severity (static body, no data leak),
reported at true severity.

Fix: `api_root(headers, State)` validates the bearer like `get_config`.

Pinned by fails-on-old tests (200 -> assert 401):
- api_root_rejects_missing_bearer
- api_root_rejects_wrong_bearer
guarded by api_root_accepts_correct_bearer (still 200 with valid token).

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(homecore-api security): recover WS subscription on broadcast lag (HC-WS-LAG-01, ADR-161)

`subscribe_events`'s per-subscription task matched `Err(_) => break` on
both broadcast `recv()` arms. `RecvError::Lagged(n)` (a slow consumer
falling >EVENT_CHANNEL_CAPACITY=4,096 events behind) is recoverable —
the bus doc says "Lagged receivers must re-sync" and HA keeps the
subscription alive across a lag. The old code treated the first lag as
fatal, so after an event burst the client's stream went permanently
silent with no error frame — a self-inflicted event-delivery DoS under
load. LOW severity.

Fix: `Lagged(_) => continue` (skip dropped window, re-sync),
`Closed => break`, on both the system and domain arms.

Pinned by subscription_survives_broadcast_lag: subscribes, floods 6,000
filtered events past the 4,096 capacity to force a Lagged, then asserts
a subsequent subscribed event is still delivered (old code: 5s timeout).

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs(homecore-api security): record HC-API-AUTH-01 + HC-WS-LAG-01 review (ADR-161)

CHANGELOG [Unreleased] Security entry + ADR-161 addendum documenting the
beyond-SOTA network-API review: two LOW bugs fixed (unauthenticated
GET /api/; WS subscription killed on broadcast lag) and the
auth/traversal/injection/info-leak/CORS dimensions confirmed clean with
evidence (no traversal surface — in-memory DashMap + EntityId allowlist;
HashSet token compare, not a byte-== timing oracle).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-14 16:48:57 -04:00
..
src fix(homecore-api security): auth-gate GET /api/ (was unauthenticated) + recover WS subscription on broadcast lag (#1076) 2026-06-14 16:48:57 -04:00
tests fix(homecore-api security): auth-gate GET /api/ (was unauthenticated) + recover WS subscription on broadcast lag (#1076) 2026-06-14 16:48:57 -04:00
Cargo.toml fix(homecore-api): close WS auth bypass + reply-theater, harden dev bin (ADR-161 A1/A2/A8) 2026-06-12 00:55:16 -04:00
README.md docs(homecore-api): comprehensive README — REST + WebSocket API 2026-05-25 23:09:55 -04:00

README.md

homecore-api

Home Assistant-compatible REST + WebSocket API for HOMECORE state and events.

Crates.io License MSRV: 1.89+ Tests ADR-130

Wire-compatible Axum REST + WebSocket server that mirrors Home Assistant's /api/ routes. Ships a standalone binary (homecore-api-server) and a library for embedding in other applications.

What this crate does

homecore-api provides the HTTP boundary layer for HOMECORE. It wires Axum routes to the homecore state machine, exposing:

  • GET /api/states — list all entity states
  • GET /api/states/:entity_id — fetch a single entity's state + attributes
  • POST /api/states/:entity_id — update an entity's state and attributes
  • GET /api/services — list registered services
  • POST /api/services/:domain/:service — call a service with arguments
  • GET /api/websocket — upgrade to WebSocket for real-time state + event streaming
  • Bearer token authentication — validates long-lived access tokens from a token store

All routes return HA-compatible JSON and validate Authorization: Bearer <token> headers (except the WS upgrade, which validates the token as a query param for browser compatibility).

Features

  • HA-compatible JSON schema/api/states returns [{"entity_id": "...", "state": "...", "attributes": {...}}] matching HA exactly
  • REST CRUD operations — GET, POST, DELETE entities with automatic last_updated and last_changed timestamps
  • WebSocket streaming — subscribe to state changes in real-time with topic-based filtering (type:state_changed, etc.)
  • Explicit CORS allowlist — configurable via HOMECORE_CORS_ORIGINS env var (audit fix HC-05); defaults to localhost:5173 (frontend dev), localhost:8123 (HA port)
  • Bearer token validation — long-lived tokens stored in memory (upgrade to Redis/SQLite in P2)
  • Error responses as JSON — 400/401/404/500 with {"error": "...", "message": "..."} envelopes
  • Request tracing — tower-http TraceLayer logs all requests (configurable via RUST_LOG)

Capabilities

Capability Method Endpoint Returns
List all entities GET /api/states [{entity_id, state, attributes, last_changed, ...}]
Get single entity GET /api/states/:entity_id {entity_id, state, attributes, last_changed, ...} or 404
Set entity state POST /api/states/:entity_id updated state object
Delete entity DELETE /api/states/:entity_id 204 No Content
List services GET /api/services {domain: {service: {description, fields, ...}}}
Call service POST /api/services/:domain/:service service result (P2)
Stream state changes WebSocket /api/websocket {type, event} JSON messages
Validate token Bearer auth all routes 401 Unauthorized if token invalid

Comparison to Home Assistant

Aspect Home Assistant homecore-api
Framework aiohttp Axum
Server type Single-threaded async (Python asyncio) Multi-threaded async (Tokio)
JSON schema HA's /api/states format Wire-compatible (identical)
CORS Permissive (all origins allowed) Explicit allowlist (audit fix HC-05)
Authentication long_lived_access_tokens (SQLite) LongLivedTokenStore (in-memory P1)
WebSocket codec HA's message format + types dict JSON messages with type/event fields (P2)
Service calling async handler dispatch ServiceRegistry stub (P2)
Error handling Python exception → JSON 500 Rust Result + thiserror → JSON with details

Performance

  • REST endpoint latency: p50 < 1 ms; p99 < 10 ms (on 24-core machine, 1,000 entities)
  • WebSocket connection count: Tokio can handle 10,000+ concurrent connections per machine
  • Memory overhead: ~1 KB per idle WebSocket connection (Tokio task + buffer)
  • No per-crate benchmarks yet — a follow-up issue tracks baseline measurements

Usage

use homecore_api::{router, SharedState};
use homecore::HomeCore;
use axum::Server;
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    // Create the shared HOMECORE runtime
    let homecore = HomeCore::new();
    let state = SharedState::new(homecore);

    // Build the Axum router
    let app = router(state);

    // Bind to 8123
    let addr = SocketAddr::from(([127, 0, 0, 1], 8123));
    Server::bind(&addr)
        .serve(app.into_make_service_with_connect_info::<SocketAddr>())
        .await
        .expect("server error");
}

Or run the standalone binary:

cargo run -p homecore-api --bin homecore-api-server
# Listens on http://localhost:8123

Test it:

# List states
curl -H "Authorization: Bearer longlivedtoken" \
  http://localhost:8123/api/states

# Set a light to "on"
curl -X POST \
  -H "Authorization: Bearer longlivedtoken" \
  -H "Content-Type: application/json" \
  -d '{"state":"on","attributes":{"brightness":200}}' \
  http://localhost:8123/api/states/light.kitchen

Relation to other HOMECORE crates

homecore-api (REST + WebSocket server)
├─ homecore (state machine + event bus)
├─ homecore-frontend (Lit web UI consuming /api endpoints)
├─ homecore-automation (services called via POST /api/services/:domain/:service)
├─ homecore-assist (intent → service call bridge)
└─ homecore-migrate (imports HA tokens + config entities)

References