Files
kestrove-core/crates/kv-wasm/src/dto.rs
T
samiandClaude Opus 5.5 20d465185d
ci / test (push) Canceled after 0s
ci / wasm (push) Canceled after 0s
ci / deny (push) Canceled after 0s
ci / fuzz (push) Canceled after 0s
Sign in with Google, Apple…: item field, bridge save and identities
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]>
2026-10-24 15:19:14 +07:00

316 lines
9.0 KiB
Rust

//! Records crossing the JS boundary. Same fields as kv-ffi's UniFFI records, in camelCase; enums are
//! camelCase strings (`"login"`, `"secureNote"`, `"pendingApproval"`); byte fields are `Uint8Array`.
//! The TypeScript declarations for these live in `wasm.rs` (`TS_TYPES`); keep both in step.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ItemKindDto {
Login,
SecureNote,
Card,
Identity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MatchLevelDto {
None,
Domain,
Host,
Exact,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PlatformDto {
Ios,
Macos,
Android,
Windows,
Linux,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemSummary {
pub id: String,
pub kind: ItemKindDto,
pub title: String,
pub username: Option<String>,
pub urls: Vec<String>,
pub favorite: bool,
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,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemDto {
/// Empty string means "new item".
#[serde(default)]
pub id: String,
pub kind: ItemKindDto,
pub title: String,
#[serde(default)]
pub username: Option<String>,
#[serde(default)]
pub password: Option<String>,
#[serde(default)]
pub urls: Vec<String>,
#[serde(default)]
pub totp: Option<String>,
#[serde(default)]
pub notes: Option<String>,
#[serde(default)]
pub favorite: bool,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub sign_in_with: Option<kv_model::SignInWith>,
#[serde(default)]
pub modified_at: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TotpCode {
pub code: String,
pub seconds_remaining: u64,
pub period: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerInfoDto {
pub version: String,
pub api: u32,
pub setup_open: bool,
pub min_client: String,
/// "invite" or "open" (absent on servers from before multi-user).
pub registration: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountDetailsDto {
pub email: Option<String>,
pub is_admin: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminUserDto {
pub account_id: String,
pub email: Option<String>,
pub is_admin: bool,
pub disabled: bool,
pub created_at: String,
pub last_seen: Option<i64>,
pub devices: u32,
pub items: u32,
pub storage_bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InviteDto {
pub invite_id: String,
pub email: Option<String>,
pub created_at: i64,
pub expires_at: i64,
pub used: bool,
pub revoked: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InviteCreatedDto {
pub invite_id: String,
pub code: String,
pub expires_at: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminSettingsDto {
/// "invite" or "open".
pub registration: String,
pub allowed_domains: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum LoginResult {
/// Logged in and unlocked.
Ok,
/// An enrolled device must approve this browser (or pass `totp`); call `login` again afterwards.
PendingApproval,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncReport {
pub pulled: u32,
pub pushed: u32,
pub merged: u32,
pub restored_item_ids: Vec<String>,
pub vaults_synced: u32,
pub vaults_skipped: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceDto {
pub device_id: String,
pub name: String,
pub platform: PlatformDto,
pub approved: bool,
pub revoked: bool,
pub current: bool,
pub created_at: String,
pub key_fingerprint: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeySummary {
pub item_id: String,
#[serde(with = "serde_bytes")]
pub credential_id: Vec<u8>,
pub rp_id: String,
#[serde(with = "serde_bytes")]
pub user_handle: Vec<u8>,
pub user_name: String,
pub created_at: i64,
}
/// `{ type: "onePux" } | { type: "csv" } | { type: "kestroveEncrypted", passphrase }`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ImportFormat {
OnePux,
Csv,
KestroveEncrypted { passphrase: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum DuplicateHandling {
Skip,
AddSuffix,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportOptions {
pub duplicates: DuplicateHandling,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportPreview {
pub total: u32,
pub logins: u32,
pub secure_notes: u32,
pub cards: u32,
pub identities: u32,
pub duplicates: u32,
pub attachments_skipped: u32,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportReport {
pub imported: u32,
pub skipped_duplicates: u32,
pub renamed_duplicates: u32,
pub attachments_skipped: u32,
pub warnings: Vec<String>,
pub item_ids: Vec<String>,
}
// ---------------------------------------------------------------- onboarding (docs/10)
/// A parsed, validated setup payload: everything a new device needs except the master password.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetupInfo {
/// Normalized server URL.
pub server_url: String,
/// Standard base64 SPKI pins, the format `configure_server` takes. Empty = WebPKI only.
#[serde(default)]
pub spki_pins: Vec<String>,
pub account_id: String,
/// Display form `K1-…`.
pub secret_key: String,
/// Enrollment ticket; `login_with_setup` presents it so no manual approval is needed.
#[serde(default)]
pub ticket: Option<String>,
}
impl zeroize::Zeroize for SetupInfo {
fn zeroize(&mut self) {
self.secret_key.zeroize();
self.ticket.zeroize();
}
}
impl std::fmt::Debug for SetupInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SetupInfo")
.field("server_url", &self.server_url)
.field("spki_pins", &self.spki_pins)
.field("account_id", &self.account_id)
.field("secret_key", &"<redacted>")
.field("ticket", &self.ticket.as_ref().map(|_| "<redacted>"))
.finish()
}
}
/// The "Add a device" QR: a setup payload with a fresh, unbound ticket.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddDevicePayload {
pub payload: String,
/// For `revoke_enrollment_ticket` when the user cancels.
pub ticket_id: String,
/// Unix seconds; hide the QR after this.
pub expires_at: i64,
}
/// Shown by the joining device: a QR for a signed-in phone to scan.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JoinSession {
pub qr: String,
/// Unix seconds.
pub expires_at: i64,
}
/// Result of `join_poll`: `code` once a phone has scanned the QR, `setup` once it sent the bundle.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JoinProgress {
pub code: Option<String>,
pub setup: Option<SetupInfo>,
}
/// Shown by the approving device after scanning: the code to compare and who is asking.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JoinConfirm {
/// `"123 456"`. The joining device shows the same code.
pub code: String,
pub device_name: String,
pub platform: PlatformDto,
/// Same format as `DeviceDto.key_fingerprint`.
pub key_fingerprint: String,
}