Implement iPhone TOTP tab and Watch sharing

This commit is contained in:
2026-08-11 20:54:34 +02:00
parent ae64ce47a3
commit b01cc8bb6d
10 changed files with 1980 additions and 5 deletions

View File

@@ -37,6 +37,11 @@ use ironstorage::{
MobilePasswordErrorKind as StoragePasswordErrorKind,
MobilePasswordRowKind as StoragePasswordRowKind,
},
mobile_totp::{
MobileTotpDetail as StorageTotpDetail, MobileTotpPage as StorageTotpPage,
MobileWatchSnapshotState as StorageWatchSnapshotState,
MobileWatchSnapshotStatus as StorageWatchSnapshotStatus,
},
};
uniffi::setup_scaffolding!();
@@ -630,6 +635,102 @@ pub struct MobileEntryCopy {
pub timeout_seconds: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileWatchSnapshotState {
Unavailable,
Pending,
}
impl From<StorageWatchSnapshotState> for MobileWatchSnapshotState {
fn from(state: StorageWatchSnapshotState) -> Self {
match state {
StorageWatchSnapshotState::Unavailable => Self::Unavailable,
StorageWatchSnapshotState::Pending => Self::Pending,
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct MobileWatchSnapshotStatus {
pub state: MobileWatchSnapshotState,
pub detail: String,
}
impl From<&StorageWatchSnapshotStatus> for MobileWatchSnapshotStatus {
fn from(status: &StorageWatchSnapshotStatus) -> Self {
Self {
state: status.state().into(),
detail: status.detail().to_owned(),
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct MobileTotpRow {
pub path: String,
pub issuer: Option<String>,
pub account: String,
pub title: String,
pub detail: String,
pub shared_with_watch: bool,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileTotpPage {
pub rows: Vec<MobileTotpRow>,
pub unavailable_entries: u32,
pub watch: MobileWatchSnapshotStatus,
}
impl From<StorageTotpPage> for MobileTotpPage {
fn from(page: StorageTotpPage) -> Self {
Self {
rows: page
.rows()
.iter()
.map(|row| MobileTotpRow {
path: row.path().to_owned(),
issuer: row.issuer().map(str::to_owned),
account: row.account().to_owned(),
title: row.title().to_owned(),
detail: row.detail().to_owned(),
shared_with_watch: row.shared_with_watch(),
})
.collect(),
unavailable_entries: page.unavailable_entries(),
watch: page.watch().into(),
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct MobileTotpDetail {
pub path: String,
pub issuer: Option<String>,
pub account: String,
pub code: String,
pub valid_until: u64,
pub period: u64,
pub shared_with_watch: bool,
pub watch: MobileWatchSnapshotStatus,
}
impl From<StorageTotpDetail> for MobileTotpDetail {
fn from(detail: StorageTotpDetail) -> Self {
Self {
path: detail.path().to_owned(),
issuer: detail.issuer().map(str::to_owned),
account: detail.account().to_owned(),
code: String::from_utf8(detail.code().expose().to_vec())
.expect("storage-generated TOTP codes are ASCII"),
valid_until: detail.valid_until(),
period: detail.period(),
shared_with_watch: detail.shared_with_watch(),
watch: detail.watch().into(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileEntryEditorFieldKind {
Password,
@@ -894,6 +995,60 @@ impl MobileAuthentication {
.map_err(Into::into)
}
pub fn unlock_totp(
&self,
passphrase: Option<String>,
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
self.authentication
.unlock_totp(
passphrase
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
)
.map(Into::into)
.map_err(Into::into)
}
pub fn totp_page(&self) -> Result<MobileTotpPage, MobileAuthenticationFfiError> {
self.authentication
.totp_page()
.map(Into::into)
.map_err(Into::into)
}
pub fn totp_detail(
&self,
path: String,
unix_seconds: u64,
) -> Result<MobileTotpDetail, MobileAuthenticationFfiError> {
self.authentication
.totp_detail(&path, unix_seconds)
.map(Into::into)
.map_err(Into::into)
}
pub fn copy_totp_code(
&self,
path: String,
unix_seconds: u64,
) -> Result<MobileEntryCopy, MobileAuthenticationFfiError> {
self.authentication
.copy_totp_code(&path, unix_seconds)
.map(Into::into)
.map_err(Into::into)
}
pub fn set_totp_watch_shared(
&self,
path: String,
shared: bool,
unix_seconds: u64,
) -> Result<MobileTotpDetail, MobileAuthenticationFfiError> {
self.authentication
.set_totp_watch_shared(&path, shared, unix_seconds)
.map(Into::into)
.map_err(Into::into)
}
pub fn begin_create_entry(
&self,
directory: String,