M3: working desktop app + browser pairing channel
- Builds and runs (tauri build --debug); placeholder icons; persistent VaultCore::open in the OS app-data dir; Secret Key remembered on the device; tray keeps the app serving the extension. - Auto-lock: idle (system idle on Windows/macOS, in-app activity on Linux), screen lock, sleep. - Local IPC for kestrove-nmh: 0700 dir + 0600 Unix socket with peer-uid checks; Windows named pipe (first instance, local only, user DACL, client SID check) behind cfg. - Pairing channel: X25519 → HKDF → 6-word fingerprint confirmed in both UIs, per-connection session keys, AES-256-GCM with strict counters. Test vectors shared with kestrove-browser. - Broker: query/fill/totp/save with origin re-checks; pairing and save prompts in the UI. - kestrove-nmh relays ciphertext only; native-host installers for Linux/macOS/Windows. - Tests: unit tests, tests/nmh_stdio.rs (real kestrove-nmh over stdio), examples/test-host. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
//! Headless stand-in for the desktop app, for end-to-end tests of the browser extension and
|
||||
//! `kestrove-nmh`. It runs the same `Broker` as the app over the same IPC endpoint, with an
|
||||
//! in-memory vault holding one test login, and plays the user: it approves pairings and saves.
|
||||
//! Never ship this: it auto-approves.
|
||||
//!
|
||||
//! KESTROVE_IPC_DIR=/tmp/kv-e2e KESTROVE_TEST_ORIGIN=http://127.0.0.1:8765 \
|
||||
//! cargo run --example test-host
|
||||
//!
|
||||
//! Prints `READY <endpoint>`, then `PAIRED <words>` and `SAVED <origin> <username>` as they happen.
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use kestrove_desktop::broker::{Broker, PairingStore, PairingView, SaveView, Ui};
|
||||
use kestrove_desktop::ipc;
|
||||
use kv_ffi::{ItemDto, ItemKindDto, VaultCore};
|
||||
|
||||
struct Quiet;
|
||||
impl Ui for Quiet {
|
||||
fn pairing_requested(&self, _: &PairingView) {}
|
||||
fn save_requested(&self, _: &SaveView) {}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let origin = std::env::var("KESTROVE_TEST_ORIGIN").unwrap_or_else(|_| "http://127.0.0.1:8765".into());
|
||||
let core = VaultCore::new_in_memory();
|
||||
core.create_account("test master password".into())?;
|
||||
core.save_item(ItemDto {
|
||||
id: String::new(),
|
||||
kind: ItemKindDto::Login,
|
||||
title: "Test login".into(),
|
||||
username: Some("[email protected]".into()),
|
||||
password: Some("correct-horse-42".into()),
|
||||
urls: vec![origin],
|
||||
totp: Some("JBSWY3DPEHPK3PXP".into()),
|
||||
notes: None,
|
||||
favorite: false,
|
||||
tags: vec![],
|
||||
modified_at: 0,
|
||||
})?;
|
||||
let broker = Broker::new(core, Arc::new(Quiet), PairingStore::in_memory());
|
||||
let listener = ipc::Listener::bind()?;
|
||||
println!("READY {}", ipc::endpoint()?.display());
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
let b = broker.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
if let Some(p) = b.pending_pairing() {
|
||||
if b.resolve_pairing(&p.id, true).is_ok() {
|
||||
println!("PAIRED {}", p.words.join(" "));
|
||||
}
|
||||
}
|
||||
for s in b.pending_saves() {
|
||||
if b.resolve_save(&s.id, true).is_ok() {
|
||||
println!("SAVED {} {}", s.origin, s.username);
|
||||
}
|
||||
}
|
||||
let _ = std::io::stdout().flush();
|
||||
});
|
||||
|
||||
loop {
|
||||
let conn = listener.accept()?;
|
||||
let b = broker.clone();
|
||||
thread::spawn(move || {
|
||||
let _ = b.serve(conn);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user