Approve sites' passkey requests from the browser extension
The app comes forward with a prompt to sign in with or save a passkey, confirmed with the master password (the user verification each use needs). Asked while locked: unlock, then it continues. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
//! 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.
|
||||
//! in-memory vault holding one test login, and plays the user: it approves pairings, saves and passkeys.
|
||||
//! 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.
|
||||
//! Prints `READY <endpoint>`, then `PAIRED <words>`, `SAVED <origin> <username>` and
|
||||
//! `PASSKEY <create|get> <rp_id> <user>` as they happen.
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
@@ -14,6 +15,7 @@ use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use kestrove_desktop::broker::{Broker, PairingStore, PairingView, SaveView, Ui};
|
||||
use kestrove_desktop::passkey::{PasskeyKind, PasskeyView};
|
||||
use kestrove_desktop::ipc;
|
||||
use kv_ffi::{ItemDto, ItemKindDto, VaultCore};
|
||||
|
||||
@@ -21,6 +23,7 @@ struct Quiet;
|
||||
impl Ui for Quiet {
|
||||
fn pairing_requested(&self, _: &PairingView) {}
|
||||
fn save_requested(&self, _: &SaveView) {}
|
||||
fn passkey_requested(&self, _: &PasskeyView) {}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -53,6 +56,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("PAIRED {}", p.words.join(" "));
|
||||
}
|
||||
}
|
||||
for p in b.pending_passkeys() {
|
||||
if b.resolve_passkey(&p.id, true, None).is_ok() {
|
||||
let (kind, user) = match p.kind {
|
||||
PasskeyKind::Create => ("create", p.user_name.clone()),
|
||||
PasskeyKind::Get => ("get", p.choices.first().map(|c| c.user_name.clone()).unwrap_or_default()),
|
||||
};
|
||||
println!("PASSKEY {kind} {} {user}", p.rp_id);
|
||||
}
|
||||
}
|
||||
for s in b.pending_saves() {
|
||||
if b.resolve_save(&s.id, true).is_ok() {
|
||||
println!("SAVED {} {}", s.origin, s.username);
|
||||
|
||||
@@ -9,5 +9,5 @@ pub mod qr;
|
||||
pub mod sync;
|
||||
pub mod title_bar;
|
||||
|
||||
pub use kv_ffi::bridge::{broker, channel, words};
|
||||
pub use kv_ffi::bridge::{broker, channel, passkey, words};
|
||||
pub use kv_ipc::{framing, ipc, private_file};
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::{io, thread};
|
||||
|
||||
use kestrove_desktop::autolock::{self, Activity, Reason};
|
||||
use kestrove_desktop::broker::{Broker, PairingStore, PairingView, SaveView, Ui};
|
||||
use kestrove_desktop::passkey::PasskeyView;
|
||||
use kestrove_desktop::device_key::DeviceKey;
|
||||
use kestrove_desktop::sync::{
|
||||
self, AddDeviceView, DeviceView, JoinConfirmView, JoinPollView, JoinView, SetupView, SignIn, SyncService,
|
||||
@@ -136,6 +137,13 @@ impl Ui for TauriUi {
|
||||
self.surface();
|
||||
let _ = self.handle.emit("save-request", req);
|
||||
}
|
||||
fn passkey_requested(&self, req: &PasskeyView) {
|
||||
self.surface();
|
||||
let _ = self.handle.emit("passkey-request", req);
|
||||
}
|
||||
fn passkey_settled(&self, id: &str) {
|
||||
let _ = self.handle.emit("passkey-settled", id);
|
||||
}
|
||||
fn activity(&self) {
|
||||
self.activity.touch();
|
||||
}
|
||||
@@ -355,6 +363,36 @@ fn resolve_save(app: State<'_, App>, id: String, accept: bool) -> CmdResult<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn pending_passkeys(app: State<'_, App>) -> Vec<PasskeyView> {
|
||||
app.broker.pending_passkeys()
|
||||
}
|
||||
|
||||
/// A page's passkey request (docs/05 "Passkeys in the browser"). Accepting needs the master password: the user
|
||||
/// verification every passkey use requires, where the app has no biometrics to ask.
|
||||
#[tauri::command]
|
||||
fn resolve_passkey(
|
||||
app: State<'_, App>,
|
||||
id: String,
|
||||
accept: bool,
|
||||
credential_id: Option<String>,
|
||||
master_password: Option<String>,
|
||||
) -> CmdResult<()> {
|
||||
if accept {
|
||||
let pw = Zeroizing::new(master_password.unwrap_or_default());
|
||||
app.core.confirm_master_password(pw.to_string()).map_err(|e| match e {
|
||||
CoreError::BadCredentials => "Wrong master password.".to_string(),
|
||||
e => err(e),
|
||||
})?;
|
||||
}
|
||||
app.broker.resolve_passkey(&id, accept, credential_id.as_deref())?;
|
||||
app.activity.touch();
|
||||
if accept {
|
||||
app.sync.request_sync();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- sync server (docs/03)
|
||||
|
||||
#[tauri::command]
|
||||
@@ -645,6 +683,8 @@ fn main() {
|
||||
list_pairings,
|
||||
revoke_pairing,
|
||||
pending_saves,
|
||||
pending_passkeys,
|
||||
resolve_passkey,
|
||||
resolve_save,
|
||||
sync_status,
|
||||
sync_now,
|
||||
|
||||
@@ -20,6 +20,7 @@ struct NoUi;
|
||||
impl Ui for NoUi {
|
||||
fn pairing_requested(&self, _: &PairingView) {}
|
||||
fn save_requested(&self, _: &SaveView) {}
|
||||
fn passkey_requested(&self, _: &kestrove_desktop::passkey::PasskeyView) {}
|
||||
}
|
||||
|
||||
struct Host {
|
||||
|
||||
Reference in New Issue
Block a user