mirror of
https://github.com/ruvnet/RuView
synced 2026-07-29 18:31:44 +00:00
31fb3d53f6
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
61 lines
1.9 KiB
Rust
61 lines
1.9 KiB
Rust
//! WiFi-DensePose CLI Entry Point
|
|
//!
|
|
//! This is the main entry point for the wifi-densepose command-line tool.
|
|
|
|
use clap::Parser;
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
|
|
|
use wifi_densepose_cli::{Cli, Commands};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
// Initialize logging
|
|
tracing_subscriber::registry()
|
|
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
|
|
.with(tracing_subscriber::fmt::layer().with_target(false))
|
|
.init();
|
|
|
|
let cli = Cli::parse();
|
|
|
|
match cli.command {
|
|
Commands::Login(args) => {
|
|
wifi_densepose_cli::auth::login_cmd(args).await?;
|
|
}
|
|
Commands::Logout(args) => {
|
|
wifi_densepose_cli::auth::logout_cmd(args).await?;
|
|
}
|
|
Commands::Whoami(args) => {
|
|
wifi_densepose_cli::auth::whoami_cmd(args).await?;
|
|
}
|
|
Commands::Calibrate(args) => {
|
|
wifi_densepose_cli::calibrate::execute(args).await?;
|
|
}
|
|
Commands::CalibrateServe(args) => {
|
|
wifi_densepose_cli::calibrate_api::execute(args).await?;
|
|
}
|
|
Commands::Enroll(args) => {
|
|
wifi_densepose_cli::room::enroll(args).await?;
|
|
}
|
|
Commands::TrainRoom(args) => {
|
|
wifi_densepose_cli::room::train_room(args).await?;
|
|
}
|
|
Commands::RoomStatus(args) => {
|
|
wifi_densepose_cli::room::room_status(args).await?;
|
|
}
|
|
Commands::RoomWatch(args) => {
|
|
wifi_densepose_cli::room::room_watch(args).await?;
|
|
}
|
|
#[cfg(feature = "mat")]
|
|
Commands::Mat(mat_cmd) => {
|
|
wifi_densepose_cli::mat::execute(mat_cmd).await?;
|
|
}
|
|
Commands::Version => {
|
|
println!("wifi-densepose {}", env!("CARGO_PKG_VERSION"));
|
|
#[cfg(feature = "mat")]
|
|
println!("MAT module version: {}", wifi_densepose_mat::VERSION);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|