Onboarding core: setup payloads, enrollment tickets, scan-to-join (docs/10)
* kv-proto: ticket and join wire types, LoginFinish.ticket, b64_opt. * kv-crypto: pairing.rs (HPKE AuthPSK seal/open with the QR secret as PSK, SAS code). * kv-sync: onboarding.rs (kestrove://setup/v1 and kestrove://join/v1 codec with strict validation), ApiClient ticket/join calls and login_with_ticket, test server support for tickets and rendezvous. * kv-ffi: parse/encode_setup_payload, setup_payload_for_kit, add_device_payload, revoke_enrollment_ticket, login_with_setup, join_start/poll/cancel, join_approve/send. The Secret Key is kept locally, HPKE-sealed to enc_pk. * Tests: codec round trips and rejections, AuthPSK tamper cases, end-to-end Add-device QR and scan-to-join between two clients, forged bundles, bound tickets; live test extended with scan-to-join. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
//! Two devices against a real, freshly started kestrove-server. Ignored by default.
|
||||
//!
|
||||
//! ```sh
|
||||
//! # in kestrove-server (empty database, so it prints SETUP_TOKEN=… on boot):
|
||||
//! KV_DATABASE=sqlite::memory: KV_BIND=127.0.0.1:8080 KV_PUBLIC_URL=http://127.0.0.1:8080 cargo run
|
||||
//! # in kestrove-server (empty database; without KV_SETUP_CODE it prints SETUP_TOKEN=… on boot instead):
|
||||
//! KV_DATABASE=sqlite::memory: KV_BIND=127.0.0.1:8080 KV_PUBLIC_URL=http://127.0.0.1:8080 \
|
||||
//! KV_SETUP_CODE=live-test-setup-code cargo run
|
||||
//! # here:
|
||||
//! KESTROVE_SERVER_URL=http://127.0.0.1:8080 KESTROVE_SETUP_TOKEN=<token> \
|
||||
//! KESTROVE_SERVER_URL=http://127.0.0.1:8080 KESTROVE_SETUP_TOKEN=live-test-setup-code \
|
||||
//! cargo test -p kv-ffi --test live_server -- --ignored --nocapture
|
||||
//! ```
|
||||
//! The setup token works once, so restart the server with an empty database for every run.
|
||||
//! The setup code works once, so restart the server with an empty database for every run. The run takes about
|
||||
//! four minutes because it waits out the server's rate limit on the login endpoints.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -107,6 +109,22 @@ async fn two_devices_against_local_server() {
|
||||
b.sync_now().await.unwrap();
|
||||
assert!(matches!(b.get_item(temp), Err(CoreError::NotFound)));
|
||||
|
||||
// Scan-to-join (docs/10 §4): a third device that knows only the server URL needs no approval.
|
||||
tokio::time::sleep(std::time::Duration::from_secs(61)).await; // rate limit again
|
||||
let c = VaultCore::new_in_memory();
|
||||
connect(&c, &url, "device C");
|
||||
let session = c.join_start(url.clone()).await.unwrap();
|
||||
let confirm = a.join_approve(session.qr).await.unwrap();
|
||||
assert_eq!(confirm.device_name, "device C");
|
||||
assert_eq!(c.join_poll().await.unwrap().code.as_deref(), Some(confirm.code.as_str()));
|
||||
a.join_send().await.unwrap();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await; // the server allows one poll per second
|
||||
let setup = c.join_poll().await.unwrap().setup.expect("bundle");
|
||||
assert_eq!(setup.secret_key, sk);
|
||||
assert_eq!(c.login_with_setup(setup, pw.into()).await.unwrap(), LoginResult::Ok);
|
||||
c.sync_now().await.unwrap();
|
||||
assert!(c.list_items().unwrap().iter().any(|i| i.title == "Bank"));
|
||||
|
||||
b.logout().await.unwrap();
|
||||
println!("live sync OK against {url}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user