Skip to main content

bwx/
api.rs

1#![allow(clippy::as_conversions)]
2
3use crate::prelude::*;
4
5use rand::distr::SampleString as _;
6use sha2::Digest as _;
7use tokio::io::AsyncReadExt as _;
8
9use crate::json::{
10    DeserializeJsonWithPath as _, DeserializeJsonWithPathAsync as _,
11};
12
13#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14#[repr(u8)]
15pub enum UriMatchType {
16    Domain = 0,
17    Host = 1,
18    StartsWith = 2,
19    Exact = 3,
20    RegularExpression = 4,
21    Never = 5,
22}
23
24impl serde::Serialize for UriMatchType {
25    fn serialize<S>(
26        &self,
27        serializer: S,
28    ) -> std::result::Result<S::Ok, S::Error>
29    where
30        S: serde::Serializer,
31    {
32        let v: u8 = match self {
33            Self::Domain => 0,
34            Self::Host => 1,
35            Self::StartsWith => 2,
36            Self::Exact => 3,
37            Self::RegularExpression => 4,
38            Self::Never => 5,
39        };
40        serializer.serialize_u8(v)
41    }
42}
43
44impl<'de> serde::Deserialize<'de> for UriMatchType {
45    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
46    where
47        D: serde::Deserializer<'de>,
48    {
49        let v = u8::deserialize(deserializer)?;
50        match v {
51            0 => Ok(Self::Domain),
52            1 => Ok(Self::Host),
53            2 => Ok(Self::StartsWith),
54            3 => Ok(Self::Exact),
55            4 => Ok(Self::RegularExpression),
56            5 => Ok(Self::Never),
57            _ => Err(serde::de::Error::custom(format!(
58                "invalid UriMatchType: {v}"
59            ))),
60        }
61    }
62}
63
64impl std::fmt::Display for UriMatchType {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        #[allow(clippy::enum_glob_use)]
67        use UriMatchType::*;
68        let s = match self {
69            Domain => "domain",
70            Host => "host",
71            StartsWith => "starts_with",
72            Exact => "exact",
73            RegularExpression => "regular_expression",
74            Never => "never",
75        };
76        write!(f, "{s}")
77    }
78}
79
80#[derive(Debug, Copy, Clone, PartialEq, Eq)]
81pub enum TwoFactorProviderType {
82    Authenticator = 0,
83    Email = 1,
84    Duo = 2,
85    Yubikey = 3,
86    U2f = 4,
87    Remember = 5,
88    OrganizationDuo = 6,
89    WebAuthn = 7,
90}
91
92impl TwoFactorProviderType {
93    pub fn message(&self) -> &str {
94        match *self {
95            Self::Authenticator => "Enter the 6 digit verification code from your authenticator app.",
96            Self::Yubikey => "Insert your Yubikey and push the button.",
97            Self::Email => "Enter the PIN you received via email.",
98            _ => "Enter the code."
99        }
100    }
101
102    pub fn header(&self) -> &str {
103        match *self {
104            Self::Authenticator => "Authenticator App",
105            Self::Yubikey => "Yubikey",
106            Self::Email => "Email Code",
107            _ => "Two Factor Authentication",
108        }
109    }
110
111    pub fn grab(&self) -> bool {
112        !matches!(self, Self::Email)
113    }
114}
115
116impl<'de> serde::Deserialize<'de> for TwoFactorProviderType {
117    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
118    where
119        D: serde::Deserializer<'de>,
120    {
121        struct TwoFactorProviderTypeVisitor;
122        impl serde::de::Visitor<'_> for TwoFactorProviderTypeVisitor {
123            type Value = TwoFactorProviderType;
124
125            fn expecting(
126                &self,
127                formatter: &mut std::fmt::Formatter,
128            ) -> std::fmt::Result {
129                formatter.write_str("two factor provider id")
130            }
131
132            fn visit_str<E>(
133                self,
134                value: &str,
135            ) -> std::result::Result<Self::Value, E>
136            where
137                E: serde::de::Error,
138            {
139                value.parse().map_err(serde::de::Error::custom)
140            }
141
142            fn visit_u64<E>(
143                self,
144                value: u64,
145            ) -> std::result::Result<Self::Value, E>
146            where
147                E: serde::de::Error,
148            {
149                std::convert::TryFrom::try_from(value)
150                    .map_err(serde::de::Error::custom)
151            }
152        }
153
154        deserializer.deserialize_any(TwoFactorProviderTypeVisitor)
155    }
156}
157
158impl std::convert::TryFrom<u64> for TwoFactorProviderType {
159    type Error = Error;
160
161    fn try_from(ty: u64) -> Result<Self> {
162        match ty {
163            0 => Ok(Self::Authenticator),
164            1 => Ok(Self::Email),
165            2 => Ok(Self::Duo),
166            3 => Ok(Self::Yubikey),
167            4 => Ok(Self::U2f),
168            5 => Ok(Self::Remember),
169            6 => Ok(Self::OrganizationDuo),
170            7 => Ok(Self::WebAuthn),
171            _ => Err(Error::InvalidTwoFactorProvider {
172                ty: format!("{ty}"),
173            }),
174        }
175    }
176}
177
178impl std::str::FromStr for TwoFactorProviderType {
179    type Err = Error;
180
181    fn from_str(ty: &str) -> Result<Self> {
182        match ty {
183            "0" => Ok(Self::Authenticator),
184            "1" => Ok(Self::Email),
185            "2" => Ok(Self::Duo),
186            "3" => Ok(Self::Yubikey),
187            "4" => Ok(Self::U2f),
188            "5" => Ok(Self::Remember),
189            "6" => Ok(Self::OrganizationDuo),
190            "7" => Ok(Self::WebAuthn),
191            _ => Err(Error::InvalidTwoFactorProvider { ty: ty.to_string() }),
192        }
193    }
194}
195
196#[derive(Debug, Copy, Clone, PartialEq, Eq)]
197pub enum KdfType {
198    Pbkdf2 = 0,
199    Argon2id = 1,
200}
201
202impl<'de> serde::Deserialize<'de> for KdfType {
203    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
204    where
205        D: serde::Deserializer<'de>,
206    {
207        struct KdfTypeVisitor;
208        impl serde::de::Visitor<'_> for KdfTypeVisitor {
209            type Value = KdfType;
210
211            fn expecting(
212                &self,
213                formatter: &mut std::fmt::Formatter,
214            ) -> std::fmt::Result {
215                formatter.write_str("kdf id")
216            }
217
218            fn visit_str<E>(
219                self,
220                value: &str,
221            ) -> std::result::Result<Self::Value, E>
222            where
223                E: serde::de::Error,
224            {
225                value.parse().map_err(serde::de::Error::custom)
226            }
227
228            fn visit_u64<E>(
229                self,
230                value: u64,
231            ) -> std::result::Result<Self::Value, E>
232            where
233                E: serde::de::Error,
234            {
235                std::convert::TryFrom::try_from(value)
236                    .map_err(serde::de::Error::custom)
237            }
238        }
239
240        deserializer.deserialize_any(KdfTypeVisitor)
241    }
242}
243
244impl std::convert::TryFrom<u64> for KdfType {
245    type Error = Error;
246
247    fn try_from(ty: u64) -> Result<Self> {
248        match ty {
249            0 => Ok(Self::Pbkdf2),
250            1 => Ok(Self::Argon2id),
251            _ => Err(Error::InvalidKdfType {
252                ty: format!("{ty}"),
253            }),
254        }
255    }
256}
257
258impl std::str::FromStr for KdfType {
259    type Err = Error;
260
261    fn from_str(ty: &str) -> Result<Self> {
262        match ty {
263            "0" => Ok(Self::Pbkdf2),
264            "1" => Ok(Self::Argon2id),
265            _ => Err(Error::InvalidKdfType { ty: ty.to_string() }),
266        }
267    }
268}
269
270impl serde::Serialize for KdfType {
271    fn serialize<S>(
272        &self,
273        serializer: S,
274    ) -> std::result::Result<S::Ok, S::Error>
275    where
276        S: serde::Serializer,
277    {
278        let s = match self {
279            Self::Pbkdf2 => "0",
280            Self::Argon2id => "1",
281        };
282        serializer.serialize_str(s)
283    }
284}
285
286#[derive(Debug, Copy, Clone, PartialEq, Eq)]
287#[repr(u8)]
288pub enum CipherRepromptType {
289    None = 0,
290    Password = 1,
291}
292
293impl serde::Serialize for CipherRepromptType {
294    fn serialize<S>(
295        &self,
296        serializer: S,
297    ) -> std::result::Result<S::Ok, S::Error>
298    where
299        S: serde::Serializer,
300    {
301        let v: u8 = match self {
302            Self::None => 0,
303            Self::Password => 1,
304        };
305        serializer.serialize_u8(v)
306    }
307}
308
309impl<'de> serde::Deserialize<'de> for CipherRepromptType {
310    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
311    where
312        D: serde::Deserializer<'de>,
313    {
314        let v = u8::deserialize(deserializer)?;
315        match v {
316            0 => Ok(Self::None),
317            1 => Ok(Self::Password),
318            _ => Err(serde::de::Error::custom(format!(
319                "invalid CipherRepromptType: {v}"
320            ))),
321        }
322    }
323}
324
325#[derive(serde::Serialize, Debug)]
326struct PreloginReq {
327    email: String,
328}
329
330#[derive(serde::Deserialize, Debug)]
331struct PreloginRes {
332    #[serde(rename = "Kdf", alias = "kdf")]
333    kdf: KdfType,
334    #[serde(rename = "KdfIterations", alias = "kdfIterations")]
335    kdf_iterations: u32,
336    #[serde(rename = "KdfMemory", alias = "kdfMemory")]
337    kdf_memory: Option<u32>,
338    #[serde(rename = "KdfParallelism", alias = "kdfParallelism")]
339    kdf_parallelism: Option<u32>,
340}
341
342#[derive(serde::Serialize, Debug)]
343struct ConnectTokenReq {
344    grant_type: String,
345    scope: String,
346    client_id: String,
347    #[serde(rename = "deviceType")]
348    device_type: u32,
349    #[serde(rename = "deviceIdentifier")]
350    device_identifier: String,
351    #[serde(rename = "deviceName")]
352    device_name: String,
353    #[serde(rename = "devicePushToken")]
354    device_push_token: String,
355    #[serde(rename = "twoFactorToken")]
356    two_factor_token: Option<String>,
357    #[serde(rename = "twoFactorProvider")]
358    two_factor_provider: Option<u32>,
359    #[serde(flatten)]
360    auth: ConnectTokenAuth,
361}
362
363#[derive(serde::Serialize, Debug)]
364#[serde(untagged)]
365enum ConnectTokenAuth {
366    Password(ConnectTokenPassword),
367    AuthCode(ConnectTokenAuthCode),
368    ClientCredentials(ConnectTokenClientCredentials),
369}
370
371#[derive(serde::Serialize, Debug)]
372struct ConnectTokenPassword {
373    username: String,
374    password: String,
375}
376
377#[derive(serde::Serialize, Debug)]
378struct ConnectTokenAuthCode {
379    code: String,
380    code_verifier: String,
381    redirect_uri: String,
382}
383
384#[derive(serde::Serialize, Debug)]
385struct ConnectTokenClientCredentials {
386    username: String,
387    client_secret: String,
388}
389
390#[derive(serde::Deserialize, Debug)]
391struct ConnectTokenRes {
392    access_token: String,
393    refresh_token: String,
394    #[serde(rename = "Key", alias = "key")]
395    key: String,
396}
397
398#[derive(serde::Deserialize, Debug)]
399struct ConnectErrorRes {
400    error: String,
401    error_description: Option<String>,
402    #[serde(rename = "ErrorModel", alias = "errorModel")]
403    error_model: Option<ConnectErrorResErrorModel>,
404    #[serde(rename = "TwoFactorProviders", alias = "twoFactorProviders")]
405    two_factor_providers: Option<Vec<TwoFactorProviderType>>,
406    #[serde(
407        rename = "SsoEmail2faSessionToken",
408        alias = "ssoEmail2faSessionToken"
409    )]
410    sso_email_2fa_session_token: Option<String>,
411}
412
413#[derive(serde::Deserialize, Debug)]
414struct ConnectErrorResErrorModel {
415    #[serde(rename = "Message", alias = "message")]
416    message: String,
417}
418
419#[derive(serde::Serialize, Debug)]
420struct ConnectRefreshTokenReq {
421    grant_type: String,
422    client_id: String,
423    refresh_token: String,
424}
425
426#[derive(serde::Deserialize, Debug)]
427struct ConnectRefreshTokenRes {
428    access_token: String,
429}
430
431#[derive(serde::Serialize, Debug)]
432struct SendEmailLoginReq {
433    email: String,
434    #[serde(rename = "DeviceIdentifier", alias = "deviceIdentifier")]
435    device_identifier: String,
436    #[serde(
437        rename = "SsoEmail2faSessionToken",
438        alias = "ssoEmail2faSessionToken"
439    )]
440    sso_email_2fa_session_token: String,
441}
442
443#[derive(serde::Deserialize, Debug)]
444struct SyncRes {
445    #[serde(rename = "Ciphers", alias = "ciphers")]
446    ciphers: Vec<SyncResCipher>,
447    #[serde(rename = "Profile", alias = "profile")]
448    profile: SyncResProfile,
449    #[serde(rename = "Folders", alias = "folders")]
450    folders: Vec<SyncResFolder>,
451}
452
453#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
454struct SyncResCipher {
455    #[serde(rename = "Id", alias = "id")]
456    id: String,
457    #[serde(rename = "FolderId", alias = "folderId")]
458    folder_id: Option<String>,
459    #[serde(rename = "OrganizationId", alias = "organizationId")]
460    organization_id: Option<String>,
461    #[serde(rename = "Name", alias = "name")]
462    name: String,
463    #[serde(rename = "Login", alias = "login")]
464    login: Option<CipherLogin>,
465    #[serde(rename = "Card", alias = "card")]
466    card: Option<CipherCard>,
467    #[serde(rename = "Identity", alias = "identity")]
468    identity: Option<CipherIdentity>,
469    #[serde(rename = "SecureNote", alias = "secureNote")]
470    secure_note: Option<CipherSecureNote>,
471    #[serde(rename = "SshKey", alias = "sshKey")]
472    ssh_key: Option<CipherSshKey>,
473    #[serde(rename = "Notes", alias = "notes")]
474    notes: Option<String>,
475    #[serde(rename = "PasswordHistory", alias = "passwordHistory")]
476    password_history: Option<Vec<SyncResPasswordHistory>>,
477    #[serde(rename = "Fields", alias = "fields")]
478    fields: Option<Vec<CipherField>>,
479    #[serde(rename = "DeletedDate", alias = "deletedDate")]
480    deleted_date: Option<String>,
481    #[serde(rename = "Key", alias = "key")]
482    key: Option<String>,
483    #[serde(rename = "Reprompt", alias = "reprompt")]
484    reprompt: CipherRepromptType,
485}
486
487impl SyncResCipher {
488    fn to_entry(
489        &self,
490        folders: &[SyncResFolder],
491    ) -> Option<crate::db::Entry> {
492        if self.deleted_date.is_some() {
493            return None;
494        }
495        let history =
496            self.password_history
497                .as_ref()
498                .map_or_else(Vec::new, |history| {
499                    history
500                        .iter()
501                        .filter_map(|entry| {
502                            // Gets rid of entries with a non-existent
503                            // password
504                            entry.password.clone().map(|p| {
505                                crate::db::HistoryEntry {
506                                    last_used_date: entry
507                                        .last_used_date
508                                        .clone(),
509                                    password: p,
510                                }
511                            })
512                        })
513                        .collect()
514                });
515
516        let (folder, folder_id) =
517            self.folder_id.as_ref().map_or((None, None), |folder_id| {
518                let mut folder_name = None;
519                for folder in folders {
520                    if &folder.id == folder_id {
521                        folder_name = Some(folder.name.clone());
522                    }
523                }
524                (folder_name, Some(folder_id))
525            });
526        let data = if let Some(login) = &self.login {
527            crate::db::EntryData::Login {
528                username: login.username.clone(),
529                password: login.password.clone(),
530                totp: login.totp.clone(),
531                uris: login.uris.as_ref().map_or_else(
532                    std::vec::Vec::new,
533                    |uris| {
534                        uris.iter()
535                            .filter_map(|uri| {
536                                uri.uri.clone().map(|s| crate::db::Uri {
537                                    uri: s,
538                                    match_type: uri.match_type,
539                                })
540                            })
541                            .collect()
542                    },
543                ),
544            }
545        } else if let Some(card) = &self.card {
546            crate::db::EntryData::Card {
547                cardholder_name: card.cardholder_name.clone(),
548                number: card.number.clone(),
549                brand: card.brand.clone(),
550                exp_month: card.exp_month.clone(),
551                exp_year: card.exp_year.clone(),
552                code: card.code.clone(),
553            }
554        } else if let Some(identity) = &self.identity {
555            crate::db::EntryData::Identity {
556                title: identity.title.clone(),
557                first_name: identity.first_name.clone(),
558                middle_name: identity.middle_name.clone(),
559                last_name: identity.last_name.clone(),
560                address1: identity.address1.clone(),
561                address2: identity.address2.clone(),
562                address3: identity.address3.clone(),
563                city: identity.city.clone(),
564                state: identity.state.clone(),
565                postal_code: identity.postal_code.clone(),
566                country: identity.country.clone(),
567                phone: identity.phone.clone(),
568                email: identity.email.clone(),
569                ssn: identity.ssn.clone(),
570                license_number: identity.license_number.clone(),
571                passport_number: identity.passport_number.clone(),
572                username: identity.username.clone(),
573            }
574        } else if let Some(_secure_note) = &self.secure_note {
575            crate::db::EntryData::SecureNote
576        } else if let Some(ssh_key) = &self.ssh_key {
577            crate::db::EntryData::SshKey {
578                private_key: ssh_key.private_key.clone(),
579                public_key: ssh_key.public_key.clone(),
580                fingerprint: ssh_key.fingerprint.clone(),
581            }
582        } else {
583            return None;
584        };
585        let fields = self.fields.as_ref().map_or_else(Vec::new, |fields| {
586            fields
587                .iter()
588                .map(|field| crate::db::Field {
589                    ty: field.ty,
590                    name: field.name.clone(),
591                    value: field.value.clone(),
592                    linked_id: field.linked_id,
593                })
594                .collect()
595        });
596        Some(crate::db::Entry {
597            id: self.id.clone(),
598            org_id: self.organization_id.clone(),
599            folder,
600            folder_id: folder_id.map(std::string::ToString::to_string),
601            name: self.name.clone(),
602            data,
603            fields,
604            notes: self.notes.clone(),
605            history,
606            key: self.key.clone(),
607            master_password_reprompt: self.reprompt,
608        })
609    }
610}
611
612#[derive(serde::Deserialize, Debug)]
613struct SyncResProfile {
614    #[serde(rename = "Key", alias = "key")]
615    key: String,
616    #[serde(rename = "PrivateKey", alias = "privateKey")]
617    private_key: String,
618    #[serde(rename = "Organizations", alias = "organizations")]
619    organizations: Vec<SyncResProfileOrganization>,
620}
621
622#[derive(serde::Deserialize, Debug)]
623struct SyncResProfileOrganization {
624    #[serde(rename = "Id", alias = "id")]
625    id: String,
626    #[serde(rename = "Key", alias = "key")]
627    key: String,
628}
629
630#[derive(serde::Deserialize, Debug, Clone)]
631struct SyncResFolder {
632    #[serde(rename = "Id", alias = "id")]
633    id: String,
634    #[serde(rename = "Name", alias = "name")]
635    name: String,
636}
637
638#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
639struct CipherLogin {
640    #[serde(rename = "Username", alias = "username")]
641    username: Option<String>,
642    #[serde(rename = "Password", alias = "password")]
643    password: Option<String>,
644    #[serde(rename = "Totp", alias = "totp")]
645    totp: Option<String>,
646    #[serde(rename = "Uris", alias = "uris")]
647    uris: Option<Vec<CipherLoginUri>>,
648}
649
650#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
651struct CipherLoginUri {
652    #[serde(rename = "Uri", alias = "uri")]
653    uri: Option<String>,
654    #[serde(rename = "Match", alias = "match")]
655    match_type: Option<UriMatchType>,
656}
657
658#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
659struct CipherCard {
660    #[serde(rename = "CardholderName", alias = "cardholderName")]
661    cardholder_name: Option<String>,
662    #[serde(rename = "Number", alias = "number")]
663    number: Option<String>,
664    #[serde(rename = "Brand", alias = "brand")]
665    brand: Option<String>,
666    #[serde(rename = "ExpMonth", alias = "expMonth")]
667    exp_month: Option<String>,
668    #[serde(rename = "ExpYear", alias = "expYear")]
669    exp_year: Option<String>,
670    #[serde(rename = "Code", alias = "code")]
671    code: Option<String>,
672}
673
674#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
675struct CipherIdentity {
676    #[serde(rename = "Title", alias = "title")]
677    title: Option<String>,
678    #[serde(rename = "FirstName", alias = "firstName")]
679    first_name: Option<String>,
680    #[serde(rename = "MiddleName", alias = "middleName")]
681    middle_name: Option<String>,
682    #[serde(rename = "LastName", alias = "lastName")]
683    last_name: Option<String>,
684    #[serde(rename = "Address1", alias = "address1")]
685    address1: Option<String>,
686    #[serde(rename = "Address2", alias = "address2")]
687    address2: Option<String>,
688    #[serde(rename = "Address3", alias = "address3")]
689    address3: Option<String>,
690    #[serde(rename = "City", alias = "city")]
691    city: Option<String>,
692    #[serde(rename = "State", alias = "state")]
693    state: Option<String>,
694    #[serde(rename = "PostalCode", alias = "postalCode")]
695    postal_code: Option<String>,
696    #[serde(rename = "Country", alias = "country")]
697    country: Option<String>,
698    #[serde(rename = "Phone", alias = "phone")]
699    phone: Option<String>,
700    #[serde(rename = "Email", alias = "email")]
701    email: Option<String>,
702    #[serde(rename = "SSN", alias = "ssn")]
703    ssn: Option<String>,
704    #[serde(rename = "LicenseNumber", alias = "licenseNumber")]
705    license_number: Option<String>,
706    #[serde(rename = "PassportNumber", alias = "passportNumber")]
707    passport_number: Option<String>,
708    #[serde(rename = "Username", alias = "username")]
709    username: Option<String>,
710}
711
712#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
713struct CipherSshKey {
714    #[serde(rename = "PrivateKey", alias = "privateKey")]
715    private_key: Option<String>,
716    #[serde(rename = "PublicKey", alias = "publicKey")]
717    public_key: Option<String>,
718    #[serde(rename = "Fingerprint", alias = "keyFingerprint")]
719    fingerprint: Option<String>,
720}
721
722#[derive(Debug, Clone, Copy, PartialEq, Eq)]
723#[repr(u16)]
724pub enum FieldType {
725    Text = 0,
726    Hidden = 1,
727    Boolean = 2,
728    Linked = 3,
729}
730
731impl serde::Serialize for FieldType {
732    fn serialize<S>(
733        &self,
734        serializer: S,
735    ) -> std::result::Result<S::Ok, S::Error>
736    where
737        S: serde::Serializer,
738    {
739        let v: u16 = match self {
740            Self::Text => 0,
741            Self::Hidden => 1,
742            Self::Boolean => 2,
743            Self::Linked => 3,
744        };
745        serializer.serialize_u16(v)
746    }
747}
748
749impl<'de> serde::Deserialize<'de> for FieldType {
750    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
751    where
752        D: serde::Deserializer<'de>,
753    {
754        let v = u16::deserialize(deserializer)?;
755        match v {
756            0 => Ok(Self::Text),
757            1 => Ok(Self::Hidden),
758            2 => Ok(Self::Boolean),
759            3 => Ok(Self::Linked),
760            _ => Err(serde::de::Error::custom(format!(
761                "invalid FieldType: {v}"
762            ))),
763        }
764    }
765}
766
767#[derive(Debug, Clone, Copy, PartialEq, Eq)]
768#[repr(u16)]
769pub enum LinkedIdType {
770    LoginUsername = 100,
771    LoginPassword = 101,
772    CardCardholderName = 300,
773    CardExpMonth = 301,
774    CardExpYear = 302,
775    CardCode = 303,
776    CardBrand = 304,
777    CardNumber = 305,
778    IdentityTitle = 400,
779    IdentityMiddleName = 401,
780    IdentityAddress1 = 402,
781    IdentityAddress2 = 403,
782    IdentityAddress3 = 404,
783    IdentityCity = 405,
784    IdentityState = 406,
785    IdentityPostalCode = 407,
786    IdentityCountry = 408,
787    IdentityCompany = 409,
788    IdentityEmail = 410,
789    IdentityPhone = 411,
790    IdentitySsn = 412,
791    IdentityUsername = 413,
792    IdentityPassportNumber = 414,
793    IdentityLicenseNumber = 415,
794    IdentityFirstName = 416,
795    IdentityLastName = 417,
796    IdentityFullName = 418,
797}
798
799impl serde::Serialize for LinkedIdType {
800    fn serialize<S>(
801        &self,
802        serializer: S,
803    ) -> std::result::Result<S::Ok, S::Error>
804    where
805        S: serde::Serializer,
806    {
807        let v: u16 = match self {
808            Self::LoginUsername => 100,
809            Self::LoginPassword => 101,
810            Self::CardCardholderName => 300,
811            Self::CardExpMonth => 301,
812            Self::CardExpYear => 302,
813            Self::CardCode => 303,
814            Self::CardBrand => 304,
815            Self::CardNumber => 305,
816            Self::IdentityTitle => 400,
817            Self::IdentityMiddleName => 401,
818            Self::IdentityAddress1 => 402,
819            Self::IdentityAddress2 => 403,
820            Self::IdentityAddress3 => 404,
821            Self::IdentityCity => 405,
822            Self::IdentityState => 406,
823            Self::IdentityPostalCode => 407,
824            Self::IdentityCountry => 408,
825            Self::IdentityCompany => 409,
826            Self::IdentityEmail => 410,
827            Self::IdentityPhone => 411,
828            Self::IdentitySsn => 412,
829            Self::IdentityUsername => 413,
830            Self::IdentityPassportNumber => 414,
831            Self::IdentityLicenseNumber => 415,
832            Self::IdentityFirstName => 416,
833            Self::IdentityLastName => 417,
834            Self::IdentityFullName => 418,
835        };
836        serializer.serialize_u16(v)
837    }
838}
839
840impl<'de> serde::Deserialize<'de> for LinkedIdType {
841    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
842    where
843        D: serde::Deserializer<'de>,
844    {
845        let v = u16::deserialize(deserializer)?;
846        match v {
847            100 => Ok(Self::LoginUsername),
848            101 => Ok(Self::LoginPassword),
849            300 => Ok(Self::CardCardholderName),
850            301 => Ok(Self::CardExpMonth),
851            302 => Ok(Self::CardExpYear),
852            303 => Ok(Self::CardCode),
853            304 => Ok(Self::CardBrand),
854            305 => Ok(Self::CardNumber),
855            400 => Ok(Self::IdentityTitle),
856            401 => Ok(Self::IdentityMiddleName),
857            402 => Ok(Self::IdentityAddress1),
858            403 => Ok(Self::IdentityAddress2),
859            404 => Ok(Self::IdentityAddress3),
860            405 => Ok(Self::IdentityCity),
861            406 => Ok(Self::IdentityState),
862            407 => Ok(Self::IdentityPostalCode),
863            408 => Ok(Self::IdentityCountry),
864            409 => Ok(Self::IdentityCompany),
865            410 => Ok(Self::IdentityEmail),
866            411 => Ok(Self::IdentityPhone),
867            412 => Ok(Self::IdentitySsn),
868            413 => Ok(Self::IdentityUsername),
869            414 => Ok(Self::IdentityPassportNumber),
870            415 => Ok(Self::IdentityLicenseNumber),
871            416 => Ok(Self::IdentityFirstName),
872            417 => Ok(Self::IdentityLastName),
873            418 => Ok(Self::IdentityFullName),
874            _ => Err(serde::de::Error::custom(format!(
875                "invalid LinkedIdType: {v}"
876            ))),
877        }
878    }
879}
880
881#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
882struct CipherField {
883    #[serde(rename = "Type", alias = "type")]
884    ty: Option<FieldType>,
885    #[serde(rename = "Name", alias = "name")]
886    name: Option<String>,
887    #[serde(rename = "Value", alias = "value")]
888    value: Option<String>,
889    #[serde(rename = "LinkedId", alias = "linkedId")]
890    linked_id: Option<LinkedIdType>,
891}
892
893// this is just a name and some notes, both of which are already on the cipher
894// object
895#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
896struct CipherSecureNote {}
897
898#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
899struct SyncResPasswordHistory {
900    #[serde(rename = "LastUsedDate", alias = "lastUsedDate")]
901    last_used_date: String,
902    #[serde(rename = "Password", alias = "password")]
903    password: Option<String>,
904}
905
906#[derive(serde::Serialize, Debug)]
907struct CiphersPostReq {
908    #[serde(rename = "type")]
909    ty: u32, // XXX what are the valid types?
910    #[serde(rename = "folderId")]
911    folder_id: Option<String>,
912    name: String,
913    notes: Option<String>,
914    login: Option<CipherLogin>,
915    card: Option<CipherCard>,
916    identity: Option<CipherIdentity>,
917    #[serde(rename = "secureNote")]
918    secure_note: Option<CipherSecureNote>,
919}
920
921#[derive(serde::Serialize, Debug)]
922struct CiphersPutReq {
923    #[serde(rename = "type")]
924    ty: u32, // XXX what are the valid types?
925    #[serde(rename = "folderId")]
926    folder_id: Option<String>,
927    #[serde(rename = "organizationId")]
928    organization_id: Option<String>,
929    name: String,
930    notes: Option<String>,
931    login: Option<CipherLogin>,
932    card: Option<CipherCard>,
933    identity: Option<CipherIdentity>,
934    fields: Vec<CipherField>,
935    #[serde(rename = "secureNote")]
936    secure_note: Option<CipherSecureNote>,
937    #[serde(rename = "passwordHistory")]
938    password_history: Vec<CiphersPutReqHistory>,
939}
940
941#[derive(serde::Serialize, Debug)]
942struct CiphersPutReqHistory {
943    #[serde(rename = "LastUsedDate")]
944    last_used_date: String,
945    #[serde(rename = "Password")]
946    password: String,
947}
948
949#[derive(serde::Deserialize, Debug)]
950struct FoldersRes {
951    #[serde(rename = "Data", alias = "data")]
952    data: Vec<FoldersResData>,
953}
954
955#[derive(serde::Deserialize, Debug)]
956struct FoldersResData {
957    #[serde(rename = "Id", alias = "id")]
958    id: String,
959    #[serde(rename = "Name", alias = "name")]
960    name: String,
961}
962
963#[derive(serde::Serialize, Debug)]
964struct FoldersPostReq {
965    name: String,
966}
967
968// Used for the Bitwarden-Client-Name header. Accepted values:
969// https://github.com/bitwarden/server/blob/main/src/Core/Enums/BitwardenClient.cs
970const BITWARDEN_CLIENT: &str = "cli";
971
972// DeviceType.LinuxDesktop, as per Bitwarden API device types.
973const DEVICE_TYPE: u8 = 8;
974
975#[derive(Debug)]
976pub struct Client {
977    base_url: String,
978    identity_url: String,
979    ui_url: String,
980    client_cert_path: Option<std::path::PathBuf>,
981}
982
983impl Client {
984    pub fn new(
985        base_url: &str,
986        identity_url: &str,
987        ui_url: &str,
988        client_cert_path: Option<&std::path::Path>,
989    ) -> Self {
990        Self {
991            base_url: base_url.to_string(),
992            identity_url: identity_url.to_string(),
993            ui_url: ui_url.to_string(),
994            client_cert_path: client_cert_path
995                .map(std::path::Path::to_path_buf),
996        }
997    }
998
999    async fn reqwest_client(&self) -> Result<reqwest::Client> {
1000        let mut default_headers = reqwest::header::HeaderMap::new();
1001        default_headers.insert(
1002            "Bitwarden-Client-Name",
1003            reqwest::header::HeaderValue::from_static(BITWARDEN_CLIENT),
1004        );
1005        default_headers.insert(
1006            "Bitwarden-Client-Version",
1007            reqwest::header::HeaderValue::from_static(env!(
1008                "CARGO_PKG_VERSION"
1009            )),
1010        );
1011        default_headers.append(
1012            "Device-Type",
1013            // unwrap is safe here because DEVICE_TYPE is a number and digits
1014            // are valid ASCII
1015            reqwest::header::HeaderValue::from_str(&DEVICE_TYPE.to_string())
1016                .unwrap(),
1017        );
1018        let user_agent = format!(
1019            "{}/{}",
1020            env!("CARGO_PKG_NAME"),
1021            env!("CARGO_PKG_VERSION")
1022        );
1023        if let Some(client_cert_path) = self.client_cert_path.as_ref() {
1024            let mut buf = Vec::new();
1025            let mut f = tokio::fs::File::open(client_cert_path)
1026                .await
1027                .map_err(|e| Error::LoadClientCert {
1028                    source: e,
1029                    file: client_cert_path.clone(),
1030                })?;
1031            f.read_to_end(&mut buf).await.map_err(|e| {
1032                Error::LoadClientCert {
1033                    source: e,
1034                    file: client_cert_path.clone(),
1035                }
1036            })?;
1037            let pem = reqwest::Identity::from_pem(&buf)
1038                .map_err(|e| Error::CreateReqwestClient { source: e })?;
1039            Ok(reqwest::Client::builder()
1040                .user_agent(user_agent)
1041                .identity(pem)
1042                .default_headers(default_headers)
1043                .build()
1044                .map_err(|e| Error::CreateReqwestClient { source: e })?)
1045        } else {
1046            Ok(reqwest::Client::builder()
1047                .user_agent(user_agent)
1048                .default_headers(default_headers)
1049                .build()
1050                .map_err(|e| Error::CreateReqwestClient { source: e })?)
1051        }
1052    }
1053
1054    pub async fn prelogin(
1055        &self,
1056        email: &str,
1057    ) -> Result<(KdfType, u32, Option<u32>, Option<u32>)> {
1058        let prelogin = PreloginReq {
1059            email: email.to_string(),
1060        };
1061        let client = self.reqwest_client().await?;
1062        let res = client
1063            .post(self.identity_url("/accounts/prelogin"))
1064            .json(&prelogin)
1065            .send()
1066            .await
1067            .map_err(|source| Error::Reqwest { source })?;
1068        let prelogin_res: PreloginRes = res.json_with_path().await?;
1069        Ok((
1070            prelogin_res.kdf,
1071            prelogin_res.kdf_iterations,
1072            prelogin_res.kdf_memory,
1073            prelogin_res.kdf_parallelism,
1074        ))
1075    }
1076
1077    pub async fn register(
1078        &self,
1079        email: &str,
1080        device_id: &str,
1081        apikey: &crate::locked::ApiKey,
1082    ) -> Result<()> {
1083        let connect_req = ConnectTokenReq {
1084            auth: ConnectTokenAuth::ClientCredentials(
1085                ConnectTokenClientCredentials {
1086                    username: email.to_string(),
1087                    client_secret: String::from_utf8(
1088                        apikey.client_secret().to_vec(),
1089                    )
1090                    .unwrap(),
1091                },
1092            ),
1093            grant_type: "client_credentials".to_string(),
1094            scope: "api".to_string(),
1095            // XXX unwraps here are not necessarily safe
1096            client_id: String::from_utf8(apikey.client_id().to_vec())
1097                .unwrap(),
1098            device_type: u32::from(DEVICE_TYPE),
1099            device_identifier: device_id.to_string(),
1100            device_name: "bwx".to_string(),
1101            device_push_token: String::new(),
1102            two_factor_token: None,
1103            two_factor_provider: None,
1104        };
1105        let client = self.reqwest_client().await?;
1106        let res = client
1107            .post(self.identity_url("/connect/token"))
1108            .form(&connect_req)
1109            .send()
1110            .await
1111            .map_err(|source| Error::Reqwest { source })?;
1112        if res.status() == reqwest::StatusCode::OK {
1113            Ok(())
1114        } else {
1115            let code = res.status().as_u16();
1116            match res.text().await {
1117                Ok(body) => match body.clone().json_with_path() {
1118                    Ok(json) => Err(classify_login_error(&json, code)),
1119                    Err(e) => {
1120                        log::warn!("{e}: {body}");
1121                        Err(Error::RequestFailed { status: code })
1122                    }
1123                },
1124                Err(e) => {
1125                    log::warn!("failed to read response body: {e}");
1126                    Err(Error::RequestFailed { status: code })
1127                }
1128            }
1129        }
1130    }
1131
1132    pub async fn login(
1133        &self,
1134        email: &str,
1135        sso_id: Option<&str>,
1136        device_id: &str,
1137        password_hash: &crate::locked::PasswordHash,
1138        two_factor_token: Option<&str>,
1139        two_factor_provider: Option<TwoFactorProviderType>,
1140    ) -> Result<(String, String, String)> {
1141        let connect_req = match sso_id {
1142            Some(sso_id) => {
1143                let (sso_code, sso_code_verifier, callback_url) =
1144                    self.obtain_sso_code(sso_id).await?;
1145
1146                ConnectTokenReq {
1147                    auth: ConnectTokenAuth::AuthCode(ConnectTokenAuthCode {
1148                        code: sso_code,
1149                        code_verifier: sso_code_verifier,
1150                        redirect_uri: callback_url,
1151                    }),
1152                    grant_type: "authorization_code".to_string(),
1153                    scope: "api offline_access".to_string(),
1154                    client_id: "cli".to_string(),
1155                    device_type: u32::from(DEVICE_TYPE),
1156                    device_identifier: device_id.to_string(),
1157                    device_name: "bwx".to_string(),
1158                    device_push_token: String::new(),
1159                    two_factor_token: two_factor_token
1160                        .map(std::string::ToString::to_string),
1161                    two_factor_provider: two_factor_provider
1162                        .map(|ty| ty as u32),
1163                }
1164            }
1165            None => ConnectTokenReq {
1166                auth: ConnectTokenAuth::Password(ConnectTokenPassword {
1167                    username: email.to_string(),
1168                    password: crate::base64::encode(password_hash.hash()),
1169                }),
1170
1171                grant_type: "password".to_string(),
1172                scope: "api offline_access".to_string(),
1173                client_id: "cli".to_string(),
1174                device_type: 8,
1175                device_identifier: device_id.to_string(),
1176                device_name: "bwx".to_string(),
1177                device_push_token: String::new(),
1178                two_factor_token: two_factor_token
1179                    .map(std::string::ToString::to_string),
1180                two_factor_provider: two_factor_provider.map(|ty| ty as u32),
1181            },
1182        };
1183
1184        let client = self.reqwest_client().await?;
1185        let res = client
1186            .post(self.identity_url("/connect/token"))
1187            .form(&connect_req)
1188            .header(
1189                "auth-email",
1190                crate::base64::encode_url_safe_no_pad(email),
1191            )
1192            .send()
1193            .await
1194            .map_err(|source| Error::Reqwest { source })?;
1195
1196        if res.status() == reqwest::StatusCode::OK {
1197            let connect_res: ConnectTokenRes = res.json_with_path().await?;
1198            Ok((
1199                connect_res.access_token,
1200                connect_res.refresh_token,
1201                connect_res.key,
1202            ))
1203        } else {
1204            let code = res.status().as_u16();
1205            match res.text().await {
1206                Ok(body) => match body.clone().json_with_path() {
1207                    Ok(json) => Err(classify_login_error(&json, code)),
1208                    Err(e) => {
1209                        log::warn!("{e}: {body}");
1210                        Err(Error::RequestFailed { status: code })
1211                    }
1212                },
1213                Err(e) => {
1214                    log::warn!("failed to read response body: {e}");
1215                    Err(Error::RequestFailed { status: code })
1216                }
1217            }
1218        }
1219    }
1220
1221    pub async fn send_email_login(
1222        &self,
1223        email: &str,
1224        device_id: &str,
1225        sso_email_2fa_session_token: &str,
1226    ) -> Result<()> {
1227        let send_email_login_req = SendEmailLoginReq {
1228            email: email.to_string(),
1229            device_identifier: device_id.to_string(),
1230            sso_email_2fa_session_token: sso_email_2fa_session_token
1231                .to_string(),
1232        };
1233
1234        let client = self.reqwest_client().await?;
1235        let res = client
1236            .post(self.api_url("/two-factor/send-email-login"))
1237            .json(&send_email_login_req)
1238            .header(
1239                "auth-email",
1240                crate::base64::encode_url_safe_no_pad(email),
1241            )
1242            .send()
1243            .await
1244            .map_err(|source| Error::Reqwest { source })?;
1245
1246        if res.status() == reqwest::StatusCode::OK {
1247            Ok(())
1248        } else {
1249            let code = res.status().as_u16();
1250            log::warn!("{code}: {:?}", res.text().await);
1251            Err(Error::RequestFailed { status: code })
1252        }
1253    }
1254
1255    async fn obtain_sso_code(
1256        &self,
1257        sso_id: &str,
1258    ) -> Result<(String, String, String)> {
1259        let state =
1260            rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 64);
1261        let sso_code_verifier =
1262            rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 64);
1263
1264        let mut hasher = sha2::Sha256::new();
1265        hasher.update(sso_code_verifier.clone());
1266        let code_challenge =
1267            crate::base64::encode_url_safe_no_pad(hasher.finalize());
1268
1269        let port = find_free_port(8065, 8070).await?;
1270
1271        let listener = tokio::net::TcpListener::bind(("127.0.0.1", port))
1272            .await
1273            .map_err(|e| Error::CreateSSOCallbackServer { err: e })?;
1274
1275        let callback_server =
1276            start_sso_callback_server(listener, state.as_str());
1277
1278        let callback_url =
1279            "http://localhost:".to_string() + port.to_string().as_str();
1280
1281        let url = self.ui_url.clone()
1282            + "/#/sso?clientId="
1283            + "cli"
1284            + "&redirectUri="
1285            + urlencoding::encode(callback_url.as_str())
1286                .into_owned()
1287                .as_str()
1288            + "&state="
1289            + state.as_str()
1290            + "&codeChallenge="
1291            + code_challenge.as_str()
1292            + "&identifier="
1293            + sso_id;
1294
1295        #[cfg(feature = "sso-browser")]
1296        open::that(&url)
1297            .map_err(|e| Error::FailedToOpenWebBrowser { err: e })?;
1298        #[cfg(not(feature = "sso-browser"))]
1299        eprintln!("Open this URL to continue: {url}");
1300        // TODO: probably it'd be better to display the URL in the console if the automatic
1301        // open operation fails, instead of failing the whole process? E.g. docker container
1302        // case
1303
1304        let sso_code = callback_server.await?;
1305
1306        Ok((sso_code, sso_code_verifier, callback_url))
1307    }
1308
1309    pub async fn sync(
1310        &self,
1311        access_token: &str,
1312    ) -> Result<(
1313        String,
1314        String,
1315        std::collections::HashMap<String, String>,
1316        Vec<crate::db::Entry>,
1317    )> {
1318        let client = self.reqwest_client().await?;
1319        let res = client
1320            .get(self.api_url("/sync"))
1321            .header("Authorization", format!("Bearer {access_token}"))
1322            // This is necessary for vaultwarden to include the ssh keys in the response
1323            .header("Bitwarden-Client-Version", "2024.12.0")
1324            .send()
1325            .await
1326            .map_err(|source| Error::Reqwest { source })?;
1327        match res.status() {
1328            reqwest::StatusCode::OK => {
1329                let sync_res: SyncRes = res.json_with_path().await?;
1330                let folders = sync_res.folders.clone();
1331                let ciphers = sync_res
1332                    .ciphers
1333                    .iter()
1334                    .filter_map(|cipher| cipher.to_entry(&folders))
1335                    .collect();
1336                let org_keys = sync_res
1337                    .profile
1338                    .organizations
1339                    .iter()
1340                    .map(|org| (org.id.clone(), org.key.clone()))
1341                    .collect();
1342                Ok((
1343                    sync_res.profile.key,
1344                    sync_res.profile.private_key,
1345                    org_keys,
1346                    ciphers,
1347                ))
1348            }
1349            reqwest::StatusCode::UNAUTHORIZED => {
1350                Err(Error::RequestUnauthorized)
1351            }
1352            _ => Err(Error::RequestFailed {
1353                status: res.status().as_u16(),
1354            }),
1355        }
1356    }
1357
1358    pub fn add(
1359        &self,
1360        access_token: &str,
1361        name: &str,
1362        data: &crate::db::EntryData,
1363        notes: Option<&str>,
1364        folder_id: Option<&str>,
1365    ) -> Result<()> {
1366        let mut req = CiphersPostReq {
1367            ty: 1,
1368            folder_id: folder_id.map(std::string::ToString::to_string),
1369            name: name.to_string(),
1370            notes: notes.map(std::string::ToString::to_string),
1371            login: None,
1372            card: None,
1373            identity: None,
1374            secure_note: None,
1375        };
1376        match data {
1377            crate::db::EntryData::Login {
1378                username,
1379                password,
1380                totp,
1381                uris,
1382            } => {
1383                let uris = if uris.is_empty() {
1384                    None
1385                } else {
1386                    Some(
1387                        uris.iter()
1388                            .map(|s| CipherLoginUri {
1389                                uri: Some(s.uri.clone()),
1390                                match_type: s.match_type,
1391                            })
1392                            .collect(),
1393                    )
1394                };
1395                req.login = Some(CipherLogin {
1396                    username: username.clone(),
1397                    password: password.clone(),
1398                    totp: totp.clone(),
1399                    uris,
1400                });
1401            }
1402            crate::db::EntryData::Card {
1403                cardholder_name,
1404                number,
1405                brand,
1406                exp_month,
1407                exp_year,
1408                code,
1409            } => {
1410                req.card = Some(CipherCard {
1411                    cardholder_name: cardholder_name.clone(),
1412                    number: number.clone(),
1413                    brand: brand.clone(),
1414                    exp_month: exp_month.clone(),
1415                    exp_year: exp_year.clone(),
1416                    code: code.clone(),
1417                });
1418            }
1419            crate::db::EntryData::Identity {
1420                title,
1421                first_name,
1422                middle_name,
1423                last_name,
1424                address1,
1425                address2,
1426                address3,
1427                city,
1428                state,
1429                postal_code,
1430                country,
1431                phone,
1432                email,
1433                ssn,
1434                license_number,
1435                passport_number,
1436                username,
1437            } => {
1438                req.identity = Some(CipherIdentity {
1439                    title: title.clone(),
1440                    first_name: first_name.clone(),
1441                    middle_name: middle_name.clone(),
1442                    last_name: last_name.clone(),
1443                    address1: address1.clone(),
1444                    address2: address2.clone(),
1445                    address3: address3.clone(),
1446                    city: city.clone(),
1447                    state: state.clone(),
1448                    postal_code: postal_code.clone(),
1449                    country: country.clone(),
1450                    phone: phone.clone(),
1451                    email: email.clone(),
1452                    ssn: ssn.clone(),
1453                    license_number: license_number.clone(),
1454                    passport_number: passport_number.clone(),
1455                    username: username.clone(),
1456                });
1457            }
1458            crate::db::EntryData::SecureNote => {
1459                req.secure_note = Some(CipherSecureNote {});
1460            }
1461            crate::db::EntryData::SshKey { .. } => unreachable!(),
1462        }
1463        let client = reqwest::blocking::Client::new();
1464        let res = client
1465            .post(self.api_url("/ciphers"))
1466            .header("Authorization", format!("Bearer {access_token}"))
1467            .json(&req)
1468            .send()
1469            .map_err(|source| Error::Reqwest { source })?;
1470        match res.status() {
1471            reqwest::StatusCode::OK => Ok(()),
1472            reqwest::StatusCode::UNAUTHORIZED => {
1473                Err(Error::RequestUnauthorized)
1474            }
1475            _ => Err(Error::RequestFailed {
1476                status: res.status().as_u16(),
1477            }),
1478        }
1479    }
1480
1481    pub fn edit(
1482        &self,
1483        access_token: &str,
1484        id: &str,
1485        org_id: Option<&str>,
1486        name: &str,
1487        data: &crate::db::EntryData,
1488        fields: &[crate::db::Field],
1489        notes: Option<&str>,
1490        folder_uuid: Option<&str>,
1491        history: &[crate::db::HistoryEntry],
1492    ) -> Result<()> {
1493        let mut req = CiphersPutReq {
1494            ty: match data {
1495                crate::db::EntryData::Login { .. } => 1,
1496                crate::db::EntryData::SecureNote => 2,
1497                crate::db::EntryData::Card { .. } => 3,
1498                crate::db::EntryData::Identity { .. } => 4,
1499                crate::db::EntryData::SshKey { .. } => unreachable!(),
1500            },
1501            folder_id: folder_uuid.map(std::string::ToString::to_string),
1502            organization_id: org_id.map(std::string::ToString::to_string),
1503            name: name.to_string(),
1504            notes: notes.map(std::string::ToString::to_string),
1505            login: None,
1506            card: None,
1507            identity: None,
1508            secure_note: None,
1509            fields: fields
1510                .iter()
1511                .map(|field| CipherField {
1512                    ty: field.ty,
1513                    name: field.name.clone(),
1514                    value: field.value.clone(),
1515                    linked_id: field.linked_id,
1516                })
1517                .collect(),
1518            password_history: history
1519                .iter()
1520                .map(|entry| CiphersPutReqHistory {
1521                    last_used_date: entry.last_used_date.clone(),
1522                    password: entry.password.clone(),
1523                })
1524                .collect(),
1525        };
1526        match data {
1527            crate::db::EntryData::Login {
1528                username,
1529                password,
1530                totp,
1531                uris,
1532            } => {
1533                let uris = if uris.is_empty() {
1534                    None
1535                } else {
1536                    Some(
1537                        uris.iter()
1538                            .map(|s| CipherLoginUri {
1539                                uri: Some(s.uri.clone()),
1540                                match_type: s.match_type,
1541                            })
1542                            .collect(),
1543                    )
1544                };
1545                req.login = Some(CipherLogin {
1546                    username: username.clone(),
1547                    password: password.clone(),
1548                    totp: totp.clone(),
1549                    uris,
1550                });
1551            }
1552            crate::db::EntryData::Card {
1553                cardholder_name,
1554                number,
1555                brand,
1556                exp_month,
1557                exp_year,
1558                code,
1559            } => {
1560                req.card = Some(CipherCard {
1561                    cardholder_name: cardholder_name.clone(),
1562                    number: number.clone(),
1563                    brand: brand.clone(),
1564                    exp_month: exp_month.clone(),
1565                    exp_year: exp_year.clone(),
1566                    code: code.clone(),
1567                });
1568            }
1569            crate::db::EntryData::Identity {
1570                title,
1571                first_name,
1572                middle_name,
1573                last_name,
1574                address1,
1575                address2,
1576                address3,
1577                city,
1578                state,
1579                postal_code,
1580                country,
1581                phone,
1582                email,
1583                ssn,
1584                license_number,
1585                passport_number,
1586                username,
1587            } => {
1588                req.identity = Some(CipherIdentity {
1589                    title: title.clone(),
1590                    first_name: first_name.clone(),
1591                    middle_name: middle_name.clone(),
1592                    last_name: last_name.clone(),
1593                    address1: address1.clone(),
1594                    address2: address2.clone(),
1595                    address3: address3.clone(),
1596                    city: city.clone(),
1597                    state: state.clone(),
1598                    postal_code: postal_code.clone(),
1599                    country: country.clone(),
1600                    phone: phone.clone(),
1601                    email: email.clone(),
1602                    ssn: ssn.clone(),
1603                    license_number: license_number.clone(),
1604                    passport_number: passport_number.clone(),
1605                    username: username.clone(),
1606                });
1607            }
1608            crate::db::EntryData::SecureNote => {
1609                req.secure_note = Some(CipherSecureNote {});
1610            }
1611            crate::db::EntryData::SshKey { .. } => unreachable!(),
1612        }
1613        let client = reqwest::blocking::Client::new();
1614        let res = client
1615            .put(self.api_url(&format!("/ciphers/{id}")))
1616            .header("Authorization", format!("Bearer {access_token}"))
1617            .json(&req)
1618            .send()
1619            .map_err(|source| Error::Reqwest { source })?;
1620        match res.status() {
1621            reqwest::StatusCode::OK => Ok(()),
1622            reqwest::StatusCode::UNAUTHORIZED => {
1623                Err(Error::RequestUnauthorized)
1624            }
1625            _ => Err(Error::RequestFailed {
1626                status: res.status().as_u16(),
1627            }),
1628        }
1629    }
1630
1631    pub fn remove(&self, access_token: &str, id: &str) -> Result<()> {
1632        let client = reqwest::blocking::Client::new();
1633        let res = client
1634            .delete(self.api_url(&format!("/ciphers/{id}")))
1635            .header("Authorization", format!("Bearer {access_token}"))
1636            .send()
1637            .map_err(|source| Error::Reqwest { source })?;
1638        match res.status() {
1639            reqwest::StatusCode::OK => Ok(()),
1640            reqwest::StatusCode::UNAUTHORIZED => {
1641                Err(Error::RequestUnauthorized)
1642            }
1643            _ => Err(Error::RequestFailed {
1644                status: res.status().as_u16(),
1645            }),
1646        }
1647    }
1648
1649    pub fn folders(
1650        &self,
1651        access_token: &str,
1652    ) -> Result<Vec<(String, String)>> {
1653        let client = reqwest::blocking::Client::new();
1654        let res = client
1655            .get(self.api_url("/folders"))
1656            .header("Authorization", format!("Bearer {access_token}"))
1657            .send()
1658            .map_err(|source| Error::Reqwest { source })?;
1659        match res.status() {
1660            reqwest::StatusCode::OK => {
1661                let folders_res: FoldersRes = res.json_with_path()?;
1662                Ok(folders_res
1663                    .data
1664                    .iter()
1665                    .map(|folder| (folder.id.clone(), folder.name.clone()))
1666                    .collect())
1667            }
1668            reqwest::StatusCode::UNAUTHORIZED => {
1669                Err(Error::RequestUnauthorized)
1670            }
1671            _ => Err(Error::RequestFailed {
1672                status: res.status().as_u16(),
1673            }),
1674        }
1675    }
1676
1677    pub fn create_folder(
1678        &self,
1679        access_token: &str,
1680        name: &str,
1681    ) -> Result<String> {
1682        let req = FoldersPostReq {
1683            name: name.to_string(),
1684        };
1685        let client = reqwest::blocking::Client::new();
1686        let res = client
1687            .post(self.api_url("/folders"))
1688            .header("Authorization", format!("Bearer {access_token}"))
1689            .json(&req)
1690            .send()
1691            .map_err(|source| Error::Reqwest { source })?;
1692        match res.status() {
1693            reqwest::StatusCode::OK => {
1694                let folders_res: FoldersResData = res.json_with_path()?;
1695                Ok(folders_res.id)
1696            }
1697            reqwest::StatusCode::UNAUTHORIZED => {
1698                Err(Error::RequestUnauthorized)
1699            }
1700            _ => Err(Error::RequestFailed {
1701                status: res.status().as_u16(),
1702            }),
1703        }
1704    }
1705
1706    pub fn exchange_refresh_token(
1707        &self,
1708        refresh_token: &str,
1709    ) -> Result<String> {
1710        let connect_req = ConnectRefreshTokenReq {
1711            grant_type: "refresh_token".to_string(),
1712            client_id: "cli".to_string(),
1713            refresh_token: refresh_token.to_string(),
1714        };
1715        let client = reqwest::blocking::Client::new();
1716        let res = client
1717            .post(self.identity_url("/connect/token"))
1718            .form(&connect_req)
1719            .send()
1720            .map_err(|source| Error::Reqwest { source })?;
1721        let connect_res: ConnectRefreshTokenRes = res.json_with_path()?;
1722        Ok(connect_res.access_token)
1723    }
1724
1725    pub async fn exchange_refresh_token_async(
1726        &self,
1727        refresh_token: &str,
1728    ) -> Result<String> {
1729        let connect_req = ConnectRefreshTokenReq {
1730            grant_type: "refresh_token".to_string(),
1731            client_id: "cli".to_string(),
1732            refresh_token: refresh_token.to_string(),
1733        };
1734        let client = self.reqwest_client().await?;
1735        let res = client
1736            .post(self.identity_url("/connect/token"))
1737            .form(&connect_req)
1738            .send()
1739            .await
1740            .map_err(|source| Error::Reqwest { source })?;
1741        let connect_res: ConnectRefreshTokenRes =
1742            res.json_with_path().await?;
1743        Ok(connect_res.access_token)
1744    }
1745
1746    fn api_url(&self, path: &str) -> String {
1747        format!("{}{}", self.base_url, path)
1748    }
1749
1750    fn identity_url(&self, path: &str) -> String {
1751        format!("{}{}", self.identity_url, path)
1752    }
1753}
1754
1755async fn find_free_port(bottom: u16, top: u16) -> Result<u16> {
1756    for port in bottom..top {
1757        if tokio::net::TcpListener::bind(("127.0.0.1", port))
1758            .await
1759            .is_ok()
1760        {
1761            return Ok(port);
1762        }
1763    }
1764
1765    Err(Error::FailedToFindFreePort {
1766        range: format!("({bottom}..{top})"),
1767    })
1768}
1769
1770async fn start_sso_callback_server(
1771    listener: tokio::net::TcpListener,
1772    state: &str,
1773) -> Result<String> {
1774    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
1775    const SUCCESS_BODY: &str =
1776        "<html><head><title>Success | bwx</title></head><body> \
1777         <h1>Successfully authenticated with bwx</h1> \
1778         <p>You may now close this tab and return to the terminal.</p> \
1779         </body></html>";
1780    const FAILURE_BODY: &str =
1781        "<html><head><title>Failed | bwx</title></head><body> \
1782         <h1>Something went wrong logging into the bwx</h1> \
1783         <p>You may now close this tab and return to the terminal.</p> \
1784         </body></html>";
1785    const MAX_REQUEST_BYTES: usize = 16 * 1024;
1786
1787    loop {
1788        let (mut stream, _peer) = listener.accept().await.map_err(|e| {
1789            Error::FailedToProcessSSOCallback {
1790                msg: format!("accept: {e}"),
1791            }
1792        })?;
1793
1794        let mut buf = Vec::with_capacity(1024);
1795        let mut chunk = [0u8; 1024];
1796        let headers_complete = loop {
1797            let Ok(n) = stream.read(&mut chunk).await else {
1798                break false;
1799            };
1800            if n == 0 {
1801                break false;
1802            }
1803            buf.extend_from_slice(&chunk[..n]);
1804            if buf.windows(4).any(|w| w == b"\r\n\r\n") {
1805                break true;
1806            }
1807            if buf.len() >= MAX_REQUEST_BYTES {
1808                break false;
1809            }
1810        };
1811        if !headers_complete {
1812            continue;
1813        }
1814
1815        let request_line = std::str::from_utf8(&buf)
1816            .ok()
1817            .and_then(|s| s.lines().next())
1818            .unwrap_or("");
1819        let mut parts = request_line.split_whitespace();
1820        let method = parts.next().unwrap_or("");
1821        let target = parts.next().unwrap_or("");
1822
1823        if method != "GET" {
1824            let _ = write_response(
1825                &mut stream,
1826                "405 Method Not Allowed",
1827                FAILURE_BODY,
1828            )
1829            .await;
1830            continue;
1831        }
1832
1833        let query = target.split_once('?').map_or("", |x| x.1);
1834        let params = parse_query(query);
1835        let result = sso_query_code(&params, state);
1836
1837        let (status, body) = match &result {
1838            Ok(_) => ("200 OK", SUCCESS_BODY),
1839            Err(_) => ("400 Bad Request", FAILURE_BODY),
1840        };
1841        let _ = write_response(&mut stream, status, body).await;
1842        let _ = stream.shutdown().await;
1843
1844        return result;
1845    }
1846}
1847
1848fn parse_query(query: &str) -> std::collections::HashMap<String, String> {
1849    query
1850        .split('&')
1851        .filter(|kv| !kv.is_empty())
1852        .filter_map(|kv| {
1853            let (k, v) = kv.split_once('=').unwrap_or((kv, ""));
1854            let key = urlencoding::decode(k).ok()?.into_owned();
1855            let val = urlencoding::decode(v).ok()?.into_owned();
1856            Some((key, val))
1857        })
1858        .collect()
1859}
1860
1861async fn write_response(
1862    stream: &mut tokio::net::TcpStream,
1863    status: &str,
1864    body: &str,
1865) -> std::io::Result<()> {
1866    use tokio::io::AsyncWriteExt as _;
1867    let response = format!(
1868        "HTTP/1.1 {status}\r\n\
1869         Content-Type: text/html; charset=utf-8\r\n\
1870         Content-Length: {len}\r\n\
1871         Connection: close\r\n\
1872         \r\n\
1873         {body}",
1874        len = body.len(),
1875    );
1876    stream.write_all(response.as_bytes()).await
1877}
1878
1879fn sso_query_code(
1880    params: &std::collections::HashMap<String, String>,
1881    state: &str,
1882) -> Result<String> {
1883    let sso_code =
1884        params
1885            .get("code")
1886            .ok_or(Error::FailedToProcessSSOCallback {
1887                msg: "Could not obtain code from the URL".to_string(),
1888            })?;
1889
1890    let received_state =
1891        params
1892            .get("state")
1893            .ok_or(Error::FailedToProcessSSOCallback {
1894                msg: "Could not obtain state from the URL".to_string(),
1895            })?;
1896
1897    if received_state.split("_identifier=").next().unwrap() != state {
1898        // Intentionally does not include either state value — they are
1899        // live OAuth state tokens and this error can reach stderr / logs.
1900        return Err(Error::FailedToProcessSSOCallback {
1901            msg: "SSO callback state mismatch".to_string(),
1902        });
1903    }
1904
1905    Ok(sso_code.clone())
1906}
1907
1908fn classify_login_error(error_res: &ConnectErrorRes, code: u16) -> Error {
1909    let error_desc = error_res.error_description.clone();
1910    let error_desc = error_desc.as_deref();
1911    match error_res.error.as_str() {
1912        "invalid_grant" => match error_desc {
1913            Some("invalid_username_or_password") => {
1914                if let Some(error_model) = error_res.error_model.as_ref() {
1915                    let message = error_model.message.as_str().to_string();
1916                    return Error::IncorrectPassword { message };
1917                }
1918            }
1919            Some("Two factor required.") => {
1920                if let Some(providers) =
1921                    error_res.two_factor_providers.as_ref()
1922                {
1923                    return Error::TwoFactorRequired {
1924                        providers: providers.clone(),
1925                        sso_email_2fa_session_token: error_res
1926                            .sso_email_2fa_session_token
1927                            .clone(),
1928                    };
1929                }
1930            }
1931            Some("Captcha required.") => {
1932                return Error::RegistrationRequired;
1933            }
1934            _ => {}
1935        },
1936        "invalid_client" => {
1937            return Error::IncorrectApiKey;
1938        }
1939        ""
1940            // bitwarden_rs returns an empty error and error_description for
1941            // this case, for some reason
1942            if error_desc.is_none() || error_desc == Some("") =>
1943        {
1944            if let Some(error_model) = error_res.error_model.as_ref() {
1945                let message = error_model.message.as_str().to_string();
1946                match message.as_str() {
1947                    "Username or password is incorrect. Try again"
1948                    | "TOTP code is not a number" => {
1949                        return Error::IncorrectPassword { message };
1950                    }
1951                    s => {
1952                        if s.starts_with(
1953                            "Invalid TOTP code! Server time: ",
1954                        ) {
1955                            return Error::IncorrectPassword { message };
1956                        }
1957                    }
1958                }
1959            }
1960        }
1961        _ => {}
1962    }
1963
1964    log::warn!("unexpected error received during login: {error_res:?}");
1965    Error::RequestFailed { status: code }
1966}
1967
1968#[cfg(test)]
1969mod tests {
1970    use super::*;
1971
1972    fn roundtrip_u8<T>(variants: &[(T, u8)])
1973    where
1974        T: serde::Serialize
1975            + for<'de> serde::Deserialize<'de>
1976            + PartialEq
1977            + std::fmt::Debug
1978            + Copy,
1979    {
1980        for (variant, n) in variants {
1981            let v = serde_json::to_value(variant).unwrap();
1982            assert_eq!(v, serde_json::json!(n));
1983            let back: T = serde_json::from_value(v).unwrap();
1984            assert_eq!(&back, variant);
1985        }
1986    }
1987
1988    fn roundtrip_u16<T>(variants: &[(T, u16)])
1989    where
1990        T: serde::Serialize
1991            + for<'de> serde::Deserialize<'de>
1992            + PartialEq
1993            + std::fmt::Debug
1994            + Copy,
1995    {
1996        for (variant, n) in variants {
1997            let v = serde_json::to_value(variant).unwrap();
1998            assert_eq!(v, serde_json::json!(n));
1999            let back: T = serde_json::from_value(v).unwrap();
2000            assert_eq!(&back, variant);
2001        }
2002    }
2003
2004    #[test]
2005    fn uri_match_type_roundtrip() {
2006        roundtrip_u8(&[
2007            (UriMatchType::Domain, 0),
2008            (UriMatchType::Host, 1),
2009            (UriMatchType::StartsWith, 2),
2010            (UriMatchType::Exact, 3),
2011            (UriMatchType::RegularExpression, 4),
2012            (UriMatchType::Never, 5),
2013        ]);
2014        let err =
2015            serde_json::from_value::<UriMatchType>(serde_json::json!(99));
2016        assert!(err.is_err());
2017    }
2018
2019    #[test]
2020    fn cipher_reprompt_type_roundtrip() {
2021        roundtrip_u8(&[
2022            (CipherRepromptType::None, 0),
2023            (CipherRepromptType::Password, 1),
2024        ]);
2025        let err = serde_json::from_value::<CipherRepromptType>(
2026            serde_json::json!(9),
2027        );
2028        assert!(err.is_err());
2029    }
2030
2031    #[test]
2032    fn field_type_roundtrip() {
2033        roundtrip_u16(&[
2034            (FieldType::Text, 0),
2035            (FieldType::Hidden, 1),
2036            (FieldType::Boolean, 2),
2037            (FieldType::Linked, 3),
2038        ]);
2039        let err = serde_json::from_value::<FieldType>(serde_json::json!(999));
2040        assert!(err.is_err());
2041    }
2042
2043    #[test]
2044    fn two_factor_provider_type_from_u64() {
2045        let cases = [
2046            (0, TwoFactorProviderType::Authenticator),
2047            (1, TwoFactorProviderType::Email),
2048            (2, TwoFactorProviderType::Duo),
2049            (3, TwoFactorProviderType::Yubikey),
2050            (4, TwoFactorProviderType::U2f),
2051            (5, TwoFactorProviderType::Remember),
2052            (6, TwoFactorProviderType::OrganizationDuo),
2053            (7, TwoFactorProviderType::WebAuthn),
2054        ];
2055        for (n, expected) in cases {
2056            let got: TwoFactorProviderType =
2057                serde_json::from_value(serde_json::json!(n)).unwrap();
2058            assert_eq!(got, expected);
2059        }
2060        // Unknown numeric variants fail rather than silently decoding.
2061        let err = serde_json::from_value::<TwoFactorProviderType>(
2062            serde_json::json!(42),
2063        );
2064        assert!(err.is_err());
2065    }
2066
2067    #[test]
2068    fn two_factor_provider_type_from_str_map_key() {
2069        // Bitwarden sometimes serializes provider ids as stringified
2070        // digits (they appear as JSON object keys). The custom
2071        // `visit_str` delegates to FromStr, which accepts ASCII digits.
2072        let json = serde_json::json!("3");
2073        let got: TwoFactorProviderType =
2074            serde_json::from_value(json).unwrap();
2075        assert_eq!(got, TwoFactorProviderType::Yubikey);
2076
2077        let err = serde_json::from_value::<TwoFactorProviderType>(
2078            serde_json::json!("not-a-number"),
2079        );
2080        assert!(err.is_err());
2081    }
2082
2083    #[test]
2084    fn kdf_type_deserialize() {
2085        let p: KdfType =
2086            serde_json::from_value(serde_json::json!(0)).unwrap();
2087        assert_eq!(p, KdfType::Pbkdf2);
2088        let a: KdfType =
2089            serde_json::from_value(serde_json::json!(1)).unwrap();
2090        assert_eq!(a, KdfType::Argon2id);
2091        // Unknown numeric variants fail rather than silently decoding.
2092        let err = serde_json::from_value::<KdfType>(serde_json::json!(9));
2093        assert!(err.is_err());
2094    }
2095
2096    #[test]
2097    fn kdf_type_serialize_as_string() {
2098        // Bitwarden's API expects the KDF type as the string "0"/"1"
2099        // in POST bodies, not a JSON number — the custom Serialize
2100        // impl encodes that intentionally.
2101        assert_eq!(
2102            serde_json::to_value(KdfType::Pbkdf2).unwrap(),
2103            serde_json::json!("0")
2104        );
2105        assert_eq!(
2106            serde_json::to_value(KdfType::Argon2id).unwrap(),
2107            serde_json::json!("1")
2108        );
2109    }
2110
2111    #[test]
2112    fn parse_query_basic() {
2113        let got = parse_query("code=abc&state=xyz");
2114        assert_eq!(got.get("code").map(String::as_str), Some("abc"));
2115        assert_eq!(got.get("state").map(String::as_str), Some("xyz"));
2116        assert_eq!(got.len(), 2);
2117    }
2118
2119    #[test]
2120    fn parse_query_empty() {
2121        assert!(parse_query("").is_empty());
2122    }
2123
2124    #[test]
2125    fn parse_query_percent_decodes_value_and_key() {
2126        // Bitwarden packs `_identifier=<org>` into the state value as an
2127        // appendage, percent-encoded.
2128        let got = parse_query("state=abc_identifier%3Dfoo&code=%20%2B");
2129        assert_eq!(
2130            got.get("state").map(String::as_str),
2131            Some("abc_identifier=foo")
2132        );
2133        assert_eq!(got.get("code").map(String::as_str), Some(" +"));
2134    }
2135
2136    #[test]
2137    fn parse_query_handles_missing_value() {
2138        // `foo` with no `=` should yield `foo` -> "".
2139        let got = parse_query("foo&bar=baz");
2140        assert_eq!(got.get("foo").map(String::as_str), Some(""));
2141        assert_eq!(got.get("bar").map(String::as_str), Some("baz"));
2142    }
2143
2144    #[test]
2145    fn parse_query_drops_empty_pairs() {
2146        // Bare `&` separators shouldn't contribute empty-key entries.
2147        let got = parse_query("&&a=1&&");
2148        assert_eq!(got.len(), 1);
2149        assert_eq!(got.get("a").map(String::as_str), Some("1"));
2150    }
2151
2152    #[test]
2153    fn sso_query_code_rejects_state_mismatch() {
2154        let mut params = std::collections::HashMap::new();
2155        params.insert("code".to_string(), "the-code".to_string());
2156        params.insert("state".to_string(), "other-state".to_string());
2157        let err = sso_query_code(&params, "expected-state")
2158            .expect_err("expected state mismatch to error");
2159        // The redacted message must NOT include either state token.
2160        let s = format!("{err}");
2161        assert!(!s.contains("other-state"), "leaked received state: {s}");
2162        assert!(!s.contains("expected-state"), "leaked sent state: {s}");
2163    }
2164
2165    #[test]
2166    fn sso_query_code_accepts_matching_state_with_identifier_suffix() {
2167        // Bitwarden appends `_identifier=<org>` to the state in the
2168        // callback; we match the prefix only.
2169        let mut params = std::collections::HashMap::new();
2170        params.insert("code".to_string(), "the-code".to_string());
2171        params
2172            .insert("state".to_string(), "s123_identifier=acme".to_string());
2173        let code = sso_query_code(&params, "s123").unwrap();
2174        assert_eq!(code, "the-code");
2175    }
2176
2177    #[test]
2178    fn linked_id_type_roundtrip() {
2179        roundtrip_u16(&[
2180            (LinkedIdType::LoginUsername, 100),
2181            (LinkedIdType::LoginPassword, 101),
2182            (LinkedIdType::CardCardholderName, 300),
2183            (LinkedIdType::CardExpMonth, 301),
2184            (LinkedIdType::CardExpYear, 302),
2185            (LinkedIdType::CardCode, 303),
2186            (LinkedIdType::CardBrand, 304),
2187            (LinkedIdType::CardNumber, 305),
2188            (LinkedIdType::IdentityTitle, 400),
2189            (LinkedIdType::IdentityMiddleName, 401),
2190            (LinkedIdType::IdentityAddress1, 402),
2191            (LinkedIdType::IdentityAddress2, 403),
2192            (LinkedIdType::IdentityAddress3, 404),
2193            (LinkedIdType::IdentityCity, 405),
2194            (LinkedIdType::IdentityState, 406),
2195            (LinkedIdType::IdentityPostalCode, 407),
2196            (LinkedIdType::IdentityCountry, 408),
2197            (LinkedIdType::IdentityCompany, 409),
2198            (LinkedIdType::IdentityEmail, 410),
2199            (LinkedIdType::IdentityPhone, 411),
2200            (LinkedIdType::IdentitySsn, 412),
2201            (LinkedIdType::IdentityUsername, 413),
2202            (LinkedIdType::IdentityPassportNumber, 414),
2203            (LinkedIdType::IdentityLicenseNumber, 415),
2204            (LinkedIdType::IdentityFirstName, 416),
2205            (LinkedIdType::IdentityLastName, 417),
2206            (LinkedIdType::IdentityFullName, 418),
2207        ]);
2208        let err =
2209            serde_json::from_value::<LinkedIdType>(serde_json::json!(9999));
2210        assert!(err.is_err());
2211    }
2212}