bitwarden_importers/importers/onepassword/convert/
login.rs1use bitwarden_exporters::{Login, LoginUri};
4use itertools::Itertools;
5
6use super::{
7 claimed::{Claimed, designation, section_fields},
8 value::non_blank,
9};
10use crate::importers::onepassword::access::wire::{VaultItemDetails, VaultItemOverview};
11
12const TOTP_PREFIX: &str = "TOTP_";
14
15pub(super) fn login(overview: &VaultItemOverview, details: &VaultItemDetails) -> (Login, Claimed) {
16 let totp = first_totp(details);
17
18 let mut login = Login {
19 username: designation(details, "username"),
20 password: designation(details, "password"),
21 login_uris: login_uris(website_addresses(overview)),
22 totp: totp.as_ref().map(|(_, secret)| secret.clone()),
23 fido2_credentials: None,
24 };
25 login.sanitize_uris();
26
27 let claimed = Claimed {
28 designations: &["username", "password"],
29 fields: totp.map(|(position, _)| position).into_iter().collect(),
30 };
31
32 (login, claimed)
33}
34
35pub(super) fn first_totp(details: &VaultItemDetails) -> Option<(usize, String)> {
39 section_fields(details)
40 .enumerate()
41 .find_map(|(position, field)| {
42 if !field
43 .id
44 .as_deref()
45 .is_some_and(|id| id.starts_with(TOTP_PREFIX))
46 {
47 return None;
48 }
49
50 let secret = field.value.as_ref()?.as_str().and_then(non_blank)?;
51 Some((position, secret.to_string()))
52 })
53}
54
55pub(super) fn website_addresses(overview: &VaultItemOverview) -> impl Iterator<Item = &str> {
58 overview.url.as_deref().into_iter().chain(
59 overview
60 .urls
61 .iter()
62 .flatten()
63 .filter_map(|url| url.url.as_deref()),
64 )
65}
66
67pub(super) fn login_uris<'a>(addresses: impl Iterator<Item = &'a str>) -> Vec<LoginUri> {
69 addresses
70 .filter_map(non_blank)
71 .unique()
72 .map(|uri| LoginUri {
73 uri: Some(uri.to_string()),
74 r#match: None,
75 })
76 .collect()
77}