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 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#[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, #[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, #[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
968const BITWARDEN_CLIENT: &str = "cli";
971
972const 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 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 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 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 .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(¶ms, 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 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 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 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 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 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 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 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 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 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(¶ms, "expected-state")
2158 .expect_err("expected state mismatch to error");
2159 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 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(¶ms, "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}