Implement iPhone TOTP tab and Watch sharing
This commit is contained in:
1 parent
ae64ce47a3
commit
b01cc8bb6d
10 files changed
+1980
-5
No files matched your search
@@ -15,6 +15,7 @@ use crate::{
|
||||
MobileEntryEditorInput, MobileEntryEditorPage, MobileEntryEditorSession, MobileEntryPage,
|
||||
MobileEntryValueError, field_value,
|
||||
},
|
||||
mobile_totp::{MobileTotpDetail, MobileTotpError, MobileTotpPage, MobileTotpService},
|
||||
recipient::RecipientPolicyManager,
|
||||
repository::{
|
||||
DirectoryPath, EncryptedEntry, EntryPath, Repository, RepositoryError, SecretBytes,
|
||||
@@ -161,6 +162,7 @@ struct MobileAuthenticationStatus {
|
||||
active: Option<ActiveMobileLease>,
|
||||
next_editor_id: u64,
|
||||
editors: BTreeMap<u64, MobileEntryDraft>,
|
||||
watch_shared_totp_entries: std::collections::BTreeSet<EntryPath>,
|
||||
}
|
||||
|
||||
/// One process-wide mobile authentication lease shared by every tab and viewer.
|
||||
@@ -194,6 +196,7 @@ impl MobileAuthentication {
|
||||
active: None,
|
||||
next_editor_id: 0,
|
||||
editors: BTreeMap::new(),
|
||||
watch_shared_totp_entries: config.watch_shared_totp_entries().clone(),
|
||||
}),
|
||||
config,
|
||||
repository,
|
||||
@@ -396,6 +399,95 @@ impl MobileAuthentication {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unlock_totp(
|
||||
&self,
|
||||
passphrase: Option<SecretBytes>,
|
||||
) -> Result<MobileAuthenticationState, MobileAuthenticationError> {
|
||||
let path = self
|
||||
.repository
|
||||
.snapshot()
|
||||
.map_err(entry_error)?
|
||||
.entries()
|
||||
.next()
|
||||
.map(|entry| entry.path().clone())
|
||||
.ok_or_else(|| entry_detail("TOTP Is Unavailable", "the password store is empty"))?;
|
||||
let ciphertext = self.repository.read_entry(&path).map_err(entry_error)?;
|
||||
self.unlock_ciphertext(&ciphertext, passphrase)
|
||||
}
|
||||
|
||||
pub fn totp_page(&self) -> Result<MobileTotpPage, MobileAuthenticationError> {
|
||||
self.ensure_active()?;
|
||||
let (handle, key, shared) = {
|
||||
let status = self.status()?;
|
||||
let active = status.active.as_ref().ok_or_else(locked_error)?;
|
||||
(
|
||||
active.handle.clone(),
|
||||
active.key.clone(),
|
||||
status.watch_shared_totp_entries.clone(),
|
||||
)
|
||||
};
|
||||
let mut provider = KeyOnlyProvider::new(handle, &key);
|
||||
MobileTotpService::new(&self.repository, &self.keys)
|
||||
.page(&shared, &mut provider)
|
||||
.map_err(totp_error)
|
||||
}
|
||||
|
||||
pub fn totp_detail(
|
||||
&self,
|
||||
path: &str,
|
||||
unix_seconds: u64,
|
||||
) -> Result<MobileTotpDetail, MobileAuthenticationError> {
|
||||
self.ensure_active()?;
|
||||
let (handle, key, shared) = {
|
||||
let status = self.status()?;
|
||||
let active = status.active.as_ref().ok_or_else(locked_error)?;
|
||||
(
|
||||
active.handle.clone(),
|
||||
active.key.clone(),
|
||||
status.watch_shared_totp_entries.clone(),
|
||||
)
|
||||
};
|
||||
let mut provider = KeyOnlyProvider::new(handle, &key);
|
||||
MobileTotpService::new(&self.repository, &self.keys)
|
||||
.detail(path, unix_seconds, &shared, &mut provider)
|
||||
.map_err(totp_error)
|
||||
}
|
||||
|
||||
pub fn copy_totp_code(
|
||||
&self,
|
||||
path: &str,
|
||||
unix_seconds: u64,
|
||||
) -> Result<MobileEntryCopy, MobileAuthenticationError> {
|
||||
let detail = self.totp_detail(path, unix_seconds)?;
|
||||
let value = String::from_utf8(detail.code().expose().to_vec())
|
||||
.map_err(|_| entry_detail("TOTP Code Is Unavailable", "the code is not UTF-8"))?;
|
||||
Ok(MobileEntryCopy {
|
||||
value,
|
||||
timeout_seconds: self.config.clipboard_timeout().duration().as_secs(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_totp_watch_shared(
|
||||
&self,
|
||||
path: &str,
|
||||
shared: bool,
|
||||
unix_seconds: u64,
|
||||
) -> Result<MobileTotpDetail, MobileAuthenticationError> {
|
||||
self.totp_detail(path, unix_seconds)?;
|
||||
let entry = EntryPath::parse(path).map_err(entry_error)?;
|
||||
let mut selected = self.status()?.watch_shared_totp_entries.clone();
|
||||
if shared {
|
||||
selected.insert(entry);
|
||||
} else {
|
||||
selected.remove(&entry);
|
||||
}
|
||||
self.config
|
||||
.update_watch_shared_totp_entries(&selected)
|
||||
.map_err(config_error)?;
|
||||
self.status()?.watch_shared_totp_entries = selected;
|
||||
self.totp_detail(path, unix_seconds)
|
||||
}
|
||||
|
||||
pub fn replace_entry_field(
|
||||
&self,
|
||||
path: &str,
|
||||
@@ -786,6 +878,10 @@ fn editor_error(error: MobileEntryEditorError) -> MobileAuthenticationError {
|
||||
entry_detail("Entry Draft Is Invalid", error)
|
||||
}
|
||||
|
||||
fn totp_error(error: MobileTotpError) -> MobileAuthenticationError {
|
||||
entry_detail("TOTP Is Unavailable", error)
|
||||
}
|
||||
|
||||
fn editor_missing() -> MobileAuthenticationError {
|
||||
entry_detail(
|
||||
"Entry Draft Is Unavailable",
|
||||
|
||||
Reference in new issue
Block a user