3068 lines
93 KiB
Rust
3068 lines
93 KiB
Rust
#![forbid(unsafe_code)]
|
|
#![deny(clippy::disallowed_types)]
|
|
|
|
//! Shared UniFFI contract for native phone presentation. Watch exports are opt-in.
|
|
|
|
mod private_storage;
|
|
pub use private_storage::*;
|
|
mod password_navigation;
|
|
pub use password_navigation::*;
|
|
|
|
use std::{
|
|
error::Error,
|
|
fmt,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
use ironstorage::{
|
|
config::{ConfigError, MobileAppearance as StorageMobileAppearance},
|
|
mobile::{
|
|
self, MobileIcon as StorageIcon, MobileShellState as StorageShellState,
|
|
MobileTab as StorageTab,
|
|
},
|
|
mobile_authentication::{
|
|
MobileAuthenticationError as StorageAuthenticationError,
|
|
MobileAuthenticationErrorKind as StorageAuthenticationErrorKind,
|
|
MobileAuthenticationState as StorageAuthenticationState,
|
|
MobileEntryCopy as StorageEntryCopy,
|
|
MobileEntryEditorRecovery as StorageEntryEditorRecovery,
|
|
MobileEntryPresentation as StorageEntryPresentation,
|
|
MobilePreferences as StorageMobilePreferences,
|
|
},
|
|
mobile_entry::{
|
|
MobileEntryEditorFieldKind as StorageEntryEditorFieldKind,
|
|
MobileEntryEditorInput as StorageEntryEditorInput,
|
|
MobileEntryEditorPage as StorageEntryEditorPage,
|
|
MobileEntryEditorSession as StorageEntryEditorSession, MobileEntryPage as StorageEntryPage,
|
|
MobileEntrySectionKind as StorageEntrySectionKind,
|
|
},
|
|
mobile_home::{
|
|
self, MobileHomeActionKind as StorageHomeActionKind,
|
|
MobileHomeChangeKind as StorageHomeChangeKind,
|
|
MobileHomeChangeStatus as StorageHomeChangeStatus, MobileHomeError as StorageHomeError,
|
|
MobileHomeErrorKind as StorageHomeErrorKind, MobileHomeFreshness as StorageHomeFreshness,
|
|
MobileHomePhase as StorageHomePhase,
|
|
},
|
|
mobile_key_transfer::{
|
|
MobileKeyTransferError as StorageKeyTransferError,
|
|
MobileKeyTransferKey as StorageKeyTransferKey,
|
|
MobileKeyTransferKind as StorageKeyTransferKind,
|
|
MobileKeyTransferProgress as StorageKeyTransferProgress,
|
|
},
|
|
mobile_mutation::{
|
|
MobileMutationAction as StorageMutationAction,
|
|
MobileMutationOutcome as StorageMutationOutcome, MobileMutationPlan as StorageMutationPlan,
|
|
MobileMutationRequest as StorageMutationRequest,
|
|
},
|
|
mobile_onboarding::{
|
|
self, MobileOnboardingError as StorageOnboardingError,
|
|
MobileOnboardingErrorKind as StorageOnboardingErrorKind,
|
|
MobileOnboardingPhase as StorageOnboardingPhase,
|
|
},
|
|
mobile_passwords::{
|
|
MobilePasswordError as StoragePasswordError,
|
|
MobilePasswordErrorKind as StoragePasswordErrorKind,
|
|
MobilePasswordRowKind as StoragePasswordRowKind,
|
|
},
|
|
mobile_totp::{
|
|
MobileTotpDetail as StorageTotpDetail,
|
|
MobileTotpDiscoveryPhase as StorageTotpDiscoveryPhase,
|
|
MobileTotpOperation as StorageTotpOperation, MobileTotpPage as StorageTotpPage,
|
|
},
|
|
};
|
|
|
|
#[cfg(feature = "watch")]
|
|
use ironstorage::{
|
|
mobile_authentication::MobileWatchPreferenceState as StorageWatchPreferenceState,
|
|
mobile_watch::{
|
|
MobileWatchSnapshotState as StorageWatchSnapshotState,
|
|
MobileWatchSnapshotStatus as StorageWatchSnapshotStatus,
|
|
WatchSnapshotTransfer as StorageWatchSnapshotTransfer,
|
|
},
|
|
};
|
|
|
|
uniffi::setup_scaffolding!();
|
|
|
|
mod android_keystore;
|
|
pub use android_keystore::*;
|
|
mod android_onboarding;
|
|
pub use android_onboarding::*;
|
|
mod platform;
|
|
pub use platform::*;
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_editor_process_recreated_notice() -> String {
|
|
ironstorage::mobile_entry::editor_process_recreated_notice().to_owned()
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileIcon {
|
|
Home,
|
|
Key,
|
|
Search,
|
|
Timer,
|
|
Settings,
|
|
Warning,
|
|
Branch,
|
|
Success,
|
|
Sync,
|
|
Protected,
|
|
Repository,
|
|
History,
|
|
Incoming,
|
|
Outgoing,
|
|
Recipients,
|
|
Signature,
|
|
File,
|
|
Merge,
|
|
Refresh,
|
|
Folder,
|
|
User,
|
|
Email,
|
|
Website,
|
|
Text,
|
|
Note,
|
|
}
|
|
|
|
impl From<StorageIcon> for MobileIcon {
|
|
fn from(icon: StorageIcon) -> Self {
|
|
match icon {
|
|
StorageIcon::Home => Self::Home,
|
|
StorageIcon::Key => Self::Key,
|
|
StorageIcon::Search => Self::Search,
|
|
StorageIcon::Timer => Self::Timer,
|
|
StorageIcon::Settings => Self::Settings,
|
|
StorageIcon::Warning => Self::Warning,
|
|
StorageIcon::Branch => Self::Branch,
|
|
StorageIcon::Success => Self::Success,
|
|
StorageIcon::Sync => Self::Sync,
|
|
StorageIcon::Protected => Self::Protected,
|
|
StorageIcon::Repository => Self::Repository,
|
|
StorageIcon::History => Self::History,
|
|
StorageIcon::Incoming => Self::Incoming,
|
|
StorageIcon::Outgoing => Self::Outgoing,
|
|
StorageIcon::Recipients => Self::Recipients,
|
|
StorageIcon::Signature => Self::Signature,
|
|
StorageIcon::File => Self::File,
|
|
StorageIcon::Merge => Self::Merge,
|
|
StorageIcon::Refresh => Self::Refresh,
|
|
StorageIcon::Folder => Self::Folder,
|
|
StorageIcon::User => Self::User,
|
|
StorageIcon::Email => Self::Email,
|
|
StorageIcon::Website => Self::Website,
|
|
StorageIcon::Text => Self::Text,
|
|
StorageIcon::Note => Self::Note,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileTab {
|
|
Home,
|
|
Passwords,
|
|
Search,
|
|
Totp,
|
|
Preferences,
|
|
}
|
|
|
|
impl From<StorageTab> for MobileTab {
|
|
fn from(tab: StorageTab) -> Self {
|
|
match tab {
|
|
StorageTab::Home => Self::Home,
|
|
StorageTab::Passwords => Self::Passwords,
|
|
StorageTab::Search => Self::Search,
|
|
StorageTab::Totp => Self::Totp,
|
|
StorageTab::Preferences => Self::Preferences,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileTab> for StorageTab {
|
|
fn from(tab: MobileTab) -> Self {
|
|
match tab {
|
|
MobileTab::Home => Self::Home,
|
|
MobileTab::Passwords => Self::Passwords,
|
|
MobileTab::Search => Self::Search,
|
|
MobileTab::Totp => Self::Totp,
|
|
MobileTab::Preferences => Self::Preferences,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileShellState {
|
|
Loading,
|
|
Empty,
|
|
Ready,
|
|
Locked,
|
|
Error,
|
|
Stale,
|
|
}
|
|
|
|
impl From<StorageShellState> for MobileShellState {
|
|
fn from(state: StorageShellState) -> Self {
|
|
match state {
|
|
StorageShellState::Loading => Self::Loading,
|
|
StorageShellState::Empty => Self::Empty,
|
|
StorageShellState::Ready => Self::Ready,
|
|
StorageShellState::Locked => Self::Locked,
|
|
StorageShellState::Error => Self::Error,
|
|
StorageShellState::Stale => Self::Stale,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileShellState> for StorageShellState {
|
|
fn from(state: MobileShellState) -> Self {
|
|
match state {
|
|
MobileShellState::Loading => Self::Loading,
|
|
MobileShellState::Empty => Self::Empty,
|
|
MobileShellState::Ready => Self::Ready,
|
|
MobileShellState::Locked => Self::Locked,
|
|
MobileShellState::Error => Self::Error,
|
|
MobileShellState::Stale => Self::Stale,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobilePage {
|
|
pub tab: MobileTab,
|
|
pub title: String,
|
|
pub icon: MobileIcon,
|
|
pub state: MobileShellState,
|
|
pub state_title: String,
|
|
pub state_detail: String,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileShell {
|
|
pub selected_tab: MobileTab,
|
|
pub pages: Vec<MobilePage>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeFreshness {
|
|
NeverRefreshed,
|
|
Cached,
|
|
Current,
|
|
}
|
|
|
|
impl From<StorageHomeFreshness> for MobileHomeFreshness {
|
|
fn from(freshness: StorageHomeFreshness) -> Self {
|
|
match freshness {
|
|
StorageHomeFreshness::NeverRefreshed => Self::NeverRefreshed,
|
|
StorageHomeFreshness::Cached => Self::Cached,
|
|
StorageHomeFreshness::Current => Self::Current,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeChangeKind {
|
|
PasswordEntry,
|
|
RecipientPolicy,
|
|
RecipientSignature,
|
|
RepositoryFile,
|
|
}
|
|
|
|
impl From<StorageHomeChangeKind> for MobileHomeChangeKind {
|
|
fn from(kind: StorageHomeChangeKind) -> Self {
|
|
match kind {
|
|
StorageHomeChangeKind::PasswordEntry => Self::PasswordEntry,
|
|
StorageHomeChangeKind::RecipientPolicy => Self::RecipientPolicy,
|
|
StorageHomeChangeKind::RecipientSignature => Self::RecipientSignature,
|
|
StorageHomeChangeKind::RepositoryFile => Self::RepositoryFile,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeChangeStatus {
|
|
Added,
|
|
Modified,
|
|
Deleted,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeActionKind {
|
|
Commit,
|
|
Fetch,
|
|
Pull,
|
|
Push,
|
|
}
|
|
|
|
impl From<StorageHomeActionKind> for MobileHomeActionKind {
|
|
fn from(kind: StorageHomeActionKind) -> Self {
|
|
match kind {
|
|
StorageHomeActionKind::Commit => Self::Commit,
|
|
StorageHomeActionKind::Fetch => Self::Fetch,
|
|
StorageHomeActionKind::Pull => Self::Pull,
|
|
StorageHomeActionKind::Push => Self::Push,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeAction {
|
|
pub kind: MobileHomeActionKind,
|
|
pub title: String,
|
|
pub icon: MobileIcon,
|
|
}
|
|
|
|
impl From<StorageHomeChangeStatus> for MobileHomeChangeStatus {
|
|
fn from(status: StorageHomeChangeStatus) -> Self {
|
|
match status {
|
|
StorageHomeChangeStatus::Added => Self::Added,
|
|
StorageHomeChangeStatus::Modified => Self::Modified,
|
|
StorageHomeChangeStatus::Deleted => Self::Deleted,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeSummaryRow {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub icon: MobileIcon,
|
|
pub actions: Vec<MobileHomeAction>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeChange {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub icon: MobileIcon,
|
|
pub kind: MobileHomeChangeKind,
|
|
pub status: MobileHomeChangeStatus,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeCommit {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub icon: MobileIcon,
|
|
pub timestamp: i64,
|
|
pub changes: Vec<MobileHomeChange>,
|
|
pub actions: Vec<MobileHomeAction>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeNotice {
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub icon: MobileIcon,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomePage {
|
|
pub remote_configured: bool,
|
|
pub freshness: MobileHomeFreshness,
|
|
pub refreshed_at: Option<i64>,
|
|
pub summaries: Vec<MobileHomeSummaryRow>,
|
|
pub incoming: Vec<MobileHomeCommit>,
|
|
pub outgoing: Vec<MobileHomeCommit>,
|
|
pub incoming_total: u32,
|
|
pub outgoing_total: u32,
|
|
pub notice: Option<MobileHomeNotice>,
|
|
}
|
|
|
|
impl From<mobile_home::MobileHomePage> for MobileHomePage {
|
|
fn from(page: mobile_home::MobileHomePage) -> Self {
|
|
Self {
|
|
remote_configured: page.remote_configured(),
|
|
freshness: page.freshness().into(),
|
|
refreshed_at: page.refreshed_at(),
|
|
summaries: page
|
|
.summaries()
|
|
.iter()
|
|
.map(|row| MobileHomeSummaryRow {
|
|
id: row.id().to_owned(),
|
|
title: row.title().to_owned(),
|
|
detail: row.detail().to_owned(),
|
|
icon: row.icon().into(),
|
|
actions: row.actions().iter().map(mobile_home_action).collect(),
|
|
})
|
|
.collect(),
|
|
incoming: page.incoming().iter().map(mobile_home_commit).collect(),
|
|
outgoing: page.outgoing().iter().map(mobile_home_commit).collect(),
|
|
incoming_total: u32::try_from(page.incoming_total()).unwrap_or(u32::MAX),
|
|
outgoing_total: u32::try_from(page.outgoing_total()).unwrap_or(u32::MAX),
|
|
notice: page.notice().map(|notice| MobileHomeNotice {
|
|
title: notice.title().to_owned(),
|
|
detail: notice.detail().to_owned(),
|
|
icon: notice.icon().into(),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mobile_home_commit(commit: &mobile_home::MobileHomeCommit) -> MobileHomeCommit {
|
|
MobileHomeCommit {
|
|
id: commit.id().to_owned(),
|
|
title: commit.title().to_owned(),
|
|
detail: commit.detail().to_owned(),
|
|
icon: commit.icon().into(),
|
|
timestamp: commit.timestamp(),
|
|
changes: commit
|
|
.changes()
|
|
.iter()
|
|
.map(|change| MobileHomeChange {
|
|
id: change.id().to_owned(),
|
|
title: change.title().to_owned(),
|
|
detail: change.detail().to_owned(),
|
|
icon: change.icon().into(),
|
|
kind: change.kind().into(),
|
|
status: change.status().into(),
|
|
})
|
|
.collect(),
|
|
actions: commit.actions().iter().map(mobile_home_action).collect(),
|
|
}
|
|
}
|
|
|
|
fn mobile_home_action(action: &mobile_home::MobileHomeAction) -> MobileHomeAction {
|
|
MobileHomeAction {
|
|
kind: action.kind().into(),
|
|
title: action.title().to_owned(),
|
|
icon: action.icon().into(),
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomePhase {
|
|
Validating,
|
|
Authenticating,
|
|
Receiving,
|
|
Integrating,
|
|
Sending,
|
|
Finishing,
|
|
}
|
|
|
|
impl From<StorageHomePhase> for MobileHomePhase {
|
|
fn from(phase: StorageHomePhase) -> Self {
|
|
match phase {
|
|
StorageHomePhase::Validating => Self::Validating,
|
|
StorageHomePhase::Authenticating => Self::Authenticating,
|
|
StorageHomePhase::Receiving => Self::Receiving,
|
|
StorageHomePhase::Integrating => Self::Integrating,
|
|
StorageHomePhase::Sending => Self::Sending,
|
|
StorageHomePhase::Finishing => Self::Finishing,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeProgress {
|
|
pub action: Option<MobileHomeActionKind>,
|
|
pub phase: MobileHomePhase,
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeErrorKind {
|
|
MissingConfiguration,
|
|
Configuration,
|
|
Authentication,
|
|
Conflict,
|
|
DirtyLocalChanges,
|
|
NoChanges,
|
|
Offline,
|
|
Interrupted,
|
|
SecureStorage,
|
|
Repository,
|
|
PartialProgress,
|
|
}
|
|
|
|
impl From<StorageHomeErrorKind> for MobileHomeErrorKind {
|
|
fn from(kind: StorageHomeErrorKind) -> Self {
|
|
match kind {
|
|
StorageHomeErrorKind::MissingConfiguration => Self::MissingConfiguration,
|
|
StorageHomeErrorKind::Configuration => Self::Configuration,
|
|
StorageHomeErrorKind::Authentication => Self::Authentication,
|
|
StorageHomeErrorKind::Conflict => Self::Conflict,
|
|
StorageHomeErrorKind::DirtyLocalChanges => Self::DirtyLocalChanges,
|
|
StorageHomeErrorKind::NoChanges => Self::NoChanges,
|
|
StorageHomeErrorKind::Offline => Self::Offline,
|
|
StorageHomeErrorKind::Interrupted => Self::Interrupted,
|
|
StorageHomeErrorKind::SecureStorage => Self::SecureStorage,
|
|
StorageHomeErrorKind::Repository => Self::Repository,
|
|
StorageHomeErrorKind::PartialProgress => Self::PartialProgress,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Error)]
|
|
pub enum MobileHomeFfiError {
|
|
Failed {
|
|
kind: MobileHomeErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Debug for MobileHomeFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter.write_str("MobileHomeFfiError([REDACTED])")
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MobileHomeFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Debug::fmt(self, formatter)
|
|
}
|
|
}
|
|
|
|
impl Error for MobileHomeFfiError {}
|
|
|
|
impl From<StorageHomeError> for MobileHomeFfiError {
|
|
fn from(error: StorageHomeError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileHomeOperation {
|
|
operation: mobile_home::MobileHomeOperation,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileHomeOperation {
|
|
pub fn activity_known(&self) -> bool {
|
|
self.operation.activity_known()
|
|
}
|
|
pub fn identity(&self) -> Result<MobileGitIdentity, MobileHomeFfiError> {
|
|
self.operation
|
|
.identity()
|
|
.map(|identity| MobileGitIdentity {
|
|
name: identity.name().to_owned(),
|
|
email: identity.email().to_owned(),
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
pub fn progress(&self) -> MobileHomeProgress {
|
|
let progress = self.operation.progress();
|
|
MobileHomeProgress {
|
|
action: progress.action().map(Into::into),
|
|
phase: progress.phase().into(),
|
|
title: progress.title().to_owned(),
|
|
detail: progress.detail().to_owned(),
|
|
}
|
|
}
|
|
|
|
pub fn cached(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.cached().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn refresh_if_stale(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation
|
|
.refresh_if_stale()
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn commit(&self, message: String) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation
|
|
.commit(&message)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn fetch(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.fetch().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn pull(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.pull().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn push(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.push().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn sync(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.sync().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn refresh_gesture(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation
|
|
.refresh_gesture()
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.operation.cancel();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobilePasswordRowKind {
|
|
Directory,
|
|
Entry,
|
|
}
|
|
|
|
impl From<StoragePasswordRowKind> for MobilePasswordRowKind {
|
|
fn from(kind: StoragePasswordRowKind) -> Self {
|
|
match kind {
|
|
StoragePasswordRowKind::Directory => Self::Directory,
|
|
StoragePasswordRowKind::Entry => Self::Entry,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobilePasswordRow {
|
|
pub id: String,
|
|
pub path: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub icon: MobileIcon,
|
|
pub kind: MobilePasswordRowKind,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobilePasswordPage {
|
|
pub id: String,
|
|
pub path: String,
|
|
pub title: String,
|
|
pub rows: Vec<MobilePasswordRow>,
|
|
}
|
|
|
|
impl From<ironstorage::mobile_passwords::MobilePasswordPage> for MobilePasswordPage {
|
|
fn from(page: ironstorage::mobile_passwords::MobilePasswordPage) -> Self {
|
|
Self {
|
|
id: page.id().to_owned(),
|
|
path: page.path().to_owned(),
|
|
title: page.title().to_owned(),
|
|
rows: page
|
|
.rows()
|
|
.iter()
|
|
.map(|row| MobilePasswordRow {
|
|
id: row.id().to_owned(),
|
|
path: row.path().to_owned(),
|
|
title: row.title().to_owned(),
|
|
detail: row.detail().to_owned(),
|
|
icon: row.icon().into(),
|
|
kind: row.kind().into(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobilePasswordErrorKind {
|
|
MissingConfiguration,
|
|
Configuration,
|
|
InvalidPath,
|
|
DirectoryMissing,
|
|
Repository,
|
|
}
|
|
|
|
impl From<StoragePasswordErrorKind> for MobilePasswordErrorKind {
|
|
fn from(kind: StoragePasswordErrorKind) -> Self {
|
|
match kind {
|
|
StoragePasswordErrorKind::MissingConfiguration => Self::MissingConfiguration,
|
|
StoragePasswordErrorKind::Configuration => Self::Configuration,
|
|
StoragePasswordErrorKind::InvalidPath => Self::InvalidPath,
|
|
StoragePasswordErrorKind::DirectoryMissing => Self::DirectoryMissing,
|
|
StoragePasswordErrorKind::Repository => Self::Repository,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Error)]
|
|
pub enum MobilePasswordFfiError {
|
|
Failed {
|
|
kind: MobilePasswordErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Debug for MobilePasswordFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter.write_str("MobilePasswordFfiError([REDACTED])")
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MobilePasswordFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Debug::fmt(self, formatter)
|
|
}
|
|
}
|
|
|
|
impl Error for MobilePasswordFfiError {}
|
|
|
|
impl From<StoragePasswordError> for MobilePasswordFfiError {
|
|
fn from(error: StoragePasswordError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileAuthenticationErrorKind {
|
|
PassphraseRequired,
|
|
InvalidPassphrase,
|
|
Cancelled,
|
|
BiometryUnavailable,
|
|
Configuration,
|
|
KeyMaterial,
|
|
Entry,
|
|
SecureStorage,
|
|
Expired,
|
|
Conflict,
|
|
}
|
|
|
|
impl From<StorageAuthenticationErrorKind> for MobileAuthenticationErrorKind {
|
|
fn from(kind: StorageAuthenticationErrorKind) -> Self {
|
|
match kind {
|
|
StorageAuthenticationErrorKind::PassphraseRequired => Self::PassphraseRequired,
|
|
StorageAuthenticationErrorKind::InvalidPassphrase => Self::InvalidPassphrase,
|
|
StorageAuthenticationErrorKind::Cancelled => Self::Cancelled,
|
|
StorageAuthenticationErrorKind::BiometryUnavailable => Self::BiometryUnavailable,
|
|
StorageAuthenticationErrorKind::Configuration => Self::Configuration,
|
|
StorageAuthenticationErrorKind::KeyMaterial => Self::KeyMaterial,
|
|
StorageAuthenticationErrorKind::Entry => Self::Entry,
|
|
StorageAuthenticationErrorKind::SecureStorage => Self::SecureStorage,
|
|
StorageAuthenticationErrorKind::Expired => Self::Expired,
|
|
StorageAuthenticationErrorKind::Conflict => Self::Conflict,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileAuthenticationState {
|
|
pub unlocked: bool,
|
|
pub biometric_unlock_enabled: bool,
|
|
pub remaining_seconds: u64,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileAppearance {
|
|
System,
|
|
Light,
|
|
Dark,
|
|
}
|
|
|
|
impl From<StorageMobileAppearance> for MobileAppearance {
|
|
fn from(appearance: StorageMobileAppearance) -> Self {
|
|
match appearance {
|
|
StorageMobileAppearance::System => Self::System,
|
|
StorageMobileAppearance::Light => Self::Light,
|
|
StorageMobileAppearance::Dark => Self::Dark,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileAppearance> for StorageMobileAppearance {
|
|
fn from(appearance: MobileAppearance) -> Self {
|
|
match appearance {
|
|
MobileAppearance::System => Self::System,
|
|
MobileAppearance::Light => Self::Light,
|
|
MobileAppearance::Dark => Self::Dark,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileWatchPreferenceState {
|
|
Unsupported,
|
|
NotPaired,
|
|
AppNotInstalled,
|
|
Ready,
|
|
Pending,
|
|
Delivered,
|
|
Current,
|
|
Failed,
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
impl From<StorageWatchPreferenceState> for MobileWatchPreferenceState {
|
|
fn from(state: StorageWatchPreferenceState) -> Self {
|
|
match state {
|
|
StorageWatchPreferenceState::Unsupported => Self::Unsupported,
|
|
StorageWatchPreferenceState::NotPaired => Self::NotPaired,
|
|
StorageWatchPreferenceState::AppNotInstalled => Self::AppNotInstalled,
|
|
StorageWatchPreferenceState::Ready => Self::Ready,
|
|
StorageWatchPreferenceState::Pending => Self::Pending,
|
|
StorageWatchPreferenceState::Delivered => Self::Delivered,
|
|
StorageWatchPreferenceState::Current => Self::Current,
|
|
StorageWatchPreferenceState::Failed => Self::Failed,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobilePreferences {
|
|
pub sync_configured: bool,
|
|
pub repository_title: String,
|
|
pub repository_url: String,
|
|
pub server_title: String,
|
|
pub server_identity: String,
|
|
pub application_account: Option<String>,
|
|
pub default_key_title: String,
|
|
pub default_key_fingerprint: String,
|
|
pub authentication_timeout_seconds: u64,
|
|
pub biometric_unlock_enabled: bool,
|
|
pub appearance: MobileAppearance,
|
|
#[cfg(feature = "watch")]
|
|
pub watch_state: MobileWatchPreferenceState,
|
|
#[cfg(feature = "watch")]
|
|
pub watch_title: String,
|
|
#[cfg(feature = "watch")]
|
|
pub watch_detail: String,
|
|
}
|
|
|
|
impl From<StorageMobilePreferences> for MobilePreferences {
|
|
fn from(preferences: StorageMobilePreferences) -> Self {
|
|
Self {
|
|
sync_configured: preferences.sync_configured(),
|
|
repository_title: preferences.repository_title().to_owned(),
|
|
repository_url: preferences.repository_url().to_owned(),
|
|
server_title: preferences.server_title().to_owned(),
|
|
server_identity: preferences.server_identity().to_owned(),
|
|
application_account: preferences.application_account().map(str::to_owned),
|
|
default_key_title: preferences.default_key_title().to_owned(),
|
|
default_key_fingerprint: preferences.default_key_fingerprint().to_owned(),
|
|
authentication_timeout_seconds: preferences.authentication_timeout_seconds(),
|
|
biometric_unlock_enabled: preferences.biometric_unlock_enabled(),
|
|
appearance: preferences.appearance().into(),
|
|
#[cfg(feature = "watch")]
|
|
watch_state: preferences.watch_state().into(),
|
|
#[cfg(feature = "watch")]
|
|
watch_title: preferences.watch_title().to_owned(),
|
|
#[cfg(feature = "watch")]
|
|
watch_detail: preferences.watch_detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileGitIdentity {
|
|
pub name: String,
|
|
pub email: String,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpCacheStatus {
|
|
pub entries: u32,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileMutationAction {
|
|
Move,
|
|
Copy,
|
|
Delete,
|
|
}
|
|
|
|
impl From<StorageMutationAction> for MobileMutationAction {
|
|
fn from(action: StorageMutationAction) -> Self {
|
|
match action {
|
|
StorageMutationAction::Move => Self::Move,
|
|
StorageMutationAction::Copy => Self::Copy,
|
|
StorageMutationAction::Delete => Self::Delete,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileMutationAction> for StorageMutationAction {
|
|
fn from(action: MobileMutationAction) -> Self {
|
|
match action {
|
|
MobileMutationAction::Move => Self::Move,
|
|
MobileMutationAction::Copy => Self::Copy,
|
|
MobileMutationAction::Delete => Self::Delete,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationDestination {
|
|
pub path: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub requires_overwrite: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationPlan {
|
|
pub action: MobileMutationAction,
|
|
pub source: String,
|
|
pub source_title: String,
|
|
pub revision: String,
|
|
pub destinations: Vec<MobileMutationDestination>,
|
|
pub has_open_editor: bool,
|
|
pub has_dirty_editor: bool,
|
|
}
|
|
|
|
impl From<StorageMutationPlan> for MobileMutationPlan {
|
|
fn from(plan: StorageMutationPlan) -> Self {
|
|
Self {
|
|
action: plan.action().into(),
|
|
source: plan.source().to_owned(),
|
|
source_title: plan.source_title().to_owned(),
|
|
revision: plan.revision().to_owned(),
|
|
destinations: plan
|
|
.destinations()
|
|
.iter()
|
|
.map(|destination| MobileMutationDestination {
|
|
path: destination.path().to_owned(),
|
|
title: destination.title().to_owned(),
|
|
detail: destination.detail().to_owned(),
|
|
requires_overwrite: destination.requires_overwrite(),
|
|
})
|
|
.collect(),
|
|
has_open_editor: plan.has_open_editor(),
|
|
has_dirty_editor: plan.has_dirty_editor(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTreeMutationDestination {
|
|
pub path: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub requires_overwrite: bool,
|
|
pub depth: u32,
|
|
pub unavailable: Option<String>,
|
|
pub collision_targets: Vec<String>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTreeMutationPlan {
|
|
pub action: MobileMutationAction,
|
|
pub source: String,
|
|
pub source_title: String,
|
|
pub kind: MobilePasswordRowKind,
|
|
pub revision: String,
|
|
pub entries: u32,
|
|
pub destinations: Vec<MobileTreeMutationDestination>,
|
|
pub has_open_editor: bool,
|
|
pub has_dirty_editor: bool,
|
|
}
|
|
|
|
impl From<StorageMutationPlan> for MobileTreeMutationPlan {
|
|
fn from(plan: StorageMutationPlan) -> Self {
|
|
Self {
|
|
action: plan.action().into(),
|
|
source: plan.source().to_owned(),
|
|
source_title: plan.source_title().to_owned(),
|
|
kind: if plan.is_directory() {
|
|
MobilePasswordRowKind::Directory
|
|
} else {
|
|
MobilePasswordRowKind::Entry
|
|
},
|
|
revision: plan.revision().to_owned(),
|
|
entries: plan.entries(),
|
|
destinations: plan
|
|
.destinations()
|
|
.iter()
|
|
.map(|destination| MobileTreeMutationDestination {
|
|
path: destination.path().to_owned(),
|
|
title: destination.title().to_owned(),
|
|
detail: destination.detail().to_owned(),
|
|
requires_overwrite: destination.requires_overwrite(),
|
|
depth: destination.depth(),
|
|
unavailable: destination.unavailable().map(str::to_owned),
|
|
collision_targets: destination.collision_targets().to_vec(),
|
|
})
|
|
.collect(),
|
|
has_open_editor: plan.has_open_editor(),
|
|
has_dirty_editor: plan.has_dirty_editor(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileNavigationMutationOutcome {
|
|
pub result: MobileMutationOutcome,
|
|
pub navigation: MobilePasswordNavigation,
|
|
pub discarded_editors: Vec<u64>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationRequest {
|
|
pub action: MobileMutationAction,
|
|
pub source: String,
|
|
pub revision: String,
|
|
pub destination: Option<String>,
|
|
pub confirmed: bool,
|
|
pub overwrite: bool,
|
|
pub discard_editor: bool,
|
|
}
|
|
|
|
impl From<MobileMutationRequest> for StorageMutationRequest {
|
|
fn from(request: MobileMutationRequest) -> Self {
|
|
Self {
|
|
action: request.action.into(),
|
|
source: request.source,
|
|
revision: request.revision,
|
|
destination: request.destination,
|
|
confirmed: request.confirmed,
|
|
overwrite: request.overwrite,
|
|
discard_editor: request.discard_editor,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationOutcome {
|
|
pub action: MobileMutationAction,
|
|
pub source: String,
|
|
pub destination: Option<String>,
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
impl From<StorageMutationOutcome> for MobileMutationOutcome {
|
|
fn from(outcome: StorageMutationOutcome) -> Self {
|
|
Self {
|
|
action: outcome.action().into(),
|
|
source: outcome.source().to_owned(),
|
|
destination: outcome.destination().map(str::to_owned),
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileEntrySectionKind {
|
|
Password,
|
|
Details,
|
|
OneTimePassword,
|
|
Notes,
|
|
}
|
|
|
|
impl From<StorageEntrySectionKind> for MobileEntrySectionKind {
|
|
fn from(kind: StorageEntrySectionKind) -> Self {
|
|
match kind {
|
|
StorageEntrySectionKind::Password => Self::Password,
|
|
StorageEntrySectionKind::Details => Self::Details,
|
|
StorageEntrySectionKind::OneTimePassword => Self::OneTimePassword,
|
|
StorageEntrySectionKind::Notes => Self::Notes,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryField {
|
|
pub id: u64,
|
|
pub label: String,
|
|
pub icon: MobileIcon,
|
|
pub value: Option<String>,
|
|
pub masked_value: String,
|
|
pub detail: Option<String>,
|
|
pub diagnostic: Option<String>,
|
|
pub sensitive: bool,
|
|
pub multiline: bool,
|
|
pub selectable: bool,
|
|
pub editable: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntrySection {
|
|
pub kind: MobileEntrySectionKind,
|
|
pub title: String,
|
|
pub fields: Vec<MobileEntryField>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryPage {
|
|
pub id: String,
|
|
pub revision: String,
|
|
pub title: String,
|
|
pub sections: Vec<MobileEntrySection>,
|
|
}
|
|
|
|
impl From<StorageEntryPage> for MobileEntryPage {
|
|
fn from(page: StorageEntryPage) -> Self {
|
|
Self {
|
|
id: page.id().to_owned(),
|
|
revision: page.revision().to_owned(),
|
|
title: page.title().to_owned(),
|
|
sections: page
|
|
.sections()
|
|
.iter()
|
|
.map(|section| MobileEntrySection {
|
|
kind: section.kind().into(),
|
|
title: section.title().to_owned(),
|
|
fields: section
|
|
.fields()
|
|
.iter()
|
|
.map(|field| MobileEntryField {
|
|
id: field.id(),
|
|
label: field.label().to_owned(),
|
|
icon: field.icon().into(),
|
|
value: field.value().map(str::to_owned),
|
|
masked_value: field.masked_value().to_owned(),
|
|
detail: field.detail().map(str::to_owned),
|
|
diagnostic: field.diagnostic().map(str::to_owned),
|
|
sensitive: field.sensitive(),
|
|
multiline: field.multiline(),
|
|
selectable: field.selectable(),
|
|
editable: field.editable(),
|
|
})
|
|
.collect(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryCopy {
|
|
pub value: String,
|
|
pub timeout_seconds: u64,
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileWatchSnapshotState {
|
|
Unavailable,
|
|
Unpaired,
|
|
Pending,
|
|
Delivered,
|
|
Current,
|
|
Failed,
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
impl From<StorageWatchSnapshotState> for MobileWatchSnapshotState {
|
|
fn from(state: StorageWatchSnapshotState) -> Self {
|
|
match state {
|
|
StorageWatchSnapshotState::Unavailable => Self::Unavailable,
|
|
StorageWatchSnapshotState::Unpaired => Self::Unpaired,
|
|
StorageWatchSnapshotState::Pending => Self::Pending,
|
|
StorageWatchSnapshotState::Delivered => Self::Delivered,
|
|
StorageWatchSnapshotState::Current => Self::Current,
|
|
StorageWatchSnapshotState::Failed => Self::Failed,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileWatchSnapshotStatus {
|
|
pub state: MobileWatchSnapshotState,
|
|
pub revision: Option<u64>,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
impl From<&StorageWatchSnapshotStatus> for MobileWatchSnapshotStatus {
|
|
fn from(status: &StorageWatchSnapshotStatus) -> Self {
|
|
Self {
|
|
state: status.state().into(),
|
|
revision: status.revision(),
|
|
detail: status.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileWatchSnapshotTransfer {
|
|
pub revision: u64,
|
|
pub selected_entries: u32,
|
|
pub snapshot: Vec<u8>,
|
|
pub delivered_receipt: Vec<u8>,
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
impl From<StorageWatchSnapshotTransfer> for MobileWatchSnapshotTransfer {
|
|
fn from(transfer: StorageWatchSnapshotTransfer) -> Self {
|
|
Self {
|
|
revision: transfer.revision(),
|
|
selected_entries: transfer.selected_entries(),
|
|
snapshot: transfer.snapshot().expose().to_vec(),
|
|
delivered_receipt: transfer.delivered_receipt().to_vec(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpRow {
|
|
pub path: String,
|
|
pub issuer: Option<String>,
|
|
pub account: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
#[cfg(feature = "watch")]
|
|
pub shared_with_watch: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpPage {
|
|
pub rows: Vec<MobileTotpRow>,
|
|
pub unavailable_entries: u32,
|
|
pub cache_notice: Option<String>,
|
|
#[cfg(feature = "watch")]
|
|
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(),
|
|
#[cfg(feature = "watch")]
|
|
shared_with_watch: row.shared_with_watch(),
|
|
})
|
|
.collect(),
|
|
unavailable_entries: page.unavailable_entries(),
|
|
cache_notice: page.cache_notice().map(str::to_owned),
|
|
#[cfg(feature = "watch")]
|
|
watch: page.watch().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileTotpDiscoveryPhase {
|
|
Preparing,
|
|
Inspecting,
|
|
Saving,
|
|
Complete,
|
|
Cancelled,
|
|
}
|
|
|
|
impl From<StorageTotpDiscoveryPhase> for MobileTotpDiscoveryPhase {
|
|
fn from(phase: StorageTotpDiscoveryPhase) -> Self {
|
|
match phase {
|
|
StorageTotpDiscoveryPhase::Preparing => Self::Preparing,
|
|
StorageTotpDiscoveryPhase::Inspecting => Self::Inspecting,
|
|
StorageTotpDiscoveryPhase::Saving => Self::Saving,
|
|
StorageTotpDiscoveryPhase::Complete => Self::Complete,
|
|
StorageTotpDiscoveryPhase::Cancelled => Self::Cancelled,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpDiscoveryProgress {
|
|
pub phase: MobileTotpDiscoveryPhase,
|
|
pub total: u32,
|
|
pub inspected: u32,
|
|
pub cache_hits: u32,
|
|
pub matches: u32,
|
|
pub unavailable: u32,
|
|
}
|
|
|
|
impl From<ironstorage::mobile_totp::MobileTotpDiscoveryProgress> for MobileTotpDiscoveryProgress {
|
|
fn from(progress: ironstorage::mobile_totp::MobileTotpDiscoveryProgress) -> Self {
|
|
Self {
|
|
phase: progress.phase().into(),
|
|
total: progress.total(),
|
|
inspected: progress.inspected(),
|
|
cache_hits: progress.cache_hits(),
|
|
matches: progress.matches(),
|
|
unavailable: progress.unavailable(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpRefreshResult {
|
|
pub progress: MobileTotpDiscoveryProgress,
|
|
pub cache_notice: Option<String>,
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileTotpOperation {
|
|
operation: StorageTotpOperation,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileTotpOperation {
|
|
pub fn progress(&self) -> MobileTotpDiscoveryProgress {
|
|
self.operation.progress().into()
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.operation.cancel();
|
|
}
|
|
}
|
|
|
|
#[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 remaining_seconds: u64,
|
|
pub period: u64,
|
|
#[cfg(feature = "watch")]
|
|
pub shared_with_watch: bool,
|
|
#[cfg(feature = "watch")]
|
|
pub watch: MobileWatchSnapshotStatus,
|
|
}
|
|
|
|
#[derive(Clone, Copy, uniffi::Enum)]
|
|
pub enum MobileOtpCodeKind {
|
|
Totp,
|
|
Hotp,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileOtpCodePresentation {
|
|
pub document_revision: String,
|
|
pub kind: MobileOtpCodeKind,
|
|
pub issuer: Option<String>,
|
|
pub account: String,
|
|
pub code: Option<String>,
|
|
pub grouped_code: Option<String>,
|
|
pub period: Option<u64>,
|
|
pub valid_until: Option<u64>,
|
|
pub remaining_seconds: Option<u64>,
|
|
pub counter: Option<u64>,
|
|
pub next_counter: Option<u64>,
|
|
}
|
|
|
|
impl From<ironstorage::mobile_totp::MobileOtpCodePresentation> for MobileOtpCodePresentation {
|
|
fn from(value: ironstorage::mobile_totp::MobileOtpCodePresentation) -> Self {
|
|
let text = |bytes: ironstorage::repository::SecretBytes| {
|
|
String::from_utf8(bytes.expose().to_vec())
|
|
.expect("storage-generated OTP codes are ASCII")
|
|
};
|
|
Self {
|
|
document_revision: value.document_revision,
|
|
kind: match value.kind {
|
|
ironstorage::otp::OtpKind::Totp => MobileOtpCodeKind::Totp,
|
|
ironstorage::otp::OtpKind::Hotp => MobileOtpCodeKind::Hotp,
|
|
},
|
|
issuer: value.issuer,
|
|
account: value.account,
|
|
code: value.code.map(text),
|
|
grouped_code: value.grouped_code.map(text),
|
|
period: value.period,
|
|
valid_until: value.valid_until,
|
|
remaining_seconds: value.remaining_seconds,
|
|
counter: value.counter,
|
|
next_counter: value.next_counter,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_otp_window_current(valid_until: u64, period: u64, unix_seconds: u64) -> bool {
|
|
ironstorage::otp::OtpCodeValidity::Timed {
|
|
valid_until,
|
|
period,
|
|
}
|
|
.is_current(unix_seconds)
|
|
}
|
|
|
|
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(),
|
|
remaining_seconds: detail.remaining_seconds(),
|
|
period: detail.period(),
|
|
#[cfg(feature = "watch")]
|
|
shared_with_watch: detail.shared_with_watch(),
|
|
#[cfg(feature = "watch")]
|
|
watch: detail.watch().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryPresentation {
|
|
pub page: MobileEntryPage,
|
|
pub totp: Option<MobileTotpDetail>,
|
|
pub cache_notice: Option<String>,
|
|
}
|
|
|
|
impl From<StorageEntryPresentation> for MobileEntryPresentation {
|
|
fn from(presentation: StorageEntryPresentation) -> Self {
|
|
let (page, totp, cache_notice) = presentation.into_parts();
|
|
Self {
|
|
page: page.into(),
|
|
totp: totp.map(Into::into),
|
|
cache_notice,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileEntryEditorFieldKind {
|
|
Password,
|
|
Field,
|
|
Note,
|
|
OtpUri,
|
|
}
|
|
|
|
impl From<StorageEntryEditorFieldKind> for MobileEntryEditorFieldKind {
|
|
fn from(kind: StorageEntryEditorFieldKind) -> Self {
|
|
match kind {
|
|
StorageEntryEditorFieldKind::Password => Self::Password,
|
|
StorageEntryEditorFieldKind::Field => Self::Field,
|
|
StorageEntryEditorFieldKind::Note => Self::Note,
|
|
StorageEntryEditorFieldKind::OtpUri => Self::OtpUri,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileEntryEditorFieldKind> for StorageEntryEditorFieldKind {
|
|
fn from(kind: MobileEntryEditorFieldKind) -> Self {
|
|
match kind {
|
|
MobileEntryEditorFieldKind::Password => Self::Password,
|
|
MobileEntryEditorFieldKind::Field => Self::Field,
|
|
MobileEntryEditorFieldKind::Note => Self::Note,
|
|
MobileEntryEditorFieldKind::OtpUri => Self::OtpUri,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorField {
|
|
pub id: u64,
|
|
pub kind: MobileEntryEditorFieldKind,
|
|
pub name: Option<String>,
|
|
pub label: String,
|
|
pub icon: MobileIcon,
|
|
pub value: Option<String>,
|
|
pub masked_value: String,
|
|
pub diagnostic: Option<String>,
|
|
pub sensitive: bool,
|
|
pub multiline: bool,
|
|
pub name_editable: bool,
|
|
pub removable: bool,
|
|
pub reorderable: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorPage {
|
|
pub path: String,
|
|
pub title: String,
|
|
pub fields: Vec<MobileEntryEditorField>,
|
|
pub dirty: bool,
|
|
pub creating: bool,
|
|
pub default_password_length: u32,
|
|
pub maximum_password_length: u32,
|
|
}
|
|
|
|
impl From<&StorageEntryEditorPage> for MobileEntryEditorPage {
|
|
fn from(page: &StorageEntryEditorPage) -> Self {
|
|
Self {
|
|
path: page.path().to_owned(),
|
|
title: page.title().to_owned(),
|
|
fields: page
|
|
.fields()
|
|
.iter()
|
|
.map(|field| MobileEntryEditorField {
|
|
id: field.id(),
|
|
kind: field.kind().into(),
|
|
name: field.name().map(str::to_owned),
|
|
label: field.label().to_owned(),
|
|
icon: field.icon().into(),
|
|
value: field.value().map(str::to_owned),
|
|
masked_value: field.masked_value().to_owned(),
|
|
diagnostic: field.diagnostic().map(str::to_owned),
|
|
sensitive: field.sensitive(),
|
|
multiline: field.multiline(),
|
|
name_editable: field.name_editable(),
|
|
removable: field.removable(),
|
|
reorderable: field.reorderable(),
|
|
})
|
|
.collect(),
|
|
dirty: page.dirty(),
|
|
creating: page.creating(),
|
|
default_password_length: page.default_password_length(),
|
|
maximum_password_length: page.maximum_password_length(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorSession {
|
|
pub id: u64,
|
|
pub page: MobileEntryEditorPage,
|
|
}
|
|
|
|
impl From<StorageEntryEditorSession> for MobileEntryEditorSession {
|
|
fn from(session: StorageEntryEditorSession) -> Self {
|
|
Self {
|
|
id: session.id(),
|
|
page: session.page().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorInput {
|
|
pub id: u64,
|
|
pub name: Option<String>,
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
impl From<MobileEntryEditorInput> for StorageEntryEditorInput {
|
|
fn from(input: MobileEntryEditorInput) -> Self {
|
|
Self::new(input.id, input.name, input.value)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorRecovery {
|
|
pub session: MobileEntryEditorSession,
|
|
pub inputs: Option<Vec<MobileEntryEditorInput>>,
|
|
}
|
|
|
|
impl From<StorageEntryEditorRecovery> for MobileEntryEditorRecovery {
|
|
fn from(recovery: StorageEntryEditorRecovery) -> Self {
|
|
Self {
|
|
session: recovery.session.into(),
|
|
inputs: recovery.inputs.map(|inputs| {
|
|
inputs
|
|
.into_iter()
|
|
.map(|input| {
|
|
let (id, name, value) = input.into_parts();
|
|
MobileEntryEditorInput { id, name, value }
|
|
})
|
|
.collect()
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<StorageEntryCopy> for MobileEntryCopy {
|
|
fn from(copy: StorageEntryCopy) -> Self {
|
|
Self {
|
|
value: copy.value().to_owned(),
|
|
timeout_seconds: copy.timeout_seconds(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<StorageAuthenticationState> for MobileAuthenticationState {
|
|
fn from(state: StorageAuthenticationState) -> Self {
|
|
Self {
|
|
unlocked: state.unlocked(),
|
|
biometric_unlock_enabled: state.biometric_unlock_enabled(),
|
|
remaining_seconds: state.remaining_seconds(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileKeyTransferKind {
|
|
Public,
|
|
Private,
|
|
}
|
|
|
|
impl From<StorageKeyTransferKind> for MobileKeyTransferKind {
|
|
fn from(kind: StorageKeyTransferKind) -> Self {
|
|
match kind {
|
|
StorageKeyTransferKind::Public => Self::Public,
|
|
StorageKeyTransferKind::Private => Self::Private,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileKeyTransferKind> for StorageKeyTransferKind {
|
|
fn from(kind: MobileKeyTransferKind) -> Self {
|
|
match kind {
|
|
MobileKeyTransferKind::Public => Self::Public,
|
|
MobileKeyTransferKind::Private => Self::Private,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferKey {
|
|
pub fingerprint: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub kind: MobileKeyTransferKind,
|
|
pub requires_passphrase: bool,
|
|
}
|
|
|
|
impl From<&StorageKeyTransferKey> for MobileKeyTransferKey {
|
|
fn from(key: &StorageKeyTransferKey) -> Self {
|
|
Self {
|
|
fingerprint: key.fingerprint().to_owned(),
|
|
title: key.title().to_owned(),
|
|
detail: key.detail().to_owned(),
|
|
kind: key.kind().into(),
|
|
requires_passphrase: key.requires_passphrase(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferFrame {
|
|
pub sequence: u32,
|
|
pub total: u32,
|
|
pub width: u32,
|
|
pub modules: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferExport {
|
|
pub key: MobileKeyTransferKey,
|
|
pub frames: Vec<MobileKeyTransferFrame>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferProgress {
|
|
pub received: u32,
|
|
pub total: u32,
|
|
pub duplicate: bool,
|
|
pub key: Option<MobileKeyTransferKey>,
|
|
}
|
|
|
|
impl From<StorageKeyTransferProgress> for MobileKeyTransferProgress {
|
|
fn from(progress: StorageKeyTransferProgress) -> Self {
|
|
Self {
|
|
received: u32::try_from(progress.received()).unwrap_or(u32::MAX),
|
|
total: u32::try_from(progress.total()).unwrap_or(u32::MAX),
|
|
duplicate: progress.duplicate(),
|
|
key: progress.key().map(Into::into),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferOutcome {
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(uniffi::Error)]
|
|
pub enum MobileKeyTransferFfiError {
|
|
Failed {
|
|
kind: MobileKeyTransferErrorKind,
|
|
message: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileKeyTransferErrorKind {
|
|
ExistingKey,
|
|
Failed,
|
|
}
|
|
|
|
impl fmt::Debug for MobileKeyTransferFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter.write_str("MobileKeyTransferFfiError([REDACTED])")
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MobileKeyTransferFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Debug::fmt(self, formatter)
|
|
}
|
|
}
|
|
|
|
impl Error for MobileKeyTransferFfiError {}
|
|
|
|
impl From<StorageKeyTransferError> for MobileKeyTransferFfiError {
|
|
fn from(error: StorageKeyTransferError) -> Self {
|
|
let kind = match &error {
|
|
StorageKeyTransferError::ExistingKey => MobileKeyTransferErrorKind::ExistingKey,
|
|
StorageKeyTransferError::LocalSetup(error)
|
|
if error.kind() == StorageOnboardingErrorKind::ExistingKey =>
|
|
{
|
|
MobileKeyTransferErrorKind::ExistingKey
|
|
}
|
|
_ => MobileKeyTransferErrorKind::Failed,
|
|
};
|
|
Self::Failed {
|
|
kind,
|
|
message: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileKeyTransfer {
|
|
service: ironstorage::mobile_key_transfer::MobileKeyTransferService,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileKeyTransfer {
|
|
pub fn export_visible(&self) -> bool {
|
|
self.service.export_visible()
|
|
}
|
|
|
|
pub fn keys(&self) -> Vec<MobileKeyTransferKey> {
|
|
self.service.keys().iter().map(Into::into).collect()
|
|
}
|
|
|
|
pub fn export(
|
|
&self,
|
|
fingerprint: String,
|
|
kind: MobileKeyTransferKind,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileKeyTransferExport, MobileKeyTransferFfiError> {
|
|
let exported = self.service.export(
|
|
&fingerprint,
|
|
kind.into(),
|
|
passphrase.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)?;
|
|
let frames = exported
|
|
.frames()
|
|
.iter()
|
|
.map(|frame| {
|
|
let matrix = frame.matrix();
|
|
let mut modules = Vec::with_capacity(matrix.width() * matrix.width());
|
|
for y in 0..matrix.width() {
|
|
for x in 0..matrix.width() {
|
|
modules.push(u8::from(matrix.is_dark(x, y).unwrap_or(false)));
|
|
}
|
|
}
|
|
MobileKeyTransferFrame {
|
|
sequence: u32::try_from(frame.sequence()).unwrap_or(u32::MAX),
|
|
total: u32::try_from(frame.total()).unwrap_or(u32::MAX),
|
|
width: u32::try_from(matrix.width()).unwrap_or(u32::MAX),
|
|
modules,
|
|
}
|
|
})
|
|
.collect();
|
|
Ok(MobileKeyTransferExport {
|
|
key: exported.key().into(),
|
|
frames,
|
|
})
|
|
}
|
|
|
|
pub fn importer(&self) -> Arc<MobileKeyTransferImport> {
|
|
Arc::new(MobileKeyTransferImport {
|
|
importer: Mutex::new(self.service.importer()),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileKeyTransferImport {
|
|
importer: Mutex<ironstorage::mobile_key_transfer::MobileKeyTransferImport>,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileKeyTransferImport {
|
|
pub fn add_camera(
|
|
&self,
|
|
frame: platform::MobileCameraFrame,
|
|
rotation_degrees: u32,
|
|
) -> Result<Option<MobileKeyTransferProgress>, MobileKeyTransferFfiError> {
|
|
self.importer
|
|
.lock()
|
|
.map_err(|_| MobileKeyTransferFfiError::Failed {
|
|
kind: MobileKeyTransferErrorKind::Failed,
|
|
message: "the key-transfer session is unavailable".to_owned(),
|
|
})?
|
|
.add_camera(
|
|
ironstorage::mobile_platform::MobileCameraFrame {
|
|
luminance: ironstorage::repository::SecretBytes::new(frame.luminance),
|
|
width: frame.width,
|
|
height: frame.height,
|
|
row_stride: frame.row_stride,
|
|
pixel_stride: frame.pixel_stride,
|
|
},
|
|
rotation_degrees,
|
|
)
|
|
.map(|progress| progress.map(Into::into))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn add_image(
|
|
&self,
|
|
image: Vec<u8>,
|
|
) -> Result<MobileKeyTransferProgress, MobileKeyTransferFfiError> {
|
|
self.importer
|
|
.lock()
|
|
.map_err(|_| MobileKeyTransferFfiError::Failed {
|
|
kind: MobileKeyTransferErrorKind::Failed,
|
|
message: "the key-transfer session is unavailable".to_owned(),
|
|
})?
|
|
.add_image(ironstorage::repository::SecretBytes::new(image))
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn add_frame(
|
|
&self,
|
|
payload: String,
|
|
) -> Result<MobileKeyTransferProgress, MobileKeyTransferFfiError> {
|
|
self.importer
|
|
.lock()
|
|
.map_err(|_| MobileKeyTransferFfiError::Failed {
|
|
kind: MobileKeyTransferErrorKind::Failed,
|
|
message: "the key-transfer session is unavailable".to_owned(),
|
|
})?
|
|
.add_frame(ironstorage::repository::SecretBytes::new(
|
|
payload.into_bytes(),
|
|
))
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn import(
|
|
&self,
|
|
passphrase: Option<String>,
|
|
make_default: bool,
|
|
overwrite: bool,
|
|
) -> Result<MobileKeyTransferOutcome, MobileKeyTransferFfiError> {
|
|
let outcome = self
|
|
.importer
|
|
.lock()
|
|
.map_err(|_| MobileKeyTransferFfiError::Failed {
|
|
kind: MobileKeyTransferErrorKind::Failed,
|
|
message: "the key-transfer session is unavailable".to_owned(),
|
|
})?
|
|
.import(
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
make_default,
|
|
overwrite,
|
|
)?;
|
|
Ok(MobileKeyTransferOutcome {
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Error)]
|
|
pub enum MobileAuthenticationFfiError {
|
|
Failed {
|
|
kind: MobileAuthenticationErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Debug for MobileAuthenticationFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter.write_str("MobileAuthenticationFfiError([REDACTED])")
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MobileAuthenticationFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Debug::fmt(self, formatter)
|
|
}
|
|
}
|
|
|
|
impl Error for MobileAuthenticationFfiError {}
|
|
|
|
impl From<StorageAuthenticationError> for MobileAuthenticationFfiError {
|
|
fn from(error: StorageAuthenticationError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileAuthentication {
|
|
authentication: Arc<ironstorage::mobile_authentication::MobileAuthentication>,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileAuthentication {
|
|
pub fn set_authentication_timeout_text(
|
|
&self,
|
|
seconds: String,
|
|
) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_authentication_timeout_text(&seconds)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn totp_cache_status(&self) -> Result<MobileTotpCacheStatus, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.totp_cache_status()
|
|
.map(|(entries, detail)| MobileTotpCacheStatus { entries, detail })
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn rebuild_totp_cache(
|
|
&self,
|
|
operation: Arc<MobileTotpOperation>,
|
|
) -> Result<MobileTotpPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.rebuild_totp_cache(&operation.operation)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn state(&self) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.state()
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn git_identity(&self) -> Result<MobileGitIdentity, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.git_identity()
|
|
.map(|identity| MobileGitIdentity {
|
|
name: identity.name().to_owned(),
|
|
email: identity.email().to_owned(),
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn mobile_appearance(&self) -> Result<MobileAppearance, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.mobile_appearance()
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_authentication_timeout(
|
|
&self,
|
|
seconds: u64,
|
|
) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_authentication_timeout(seconds)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_mobile_appearance(
|
|
&self,
|
|
appearance: MobileAppearance,
|
|
) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_mobile_appearance(appearance.into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn remove_application_token(&self) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.remove_application_token()
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_default_key(&self, fingerprint: String) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_default_key(&fingerprint)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_git_identity(
|
|
&self,
|
|
name: String,
|
|
email: String,
|
|
) -> Result<MobileGitIdentity, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_git_identity(name, email)
|
|
.map(|identity| MobileGitIdentity {
|
|
name: identity.name().to_owned(),
|
|
email: identity.email().to_owned(),
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn unlock_entry(
|
|
&self,
|
|
path: String,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.unlock_entry(
|
|
&path,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_biometric_unlock(
|
|
&self,
|
|
enabled: bool,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_biometric_unlock(enabled)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn enroll_biometric_unlock(
|
|
&self,
|
|
passphrase: String,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.enroll_biometric_unlock(ironstorage::repository::SecretBytes::new(
|
|
passphrase.into_bytes(),
|
|
))
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn restore_biometric_lease(
|
|
&self,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.restore_biometric_lease()
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn touch_user_activity(&self) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.touch_user_activity()
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn navigation_entry(
|
|
&self,
|
|
directory: String,
|
|
row_id: String,
|
|
revision: String,
|
|
passphrase: Option<String>,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileEntryPresentation, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.navigation_entry(
|
|
&directory,
|
|
&row_id,
|
|
&revision,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
unix_seconds,
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn entry_page(
|
|
&self,
|
|
path: String,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileEntryPresentation, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.entry_page(&path, unix_seconds)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn reveal_entry_field(
|
|
&self,
|
|
path: String,
|
|
field: u64,
|
|
) -> Result<String, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.reveal_entry_field(&path, field)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn reveal_presented_entry_field(
|
|
&self,
|
|
path: String,
|
|
field_id: u64,
|
|
revision: String,
|
|
) -> Result<String, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.reveal_presented_entry_field(&path, field_id, &revision)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn copy_presented_entry_field(
|
|
&self,
|
|
path: String,
|
|
field_id: u64,
|
|
revision: String,
|
|
) -> Result<MobileEntryCopy, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.copy_presented_entry_field(&path, field_id, &revision)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn replace_presented_entry_field(
|
|
&self,
|
|
path: String,
|
|
field_id: u64,
|
|
revision: String,
|
|
value: String,
|
|
) -> Result<MobileEntryPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.replace_presented_entry_field(&path, field_id, &revision, value)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn refresh_entry_presentation(
|
|
&self,
|
|
path: String,
|
|
revision: String,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileEntryPresentation, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.refresh_entry_presentation(&path, &revision, unix_seconds)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn copy_presented_totp_code(
|
|
&self,
|
|
path: String,
|
|
revision: String,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileEntryCopy, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.copy_presented_totp_code(&path, &revision, unix_seconds)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn presented_otp_code(
|
|
&self,
|
|
path: String,
|
|
revision: String,
|
|
unix_seconds: u64,
|
|
consumed_counter: Option<u64>,
|
|
) -> Result<Option<MobileOtpCodePresentation>, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.presented_otp_code(&path, &revision, unix_seconds, consumed_counter)
|
|
.map(|value| value.map(Into::into))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn copy_presented_otp_code(
|
|
&self,
|
|
path: String,
|
|
revision: String,
|
|
unix_seconds: u64,
|
|
consumed_counter: Option<u64>,
|
|
) -> Result<MobileEntryCopy, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.copy_presented_otp_code(&path, &revision, unix_seconds, consumed_counter)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn advance_presented_hotp(
|
|
&self,
|
|
path: String,
|
|
revision: String,
|
|
confirmed: bool,
|
|
) -> Result<MobileOtpCodePresentation, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.advance_presented_hotp(&path, &revision, confirmed)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn prepare_navigation_mutation(
|
|
&self,
|
|
id: String,
|
|
revision: String,
|
|
action: MobileMutationAction,
|
|
) -> Result<MobileTreeMutationPlan, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.prepare_navigation_mutation(&id, &revision, action.into())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn unlock_mutation(
|
|
&self,
|
|
source: String,
|
|
revision: String,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.unlock_mutation(
|
|
&source,
|
|
&revision,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn perform_navigation_mutation(
|
|
&self,
|
|
request: MobileMutationRequest,
|
|
return_path: String,
|
|
) -> Result<MobileNavigationMutationOutcome, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.perform_navigation_mutation(request.into(), &return_path)
|
|
.map(|outcome| {
|
|
let discarded_editors = outcome.result.discarded_editors().to_vec();
|
|
MobileNavigationMutationOutcome {
|
|
result: outcome.result.into(),
|
|
navigation: outcome.navigation.into(),
|
|
discarded_editors,
|
|
}
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn prepare_presented_entry_mutation(
|
|
&self,
|
|
path: String,
|
|
revision: String,
|
|
action: MobileMutationAction,
|
|
) -> Result<MobileMutationPlan, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.prepare_presented_entry_mutation(&path, &revision, action.into())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn copy_entry_field(
|
|
&self,
|
|
path: String,
|
|
field: u64,
|
|
) -> Result<MobileEntryCopy, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.copy_entry_field(&path, field)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn prepare_entry_mutation(
|
|
&self,
|
|
path: String,
|
|
action: MobileMutationAction,
|
|
) -> Result<MobileMutationPlan, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.prepare_entry_mutation(&path, action.into())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn perform_entry_mutation(
|
|
&self,
|
|
request: MobileMutationRequest,
|
|
) -> Result<MobileMutationOutcome, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.perform_entry_mutation(request.into())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn replace_entry_field(
|
|
&self,
|
|
path: String,
|
|
field: u64,
|
|
value: String,
|
|
) -> Result<MobileEntryPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.replace_entry_field(&path, field, value)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn checkpoint_entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
version: u64,
|
|
) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.checkpoint_entry_editor(editor, version)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn checkpoint_entry_editor_inputs(
|
|
&self,
|
|
editor: u64,
|
|
version: u64,
|
|
fields: Vec<MobileEntryEditorInput>,
|
|
) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.checkpoint_entry_editor_inputs(
|
|
editor,
|
|
version,
|
|
fields.into_iter().map(Into::into).collect(),
|
|
)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn resume_entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileEntryEditorRecovery, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.resume_entry_editor(
|
|
editor,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn begin_entry_editor(
|
|
&self,
|
|
path: String,
|
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.begin_entry_editor(&path)
|
|
.map(Into::into)
|
|
.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,
|
|
operation: Arc<MobileTotpOperation>,
|
|
) -> Result<MobileTotpPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.totp_page(&operation.operation)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn cached_totp_page(&self) -> Result<Option<MobileTotpPage>, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.cached_totp_page()
|
|
.map(|page| page.map(Into::into))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn cached_totp_navigation(
|
|
&self,
|
|
) -> Result<Option<Vec<password_navigation::MobileNavigationRow>>, MobileAuthenticationFfiError>
|
|
{
|
|
self.authentication
|
|
.cached_totp_navigation()
|
|
.map(|rows| rows.map(|rows| rows.into_iter().map(Into::into).collect()))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn search_cached_totp_page(
|
|
&self,
|
|
query: String,
|
|
) -> Result<Option<MobileTotpPage>, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.search_cached_totp_page(&query)
|
|
.map(|page| page.map(Into::into))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn search_cached_totp_navigation(
|
|
&self,
|
|
query: String,
|
|
operation: Arc<MobileTotpOperation>,
|
|
) -> Result<Option<Vec<password_navigation::MobileNavigationRow>>, MobileAuthenticationFfiError>
|
|
{
|
|
self.authentication
|
|
.search_cached_totp_navigation(&query, &operation.operation)
|
|
.map(|rows| rows.map(|rows| rows.into_iter().map(Into::into).collect()))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn refresh_totp_catalog(
|
|
&self,
|
|
operation: Arc<MobileTotpOperation>,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileTotpRefreshResult, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.refresh_totp_catalog(
|
|
&operation.operation,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(|result| MobileTotpRefreshResult {
|
|
progress: result.progress.into(),
|
|
cache_notice: result.cache_notice,
|
|
})
|
|
.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 begin_create_entry(
|
|
&self,
|
|
directory: String,
|
|
name: String,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.begin_create_entry(
|
|
&directory,
|
|
&name,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn create_directory(
|
|
&self,
|
|
parent: String,
|
|
name: String,
|
|
) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.create_directory(&parent, &name)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.entry_editor(editor)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn update_entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
fields: Vec<MobileEntryEditorInput>,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.update_entry_editor(editor, fields.into_iter().map(Into::into).collect())
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn add_entry_editor_field(
|
|
&self,
|
|
editor: u64,
|
|
kind: MobileEntryEditorFieldKind,
|
|
name: Option<String>,
|
|
value: String,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.add_entry_editor_field(editor, kind.into(), name, value)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn add_entry_editor_otp_image(
|
|
&self,
|
|
editor: u64,
|
|
image: Vec<u8>,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.add_entry_editor_otp_image(editor, ironstorage::repository::SecretBytes::new(image))
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn remove_entry_editor_field(
|
|
&self,
|
|
editor: u64,
|
|
field: u64,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.remove_entry_editor_field(editor, field)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn reorder_entry_editor_field(
|
|
&self,
|
|
editor: u64,
|
|
field: u64,
|
|
index: u32,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.reorder_entry_editor_field(editor, field, index as usize)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn generate_entry_editor_password(
|
|
&self,
|
|
editor: u64,
|
|
length: Option<u32>,
|
|
no_symbols: bool,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.generate_entry_editor_password(editor, length, no_symbols)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn save_entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
fields: Vec<MobileEntryEditorInput>,
|
|
) -> Result<MobileEntryPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.save_entry_editor(editor, fields.into_iter().map(Into::into).collect())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn discard_entry_editor(&self, editor: u64) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.discard_entry_editor(editor)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn manual_lock(&self) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication.manual_lock().map_err(Into::into)
|
|
}
|
|
|
|
pub fn cancel(&self) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication.cancel().map_err(Into::into)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileOnboardingPhase {
|
|
Validating,
|
|
Authenticating,
|
|
Receiving,
|
|
Integrating,
|
|
Finishing,
|
|
}
|
|
|
|
impl From<StorageOnboardingPhase> for MobileOnboardingPhase {
|
|
fn from(phase: StorageOnboardingPhase) -> Self {
|
|
match phase {
|
|
StorageOnboardingPhase::Validating => Self::Validating,
|
|
StorageOnboardingPhase::Authenticating => Self::Authenticating,
|
|
StorageOnboardingPhase::Receiving => Self::Receiving,
|
|
StorageOnboardingPhase::Integrating => Self::Integrating,
|
|
StorageOnboardingPhase::Finishing => Self::Finishing,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileOnboardingProgress {
|
|
pub phase: MobileOnboardingPhase,
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileOnboardingDiscovery {
|
|
pub branches: Vec<String>,
|
|
pub selected_branch: u32,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileOnboardingOutcome {
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileOnboardingErrorKind {
|
|
InvalidInput,
|
|
UnsupportedRemote,
|
|
Authentication,
|
|
Repository,
|
|
ExistingClone,
|
|
ExistingKey,
|
|
Interrupted,
|
|
SecureStorage,
|
|
Configuration,
|
|
AlreadyConfigured,
|
|
}
|
|
|
|
impl From<StorageOnboardingErrorKind> for MobileOnboardingErrorKind {
|
|
fn from(kind: StorageOnboardingErrorKind) -> Self {
|
|
match kind {
|
|
StorageOnboardingErrorKind::InvalidInput => Self::InvalidInput,
|
|
StorageOnboardingErrorKind::UnsupportedRemote => Self::UnsupportedRemote,
|
|
StorageOnboardingErrorKind::Authentication => Self::Authentication,
|
|
StorageOnboardingErrorKind::Repository => Self::Repository,
|
|
StorageOnboardingErrorKind::ExistingClone => Self::ExistingClone,
|
|
StorageOnboardingErrorKind::ExistingKey => Self::ExistingKey,
|
|
StorageOnboardingErrorKind::Interrupted => Self::Interrupted,
|
|
StorageOnboardingErrorKind::SecureStorage => Self::SecureStorage,
|
|
StorageOnboardingErrorKind::Configuration => Self::Configuration,
|
|
StorageOnboardingErrorKind::AlreadyConfigured => Self::AlreadyConfigured,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Error)]
|
|
pub enum MobileOnboardingFfiError {
|
|
Failed {
|
|
kind: MobileOnboardingErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Debug for MobileOnboardingFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter.write_str("MobileOnboardingFfiError([REDACTED])")
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MobileOnboardingFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Debug::fmt(self, formatter)
|
|
}
|
|
}
|
|
|
|
impl Error for MobileOnboardingFfiError {}
|
|
|
|
impl From<StorageOnboardingError> for MobileOnboardingFfiError {
|
|
fn from(error: StorageOnboardingError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileOnboardingOperation {
|
|
operation: mobile_onboarding::MobileOnboardingOperation,
|
|
request: mobile_onboarding::MobileOnboardingRequest,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileOnboardingOperation {
|
|
pub fn progress(&self) -> MobileOnboardingProgress {
|
|
let progress = self.operation.progress();
|
|
MobileOnboardingProgress {
|
|
phase: progress.phase().into(),
|
|
title: progress.title().to_owned(),
|
|
detail: progress.detail().to_owned(),
|
|
}
|
|
}
|
|
|
|
pub fn discover(&self) -> Result<MobileOnboardingDiscovery, MobileOnboardingFfiError> {
|
|
let discovery = self.operation.discover(&self.request)?;
|
|
Ok(MobileOnboardingDiscovery {
|
|
branches: discovery.branches().to_vec(),
|
|
selected_branch: u32::try_from(discovery.selected_branch()).unwrap_or_default(),
|
|
})
|
|
}
|
|
|
|
pub fn setup(
|
|
&self,
|
|
branch: String,
|
|
use_existing: bool,
|
|
) -> Result<MobileOnboardingOutcome, MobileOnboardingFfiError> {
|
|
let outcome = self.operation.setup(&self.request, &branch, use_existing)?;
|
|
Ok(MobileOnboardingOutcome {
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
})
|
|
}
|
|
|
|
pub fn connect_local_store(&self) -> Result<MobileOnboardingOutcome, MobileOnboardingFfiError> {
|
|
let outcome = self.operation.connect_local_store(&self.request)?;
|
|
Ok(MobileOnboardingOutcome {
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
})
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.operation.cancel();
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileLocalOnboardingOperation {
|
|
operation: mobile_onboarding::MobileOnboardingOperation,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileLocalOnboardingOperation {
|
|
pub fn generate(
|
|
&self,
|
|
user_id: String,
|
|
passphrase: String,
|
|
replace_existing_key: bool,
|
|
) -> Result<MobileOnboardingOutcome, MobileOnboardingFfiError> {
|
|
let outcome = self.operation.setup_local_generated(
|
|
&user_id,
|
|
ironstorage::repository::SecretBytes::new(passphrase.into_bytes()),
|
|
replace_existing_key,
|
|
)?;
|
|
Ok(MobileOnboardingOutcome {
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
})
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.operation.cancel();
|
|
}
|
|
}
|
|
|
|
impl From<mobile::MobileShell> for MobileShell {
|
|
fn from(shell: mobile::MobileShell) -> Self {
|
|
Self {
|
|
selected_tab: shell.selected_tab().into(),
|
|
pages: shell
|
|
.pages()
|
|
.iter()
|
|
.map(|page| MobilePage {
|
|
tab: page.tab().into(),
|
|
title: page.title().to_owned(),
|
|
icon: page.icon().into(),
|
|
state: page.state().into(),
|
|
state_title: page.state_title().to_owned(),
|
|
state_detail: page.state_detail().to_owned(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Error)]
|
|
pub enum MobilePreferenceError {
|
|
Configuration { message: String },
|
|
}
|
|
|
|
impl fmt::Debug for MobilePreferenceError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter.write_str("MobilePreferenceError([REDACTED])")
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MobilePreferenceError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Debug::fmt(self, formatter)
|
|
}
|
|
}
|
|
|
|
impl Error for MobilePreferenceError {}
|
|
|
|
impl From<ConfigError> for MobilePreferenceError {
|
|
fn from(error: ConfigError) -> Self {
|
|
Self::Configuration {
|
|
message: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn product_name() -> String {
|
|
ironstorage::PRODUCT_NAME.to_owned()
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_shell() -> MobileShell {
|
|
mobile::MobileShell::load().into()
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_shell_fixture(state: MobileShellState) -> MobileShell {
|
|
mobile::MobileShell::fixture(state.into()).into()
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn set_selected_mobile_tab(tab: MobileTab) -> Result<(), MobilePreferenceError> {
|
|
mobile::store_selected_tab(tab.into()).map_err(Into::into)
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_home_operation(
|
|
authentication: Option<Arc<MobileAuthentication>>,
|
|
) -> Arc<MobileHomeOperation> {
|
|
Arc::new(MobileHomeOperation {
|
|
operation: mobile_home::MobileHomeOperation::new(
|
|
authentication.map(|authentication| Arc::clone(&authentication.authentication)),
|
|
),
|
|
})
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_totp_operation() -> Arc<MobileTotpOperation> {
|
|
Arc::new(MobileTotpOperation {
|
|
operation: StorageTotpOperation::default(),
|
|
})
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_password_page(
|
|
path: Option<String>,
|
|
) -> Result<MobilePasswordPage, MobilePasswordFfiError> {
|
|
ironstorage::mobile_passwords::MobilePasswordPage::load(path.as_deref())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_password_search(query: String) -> Result<MobilePasswordPage, MobilePasswordFfiError> {
|
|
ironstorage::mobile_passwords::MobilePasswordPage::search(&query)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_authentication() -> Result<Arc<MobileAuthentication>, MobileAuthenticationFfiError> {
|
|
Ok(Arc::new(MobileAuthentication {
|
|
authentication: Arc::new(ironstorage::mobile_authentication::MobileAuthentication::load()?),
|
|
}))
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_key_transfer() -> Result<Arc<MobileKeyTransfer>, MobileKeyTransferFfiError> {
|
|
Ok(Arc::new(MobileKeyTransfer {
|
|
service: ironstorage::mobile_key_transfer::MobileKeyTransferService::load()?,
|
|
}))
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_local_key_transfer_importer() -> Arc<MobileKeyTransferImport> {
|
|
Arc::new(MobileKeyTransferImport {
|
|
importer: Mutex::new(
|
|
ironstorage::mobile_key_transfer::MobileKeyTransferImport::local_setup(),
|
|
),
|
|
})
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_onboarding_operation(
|
|
server_url: String,
|
|
account: String,
|
|
repository_path: String,
|
|
application_token: String,
|
|
) -> Result<Arc<MobileOnboardingOperation>, MobileOnboardingFfiError> {
|
|
Ok(Arc::new(MobileOnboardingOperation {
|
|
operation: mobile_onboarding::MobileOnboardingOperation::default(),
|
|
request: mobile_onboarding::MobileOnboardingRequest::new(
|
|
server_url,
|
|
account,
|
|
repository_path,
|
|
application_token.into_bytes(),
|
|
)?,
|
|
}))
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_local_onboarding_operation() -> Arc<MobileLocalOnboardingOperation> {
|
|
Arc::new(MobileLocalOnboardingOperation {
|
|
operation: mobile_onboarding::MobileOnboardingOperation::default(),
|
|
})
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn replace_configured_mobile_application_token(
|
|
account: String,
|
|
application_token: String,
|
|
) -> Result<(), MobileOnboardingFfiError> {
|
|
mobile_onboarding::replace_configured_application_token(
|
|
account,
|
|
application_token.into_bytes(),
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{
|
|
MobileHomeActionKind, MobileHomePhase, MobileOnboardingErrorKind, MobileOnboardingFfiError,
|
|
MobileShellState, MobileTab, StorageHomeActionKind,
|
|
};
|
|
|
|
#[test]
|
|
fn bridge_reads_product_name_from_storage_crate() {
|
|
assert_eq!(super::product_name(), ironstorage::PRODUCT_NAME);
|
|
}
|
|
|
|
#[test]
|
|
fn bridge_exposes_every_view_ready_shell_fixture() {
|
|
for state in [
|
|
MobileShellState::Loading,
|
|
MobileShellState::Empty,
|
|
MobileShellState::Ready,
|
|
MobileShellState::Locked,
|
|
MobileShellState::Error,
|
|
MobileShellState::Stale,
|
|
] {
|
|
let shell = super::mobile_shell_fixture(state);
|
|
assert_eq!(shell.selected_tab, MobileTab::Home);
|
|
assert_eq!(shell.pages.len(), 5);
|
|
assert!(shell.pages.iter().all(|page| page.state == state));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bridge_rejects_non_https_before_creating_an_operation() {
|
|
let error = match super::mobile_onboarding_operation(
|
|
"ssh://example.test".to_owned(),
|
|
"alice".to_owned(),
|
|
"team/passwords".to_owned(),
|
|
"DO-NOT-RENDER".to_owned(),
|
|
) {
|
|
Ok(_) => panic!("SSH must be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
match error {
|
|
MobileOnboardingFfiError::Failed { kind, detail, .. } => {
|
|
assert_eq!(kind, MobileOnboardingErrorKind::UnsupportedRemote);
|
|
assert!(!detail.contains("DO-NOT-RENDER"));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn home_bridge_preserves_storage_action_kinds() {
|
|
assert_eq!(
|
|
MobileHomeActionKind::from(StorageHomeActionKind::Push),
|
|
MobileHomeActionKind::Push
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn home_bridge_does_not_require_entry_authentication() {
|
|
let operation = super::mobile_home_operation(None);
|
|
assert_eq!(operation.progress().phase, MobileHomePhase::Validating);
|
|
operation.cancel();
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "watch")]
|
|
#[uniffi::export]
|
|
impl MobileAuthentication {
|
|
pub fn preferences(
|
|
&self,
|
|
watch_supported: bool,
|
|
watch_paired: bool,
|
|
watch_app_installed: bool,
|
|
) -> Result<MobilePreferences, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.preferences(watch_supported, watch_paired, watch_app_installed)
|
|
.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 watch_snapshot_status(
|
|
&self,
|
|
) -> Result<MobileWatchSnapshotStatus, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.watch_snapshot_status()
|
|
.map(|status| (&status).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn prepare_watch_snapshot(
|
|
&self,
|
|
platform_pairing_identity: String,
|
|
) -> Result<MobileWatchSnapshotTransfer, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.prepare_watch_snapshot(&platform_pairing_identity)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_watch_snapshot_unavailable(
|
|
&self,
|
|
unpaired: bool,
|
|
detail: String,
|
|
) -> Result<MobileWatchSnapshotStatus, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_watch_snapshot_unavailable(unpaired, detail)
|
|
.map(|status| (&status).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn fail_watch_snapshot(
|
|
&self,
|
|
revision: u64,
|
|
detail: String,
|
|
) -> Result<MobileWatchSnapshotStatus, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.fail_watch_snapshot(revision, detail)
|
|
.map(|status| (&status).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn acknowledge_watch_snapshot(
|
|
&self,
|
|
receipt: Vec<u8>,
|
|
) -> Result<MobileWatchSnapshotStatus, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.acknowledge_watch_snapshot(&receipt)
|
|
.map(|status| (&status).into())
|
|
.map_err(Into::into)
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "watch"))]
|
|
#[uniffi::export]
|
|
impl MobileAuthentication {
|
|
pub fn preferences(&self) -> Result<MobilePreferences, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.preferences(false, false, false)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
}
|
|
|
|
mod credentials;
|
|
pub use credentials::*;
|