Add OTP QR import from TOTP and entry editors (#120)
Dependency security audit / rustsec (push) Successful in 20s
Dependency security audit / rustsec (push) Successful in 20s
This commit is contained in:
1 parent
93a08e9c65
commit
3c3574b449
12 files changed
+448
-23
No files matched your search
@@ -1173,6 +1173,18 @@ impl MobileAuthentication {
|
||||
Ok(draft.page())
|
||||
}
|
||||
|
||||
pub fn add_entry_editor_otp_image(
|
||||
&self,
|
||||
editor: u64,
|
||||
image: SecretBytes,
|
||||
) -> Result<MobileEntryEditorPage, MobileAuthenticationError> {
|
||||
self.ensure_active()?;
|
||||
let mut status = self.status()?;
|
||||
let draft = status.editors.get_mut(&editor).ok_or_else(editor_missing)?;
|
||||
draft.add_otp_image(image).map_err(editor_error)?;
|
||||
Ok(draft.page())
|
||||
}
|
||||
|
||||
pub fn remove_entry_editor_field(
|
||||
&self,
|
||||
editor: u64,
|
||||
|
||||
@@ -7,6 +7,8 @@ use crate::document::{
|
||||
EntryFieldKind, EntrySensitivity,
|
||||
};
|
||||
use crate::generate::{GenerateError, GeneratorConfig, MAX_PASSWORD_LENGTH};
|
||||
use crate::presentation::{QrError, QrMatrix};
|
||||
use crate::repository::SecretBytes;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum MobileEntrySectionKind {
|
||||
@@ -330,6 +332,15 @@ impl MobileEntryDraft {
|
||||
name: Option<String>,
|
||||
value: String,
|
||||
) -> Result<(), MobileEntryEditorError> {
|
||||
if kind == MobileEntryEditorFieldKind::OtpUri
|
||||
&& self
|
||||
.document
|
||||
.fields()
|
||||
.iter()
|
||||
.any(|field| field.metadata().kind() == EntryFieldKind::OtpUri)
|
||||
{
|
||||
return Err(MobileEntryEditorError::ExistingOtp);
|
||||
}
|
||||
let draft = match kind {
|
||||
MobileEntryEditorFieldKind::Password => {
|
||||
return Err(MobileEntryEditorError::ProtectedPasswordField);
|
||||
@@ -343,10 +354,26 @@ impl MobileEntryDraft {
|
||||
}
|
||||
MobileEntryEditorFieldKind::OtpUri => EntryFieldDraft::otp_uri(value.into_bytes())?,
|
||||
};
|
||||
if kind == MobileEntryEditorFieldKind::OtpUri
|
||||
&& self.creating
|
||||
&& let [field] = self.document.fields()
|
||||
&& field.metadata().kind() == EntryFieldKind::Password
|
||||
&& field.value().is_empty()
|
||||
{
|
||||
self.document.update(field.id(), draft)?;
|
||||
return Ok(());
|
||||
}
|
||||
self.document.add(self.document.fields().len(), draft)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_otp_image(&mut self, image: SecretBytes) -> Result<(), MobileEntryEditorError> {
|
||||
let payload = QrMatrix::decode_image(&image).map_err(MobileEntryEditorError::Qr)?;
|
||||
let uri = String::from_utf8(payload.expose().to_vec())
|
||||
.map_err(|_| MobileEntryEditorError::NonUtf8)?;
|
||||
self.add(MobileEntryEditorFieldKind::OtpUri, None, uri)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, id: u64) -> Result<(), MobileEntryEditorError> {
|
||||
let id = EntryFieldId::from_value(id);
|
||||
if self
|
||||
@@ -420,6 +447,8 @@ pub enum MobileEntryEditorError {
|
||||
MissingFieldName,
|
||||
ProtectedPasswordField,
|
||||
NonUtf8,
|
||||
ExistingOtp,
|
||||
Qr(QrError),
|
||||
}
|
||||
|
||||
impl fmt::Display for MobileEntryEditorError {
|
||||
@@ -433,6 +462,10 @@ impl fmt::Display for MobileEntryEditorError {
|
||||
formatter.write_str("the first password field cannot be removed or moved")
|
||||
}
|
||||
Self::NonUtf8 => formatter.write_str("the entry field is not valid UTF-8"),
|
||||
Self::ExistingOtp => formatter.write_str(
|
||||
"this entry already has an OTP URI; edit its existing field to replace it",
|
||||
),
|
||||
Self::Qr(error) => error.fmt(formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,19 @@ impl QrMatrix {
|
||||
/// Decode the first QR symbol from an encoded image without exposing the
|
||||
/// secret-derived payload to a presentation adapter.
|
||||
pub fn decode_image(image: &SecretBytes) -> Result<SecretBytes, QrError> {
|
||||
let grayscale = image::load_from_memory(image.expose())
|
||||
if image.expose().len() > 32 * 1024 * 1024 {
|
||||
return Err(QrError::InvalidImage);
|
||||
}
|
||||
let mut reader = image::ImageReader::new(std::io::Cursor::new(image.expose()))
|
||||
.with_guessed_format()
|
||||
.map_err(|_| QrError::InvalidImage)?;
|
||||
let mut limits = image::Limits::default();
|
||||
limits.max_image_width = Some(4096);
|
||||
limits.max_image_height = Some(4096);
|
||||
limits.max_alloc = Some(128 * 1024 * 1024);
|
||||
reader.limits(limits);
|
||||
let grayscale = reader
|
||||
.decode()
|
||||
.map_err(|_| QrError::InvalidImage)?
|
||||
.into_luma8();
|
||||
let mut prepared = rqrr::PreparedImage::prepare_from_greyscale(
|
||||
|
||||
Reference in new issue
Block a user