Sign in with Google, Apple…: item field, bridge save and identities
ci / test (push) Canceled after 0s
ci / wasm (push) Canceled after 0s
ci / deny (push) Canceled after 0s
ci / fuzz (push) Canceled after 0s

Items record sign_in_with {provider, account}; the browser bridge reports it
with the site's identities and saves it after the usual app prompt. Items
now keep fields they don't know, so editing in an older app never drops what
a newer one added.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-24 15:19:14 +07:00
co-authored by Claude Opus 5.5
parent 8622123b00
commit 20d465185d
12 changed files with 364 additions and 15 deletions
+7
View File
@@ -297,6 +297,11 @@ impl WebCore {
model.notes = item.notes;
model.favorite = item.favorite;
model.tags = kv_model::normalize_tags(&item.tags);
model.sign_in_with = item.sign_in_with.and_then(|s| {
let provider: String = s.provider.trim().to_lowercase().chars().take(32).collect();
let account = s.account.map(|a| a.trim().chars().take(256).collect::<String>()).filter(|a| !a.is_empty());
(!provider.is_empty()).then_some(kv_model::SignInWith { provider, account })
});
model.modified_at = now.max(previous.as_ref().map_or(0, |p| p.modified_at + 1));
self.write_item(&acct, &u, &mut model, existing.filter(|_| previous.is_some()), false)?;
Ok(id.to_string())
@@ -1208,6 +1213,7 @@ fn summary(i: &Item) -> ItemSummary {
favorite: i.favorite,
has_totp: i.totp.is_some(),
tags: i.tags.clone(),
sign_in_with: i.sign_in_with.clone(),
modified_at: i.modified_at,
}
}
@@ -1235,6 +1241,7 @@ pub fn to_dto(i: &Item) -> ItemDto {
notes: i.notes.clone(),
favorite: i.favorite,
tags: i.tags.clone(),
sign_in_with: i.sign_in_with.clone(),
modified_at: i.modified_at,
}
}
+4
View File
@@ -44,6 +44,8 @@ pub struct ItemSummary {
pub has_totp: bool,
/// Web only (not in kv-ffi's record): for tag filters and "recently modified" without `getItem`.
pub tags: Vec<String>,
/// "Sign in with Google…" for the site (docs/05).
pub sign_in_with: Option<kv_model::SignInWith>,
pub modified_at: i64,
}
@@ -70,6 +72,8 @@ pub struct ItemDto {
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub sign_in_with: Option<kv_model::SignInWith>,
#[serde(default)]
pub modified_at: i64,
}
+16
View File
@@ -40,10 +40,17 @@ export interface ItemSummary {
favorite: boolean;
hasTotp: boolean;
tags: string[];
/** "Sign in with Google…" for the site (docs/05 "Sign in with…"). */
signInWith: SignInWith | null;
/** Unix seconds. */
modifiedAt: number;
}
/** `provider` is an id from `signInProviders()` (`google`, `apple`…); `account` the provider account used. */
export interface SignInWith { provider: string; account?: string | null; }
export interface SignInProvider { id: string; name: string; }
/** `id: ""` creates a new item. Omitted optional fields default to null / [] / false / 0. */
export interface ItemDto {
id: string;
@@ -56,6 +63,7 @@ export interface ItemDto {
notes: string | null;
favorite: boolean;
tags: string[];
signInWith: SignInWith | null;
/** Unix seconds; set by the core on save. */
modifiedAt: number;
}
@@ -865,6 +873,14 @@ impl VaultCore {
// ---------------------------------------------------------------- free functions
/// The identity providers offered for "Sign in with…", in display order.
#[wasm_bindgen(js_name = signInProviders, unchecked_return_type = "SignInProvider[]")]
pub fn sign_in_providers() -> std::result::Result<JsValue, JsValue> {
let list: Vec<_> =
kv_model::PROVIDERS.iter().map(|(id, name, _)| serde_json::json!({ "id": id, "name": name })).collect();
to_js(&list)
}
#[wasm_bindgen(js_name = generatePassword)]
pub fn generate_password(length: u32, symbols: bool) -> String {
core::generate_password(length, symbols)
+2
View File
@@ -110,6 +110,7 @@ fn read_with_kv_ffi(f: &Fixture) -> Vec<ItemDto> {
urls: d.urls,
totp: d.totp,
notes: d.notes,
sign_in_with: d.sign_in_with.map(|w| kv_model::SignInWith { provider: w.provider, account: w.account }),
favorite: d.favorite,
tags: d.tags,
modified_at: d.modified_at,
@@ -140,6 +141,7 @@ fn ffi_item(title: &str, kind: kv_ffi::ItemKindDto) -> kv_ffi::ItemDto {
notes: None,
favorite: false,
tags: vec![],
sign_in_with: None,
modified_at: 0,
}
}
+3
View File
@@ -49,6 +49,7 @@ fn web_item(title: &str, password: &str) -> ItemDto {
notes: None,
favorite: false,
tags: vec![],
sign_in_with: None,
modified_at: 0,
}
}
@@ -82,6 +83,7 @@ async fn web_vault_enrolls_and_syncs_with_an_app_device() {
notes: None,
favorite: false,
tags: vec![],
sign_in_with: None,
modified_at: 0,
};
let bank = app.save_item(on_app.clone()).unwrap();
@@ -189,6 +191,7 @@ async fn app_with_item(server: &TestServer) -> (Arc<kv_ffi::VaultCore>, String,
notes: None,
favorite: false,
tags: vec![],
sign_in_with: None,
modified_at: 0,
})
.unwrap();