Files
ruvnet--RuView/v2/crates/wifi-densepose-cli/src/lib.rs
T
Dragan Spiridonov 31fb3d53f6 feat(cli): wifi-densepose login — Cognitum sign-in (ADR-271 phase 2)
Phase 1 could verify a token and phase 3 could gate on one, but there was no
way for a user to OBTAIN one. This closes that: sign in with a Cognitum account
and get a token a RuView sensing server accepts, instead of everyone sharing
one static RUVIEW_API_TOKEN string.

Lives in `ruview-auth` behind a non-default `login` feature rather than in the
CLI, so the Tauri desktop app can reuse it instead of growing a second copy.
A server built with default features still gets the verifier and nothing else —
no reqwest, no tokio net, no browser launcher. (This amends ADR-271's "no login
flow in this crate" note; the reason for that line was to keep the server lean,
and a feature gate achieves it without duplication.)

Ported from meta-proxy's src/oauth/, cross-checked against musica's
cognitum_provider.rs — two independent implementations against this same AS.
Where they agree, this follows both: redirect path EXACTLY /oauth/callback,
60-second refresh skew, OOB fallback on SSH/CONTAINER//.dockerenv.

Refresh is the part with teeth. Identity rotates refresh tokens with reuse
detection, so presenting a spent one revokes the whole session family. Both
obvious implementations are wrong: refreshing concurrently looks like replay,
and retrying a failed refresh with the same token IS the replay. So
`Session::ensure_fresh` holds an async mutex across the await, re-checks expiry
after acquiring it (the waiter usually finds the work already done), persists
the rotated token BEFORE returning it, and never retries. A missing expires_at
counts as expired rather than being given a guessed default.

Least scope by default: `login` requests `sensing:read`. `--admin` adds
`sensing:admin` explicitly, and requests both because there is no scope
hierarchy server-side. A session that streams poses should not casually hold
the capability to delete the model it streams through.

Credentials are written atomically and 0600 (temp file, chmod BEFORE rename) —
the same discipline the seed applies to its cloud key. `logout` is local-only
and says so: it makes this machine unable to act as you, but revoking the
session everywhere is an account-level action.

Also `whoami`, which reports whether the stored token is live — an
expired-looking session is the most common reason a command starts 401ing, and
it should be visible directly rather than inferred from a failure elsewhere.

Verified against PRODUCTION, not just locally: authorize URLs built by this
exact code path return HTTP 200 from auth.cognitum.one for both
`sensing:read` and `sensing:read sensing:admin`, which exercises the real
client_id, scope encoding, PKCE parameters and redirect_uri shape.

Tests: 74 with --features login (51 unit + 21 verifier matrix + 2 doctests),
including the RFC 7636 Appendix B vector, multi-scope URL encoding (a space
that is hand-formatted rather than encoded silently truncates the request), a
real TCP callback round-trip, callback timeout, 0600 permissions asserted on
disk, atomic-save leaving no temp file, and refresh-window boundaries.
Unchanged: 43 with default features, 501 in the sensing server.

Co-Authored-By: Ruflo & AQE
2026-07-22 17:53:42 +02:00

95 lines
2.7 KiB
Rust

//! WiFi-DensePose CLI
//!
//! Command-line interface for WiFi-DensePose system, including the
//! Mass Casualty Assessment Tool (MAT) for disaster response.
//!
//! # Features
//!
//! - **mat**: Disaster survivor detection and triage management
//! - **version**: Display version information
//!
//! # Usage
//!
//! ```bash
//! # Start scanning for survivors
//! wifi-densepose mat scan --zone "Building A"
//!
//! # View current scan status
//! wifi-densepose mat status
//!
//! # List detected survivors
//! wifi-densepose mat survivors --sort-by triage
//!
//! # View and manage alerts
//! wifi-densepose mat alerts
//! ```
use clap::{Parser, Subcommand};
pub mod auth;
pub mod calibrate;
pub mod calibrate_api;
pub mod room;
#[cfg(feature = "mat")]
pub mod mat;
/// WiFi-DensePose Command Line Interface
#[derive(Parser, Debug)]
#[command(name = "wifi-densepose")]
#[command(
author,
version,
about = "WiFi-based pose estimation and disaster response"
)]
#[command(propagate_version = true)]
pub struct Cli {
/// Command to execute
#[command(subcommand)]
pub command: Commands,
}
/// Top-level commands
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Sign in to Cognitum (ADR-271). Stores a token this machine can present
/// to a RuView sensing server instead of sharing one static API token.
Login(auth::LoginArgs),
/// Forget the locally stored Cognitum credentials.
Logout(auth::LogoutArgs),
/// Show the stored Cognitum session: account, scope, and whether it is live.
Whoami(auth::WhoamiArgs),
/// Empty-room baseline calibration (ADR-135).
/// Captures CSI frames via UDP and saves a per-subcarrier statistical
/// baseline used for real-time motion z-scoring and CIR reference.
Calibrate(calibrate::CalibrateArgs),
/// Run the calibration HTTP API (ADR-135/151) for a UI to drive.
/// Receives ESP32 CSI over UDP and exposes start/status/stop/result
/// endpoints at `/api/v1/calibration/*` (CORS-enabled).
CalibrateServe(calibrate_api::CalibrateServeArgs),
/// Guided per-room enrollment (ADR-151 Stage 2) — walk the anchor sequence
/// against a baseline, writing labelled features.
Enroll(room::EnrollArgs),
/// Train the per-room specialist bank from an enrollment (ADR-151 Stage 4).
TrainRoom(room::TrainRoomArgs),
/// Show a trained specialist bank's summary.
RoomStatus(room::RoomStatusArgs),
/// Live mixture-of-specialists readout from the CSI stream (ADR-151 Stage 5).
RoomWatch(room::RoomWatchArgs),
/// Mass Casualty Assessment Tool commands
#[cfg(feature = "mat")]
#[command(subcommand)]
Mat(mat::MatCommand),
/// Display version information
Version,
}