1use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel};
2use bitwarden_core::{
3 key_management::{KeyIds, SymmetricKeyId},
4 require,
5};
6use bitwarden_crypto::{
7 CompositeEncryptable, CryptoError, Decryptable, EncString, IdentifyKey, KeyStoreContext,
8 OctetStreamBytes, PrimitiveEncryptable, generate_random_bytes,
9};
10use bitwarden_encoding::{B64, B64Url};
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use serde_repr::{Deserialize_repr, Serialize_repr};
14use uuid::Uuid;
15use zeroize::Zeroizing;
16
17use crate::SendParseError;
18
19const SEND_ITERATIONS: u32 = 100_000;
20
21#[derive(Serialize, Deserialize, Debug)]
22#[serde(rename_all = "camelCase", deny_unknown_fields)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
24pub struct SendFile {
25 pub id: Option<String>,
26 pub file_name: EncString,
27 pub size: Option<String>,
28 pub size_name: Option<String>,
30}
31
32#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
33#[serde(rename_all = "camelCase", deny_unknown_fields)]
34#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
35pub struct SendFileView {
36 pub id: Option<String>,
37 pub file_name: String,
38 pub size: Option<String>,
39 pub size_name: Option<String>,
41}
42
43#[derive(Serialize, Deserialize, Debug)]
44#[serde(rename_all = "camelCase", deny_unknown_fields)]
45#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
46pub struct SendText {
47 pub text: Option<EncString>,
48 pub hidden: bool,
49}
50
51#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
53#[serde(rename_all = "camelCase", deny_unknown_fields)]
54#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
55pub struct SendTextView {
56 pub text: Option<String>,
58 pub hidden: bool,
60}
61
62#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, PartialEq)]
64#[repr(u8)]
65#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
66pub enum SendType {
67 Text = 0,
69 File = 1,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
75#[repr(u8)]
76#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
77pub enum AuthType {
78 Email = 0,
80
81 Password = 1,
83
84 None = 2,
86}
87
88#[allow(missing_docs)]
89#[derive(Serialize, Deserialize, Debug)]
90#[serde(rename_all = "camelCase", deny_unknown_fields)]
91#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
92pub struct Send {
93 pub id: Option<Uuid>,
94 pub access_id: Option<String>,
95
96 pub name: EncString,
97 pub notes: Option<EncString>,
98 pub key: EncString,
99 pub password: Option<String>,
100
101 pub r#type: SendType,
102 pub file: Option<SendFile>,
103 pub text: Option<SendText>,
104
105 pub max_access_count: Option<u32>,
106 pub access_count: u32,
107 pub disabled: bool,
108 pub hide_email: bool,
109
110 pub revision_date: DateTime<Utc>,
111 pub deletion_date: DateTime<Utc>,
112 pub expiration_date: Option<DateTime<Utc>>,
113
114 pub emails: Option<String>,
118 pub auth_type: AuthType,
119}
120
121#[allow(missing_docs)]
122#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
123#[serde(rename_all = "camelCase", deny_unknown_fields)]
124#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
125pub struct SendView {
126 pub id: Option<Uuid>,
127 pub access_id: Option<String>,
128
129 pub name: String,
130 pub notes: Option<String>,
131 pub key: Option<String>,
133 pub new_password: Option<String>,
137 pub has_password: bool,
140
141 pub r#type: SendType,
142 pub file: Option<SendFileView>,
143 pub text: Option<SendTextView>,
144
145 pub max_access_count: Option<u32>,
146 pub access_count: u32,
147 pub disabled: bool,
148 pub hide_email: bool,
149
150 pub revision_date: DateTime<Utc>,
151 pub deletion_date: DateTime<Utc>,
152 pub expiration_date: Option<DateTime<Utc>>,
153
154 pub emails: Vec<String>,
158 pub auth_type: AuthType,
159}
160
161#[allow(missing_docs)]
162#[derive(Serialize, Deserialize, Debug)]
163#[serde(rename_all = "camelCase", deny_unknown_fields)]
164#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
165pub struct SendListView {
166 pub id: Option<Uuid>,
167 pub access_id: Option<String>,
168
169 pub name: String,
170
171 pub r#type: SendType,
172 pub disabled: bool,
173
174 pub revision_date: DateTime<Utc>,
175 pub deletion_date: DateTime<Utc>,
176 pub expiration_date: Option<DateTime<Utc>>,
177
178 pub auth_type: AuthType,
179}
180
181impl Send {
182 #[allow(missing_docs)]
183 pub fn get_key(
184 ctx: &mut KeyStoreContext<KeyIds>,
185 send_key: &EncString,
186 enc_key: SymmetricKeyId,
187 ) -> Result<SymmetricKeyId, CryptoError> {
188 let key: Vec<u8> = send_key.decrypt(ctx, enc_key)?;
189 Self::derive_shareable_key(ctx, &key)
190 }
191
192 fn derive_shareable_key(
193 ctx: &mut KeyStoreContext<KeyIds>,
194 key: &[u8],
195 ) -> Result<SymmetricKeyId, CryptoError> {
196 let key = Zeroizing::new(key.try_into().map_err(|_| CryptoError::InvalidKeyLen)?);
197 ctx.derive_shareable_key(key, "send", Some("send"))
198 }
199}
200
201impl IdentifyKey<SymmetricKeyId> for Send {
202 fn key_identifier(&self) -> SymmetricKeyId {
203 SymmetricKeyId::User
204 }
205}
206
207impl IdentifyKey<SymmetricKeyId> for SendView {
208 fn key_identifier(&self) -> SymmetricKeyId {
209 SymmetricKeyId::User
210 }
211}
212
213impl Decryptable<KeyIds, SymmetricKeyId, SendTextView> for SendText {
214 fn decrypt(
215 &self,
216 ctx: &mut KeyStoreContext<KeyIds>,
217 key: SymmetricKeyId,
218 ) -> Result<SendTextView, CryptoError> {
219 Ok(SendTextView {
220 text: self.text.decrypt(ctx, key)?,
221 hidden: self.hidden,
222 })
223 }
224}
225
226impl CompositeEncryptable<KeyIds, SymmetricKeyId, SendText> for SendTextView {
227 fn encrypt_composite(
228 &self,
229 ctx: &mut KeyStoreContext<KeyIds>,
230 key: SymmetricKeyId,
231 ) -> Result<SendText, CryptoError> {
232 Ok(SendText {
233 text: self.text.encrypt(ctx, key)?,
234 hidden: self.hidden,
235 })
236 }
237}
238
239impl Decryptable<KeyIds, SymmetricKeyId, SendFileView> for SendFile {
240 fn decrypt(
241 &self,
242 ctx: &mut KeyStoreContext<KeyIds>,
243 key: SymmetricKeyId,
244 ) -> Result<SendFileView, CryptoError> {
245 Ok(SendFileView {
246 id: self.id.clone(),
247 file_name: self.file_name.decrypt(ctx, key)?,
248 size: self.size.clone(),
249 size_name: self.size_name.clone(),
250 })
251 }
252}
253
254impl CompositeEncryptable<KeyIds, SymmetricKeyId, SendFile> for SendFileView {
255 fn encrypt_composite(
256 &self,
257 ctx: &mut KeyStoreContext<KeyIds>,
258 key: SymmetricKeyId,
259 ) -> Result<SendFile, CryptoError> {
260 Ok(SendFile {
261 id: self.id.clone(),
262 file_name: self.file_name.encrypt(ctx, key)?,
263 size: self.size.clone(),
264 size_name: self.size_name.clone(),
265 })
266 }
267}
268
269impl Decryptable<KeyIds, SymmetricKeyId, SendView> for Send {
270 fn decrypt(
271 &self,
272 ctx: &mut KeyStoreContext<KeyIds>,
273 key: SymmetricKeyId,
274 ) -> Result<SendView, CryptoError> {
275 let k: Vec<u8> = self.key.decrypt(ctx, key)?;
279 let key = Send::derive_shareable_key(ctx, &k)?;
280
281 Ok(SendView {
282 id: self.id,
283 access_id: self.access_id.clone(),
284
285 name: self.name.decrypt(ctx, key).ok().unwrap_or_default(),
286 notes: self.notes.decrypt(ctx, key).ok().flatten(),
287 key: Some(B64Url::from(k).to_string()),
288 new_password: None,
289 has_password: self.password.is_some(),
290
291 r#type: self.r#type,
292 file: self.file.decrypt(ctx, key).ok().flatten(),
293 text: self.text.decrypt(ctx, key).ok().flatten(),
294
295 max_access_count: self.max_access_count,
296 access_count: self.access_count,
297 disabled: self.disabled,
298 hide_email: self.hide_email,
299
300 revision_date: self.revision_date,
301 deletion_date: self.deletion_date,
302 expiration_date: self.expiration_date,
303
304 emails: self
305 .emails
306 .as_deref()
307 .unwrap_or_default()
308 .split(',')
309 .map(|e| e.trim())
310 .filter(|e| !e.is_empty())
311 .map(String::from)
312 .collect(),
313 auth_type: self.auth_type,
314 })
315 }
316}
317
318impl Decryptable<KeyIds, SymmetricKeyId, SendListView> for Send {
319 fn decrypt(
320 &self,
321 ctx: &mut KeyStoreContext<KeyIds>,
322 key: SymmetricKeyId,
323 ) -> Result<SendListView, CryptoError> {
324 let key = Send::get_key(ctx, &self.key, key)?;
328
329 Ok(SendListView {
330 id: self.id,
331 access_id: self.access_id.clone(),
332
333 name: self.name.decrypt(ctx, key)?,
334 r#type: self.r#type,
335
336 disabled: self.disabled,
337
338 revision_date: self.revision_date,
339 deletion_date: self.deletion_date,
340 expiration_date: self.expiration_date,
341
342 auth_type: self.auth_type,
343 })
344 }
345}
346
347impl CompositeEncryptable<KeyIds, SymmetricKeyId, Send> for SendView {
348 fn encrypt_composite(
349 &self,
350 ctx: &mut KeyStoreContext<KeyIds>,
351 key: SymmetricKeyId,
352 ) -> Result<Send, CryptoError> {
353 let k = match (&self.key, &self.id) {
357 (Some(k), _) => B64Url::try_from(k.as_str())
359 .map_err(|_| CryptoError::InvalidKey)?
360 .as_bytes()
361 .to_vec(),
362 (None, None) => {
364 let key = generate_random_bytes::<[u8; 16]>();
365 key.to_vec()
366 }
367 _ => return Err(CryptoError::InvalidKey),
369 };
370 let send_key = Send::derive_shareable_key(ctx, &k)?;
371
372 Ok(Send {
373 id: self.id,
374 access_id: self.access_id.clone(),
375
376 name: self.name.encrypt(ctx, send_key)?,
377 notes: self.notes.encrypt(ctx, send_key)?,
378 key: OctetStreamBytes::from(k.clone()).encrypt(ctx, key)?,
379 password: self.new_password.as_ref().map(|password| {
380 let password = bitwarden_crypto::pbkdf2(password.as_bytes(), &k, SEND_ITERATIONS);
381 B64::from(password.as_slice()).to_string()
382 }),
383
384 r#type: self.r#type,
385 file: self.file.encrypt_composite(ctx, send_key)?,
386 text: self.text.encrypt_composite(ctx, send_key)?,
387
388 max_access_count: self.max_access_count,
389 access_count: self.access_count,
390 disabled: self.disabled,
391 hide_email: self.hide_email,
392
393 revision_date: self.revision_date,
394 deletion_date: self.deletion_date,
395 expiration_date: self.expiration_date,
396
397 emails: (!self.emails.is_empty()).then(|| self.emails.join(",")),
398 auth_type: self.auth_type,
399 })
400 }
401}
402
403impl TryFrom<SendResponseModel> for Send {
404 type Error = SendParseError;
405
406 fn try_from(send: SendResponseModel) -> Result<Self, Self::Error> {
407 let auth_type = match send.auth_type {
408 Some(t) => t.try_into()?,
409 None => {
410 if send.password.is_some() {
411 AuthType::Password
412 } else if send.emails.is_some() {
413 AuthType::Email
414 } else {
415 AuthType::None
416 }
417 }
418 };
419 Ok(Send {
420 id: send.id,
421 access_id: send.access_id,
422 name: require!(send.name).parse()?,
423 notes: EncString::try_from_optional(send.notes)?,
424 key: require!(send.key).parse()?,
425 password: send.password,
426 r#type: require!(send.r#type).try_into()?,
427 file: send.file.map(|f| (*f).try_into()).transpose()?,
428 text: send.text.map(|t| (*t).try_into()).transpose()?,
429 max_access_count: send.max_access_count.map(|s| s as u32),
430 access_count: require!(send.access_count) as u32,
431 disabled: send.disabled.unwrap_or(false),
432 hide_email: send.hide_email.unwrap_or(false),
433 revision_date: require!(send.revision_date).parse()?,
434 deletion_date: require!(send.deletion_date).parse()?,
435 expiration_date: send.expiration_date.map(|s| s.parse()).transpose()?,
436 emails: send.emails,
437 auth_type,
438 })
439 }
440}
441
442impl TryFrom<bitwarden_api_api::models::SendType> for SendType {
443 type Error = bitwarden_core::MissingFieldError;
444
445 fn try_from(t: bitwarden_api_api::models::SendType) -> Result<Self, Self::Error> {
446 Ok(match t {
447 bitwarden_api_api::models::SendType::Text => SendType::Text,
448 bitwarden_api_api::models::SendType::File => SendType::File,
449 bitwarden_api_api::models::SendType::__Unknown(_) => {
450 return Err(bitwarden_core::MissingFieldError("type"));
451 }
452 })
453 }
454}
455
456impl TryFrom<bitwarden_api_api::models::AuthType> for AuthType {
457 type Error = bitwarden_core::MissingFieldError;
458
459 fn try_from(value: bitwarden_api_api::models::AuthType) -> Result<Self, Self::Error> {
460 Ok(match value {
461 bitwarden_api_api::models::AuthType::Email => AuthType::Email,
462 bitwarden_api_api::models::AuthType::Password => AuthType::Password,
463 bitwarden_api_api::models::AuthType::None => AuthType::None,
464 bitwarden_api_api::models::AuthType::__Unknown(_) => {
465 return Err(bitwarden_core::MissingFieldError("auth_type"));
466 }
467 })
468 }
469}
470
471impl TryFrom<SendFileModel> for SendFile {
472 type Error = SendParseError;
473
474 fn try_from(file: SendFileModel) -> Result<Self, Self::Error> {
475 Ok(SendFile {
476 id: file.id,
477 file_name: require!(file.file_name).parse()?,
478 size: file.size.map(|v| v.to_string()),
479 size_name: file.size_name,
480 })
481 }
482}
483
484impl TryFrom<SendTextModel> for SendText {
485 type Error = SendParseError;
486
487 fn try_from(text: SendTextModel) -> Result<Self, Self::Error> {
488 Ok(SendText {
489 text: EncString::try_from_optional(text.text)?,
490 hidden: text.hidden.unwrap_or(false),
491 })
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use bitwarden_core::key_management::create_test_crypto_with_user_key;
498 use bitwarden_crypto::SymmetricCryptoKey;
499
500 use super::*;
501
502 #[test]
503 fn test_get_send_key() {
504 let user_key: SymmetricCryptoKey = "w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q==".to_string().try_into().unwrap();
506 let crypto = create_test_crypto_with_user_key(user_key);
507 let mut ctx = crypto.context();
508
509 let send_key = "2.+1KUfOX8A83Xkwk1bumo/w==|Nczvv+DTkeP466cP/wMDnGK6W9zEIg5iHLhcuQG6s+M=|SZGsfuIAIaGZ7/kzygaVUau3LeOvJUlolENBOU+LX7g="
510 .parse()
511 .unwrap();
512
513 let send_key = Send::get_key(&mut ctx, &send_key, SymmetricKeyId::User).unwrap();
515 #[allow(deprecated)]
516 let send_key = ctx.dangerous_get_symmetric_key(send_key).unwrap();
517 let send_key_b64 = send_key.to_base64();
518 assert_eq!(
519 send_key_b64.to_string(),
520 "IR9ImHGm6rRuIjiN7csj94bcZR5WYTJj5GtNfx33zm6tJCHUl+QZlpNPba8g2yn70KnOHsAODLcR0um6E3MAlg=="
521 );
522 }
523
524 #[test]
525 pub fn test_decrypt() {
526 let user_key: SymmetricCryptoKey = "bYCsk857hl8QJJtxyRK65tjUrbxKC4aDifJpsml+NIv4W9cVgFvi3qVD+yJTUU2T4UwNKWYtt9pqWf7Q+2WCCg==".to_string().try_into().unwrap();
527 let crypto = create_test_crypto_with_user_key(user_key);
528
529 let send = Send {
530 id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(),
531 access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()),
532 r#type: SendType::Text,
533 name: "2.STIyTrfDZN/JXNDN9zNEMw==|NDLum8BHZpPNYhJo9ggSkg==|UCsCLlBO3QzdPwvMAWs2VVwuE6xwOx/vxOooPObqnEw=".parse()
534 .unwrap(),
535 notes: None,
536 file: None,
537 text: Some(SendText {
538 text: "2.2VPyLzk1tMLug0X3x7RkaQ==|mrMt9vbZsCJhJIj4eebKyg==|aZ7JeyndytEMR1+uEBupEvaZuUE69D/ejhfdJL8oKq0=".parse().ok(),
539 hidden: false,
540 }),
541 key: "2.KLv/j0V4Ebs0dwyPdtt4vw==|jcrFuNYN1Qb3onBlwvtxUV/KpdnR1LPRL4EsCoXNAt4=|gHSywGy4Rj/RsCIZFwze4s2AACYKBtqDXTrQXjkgtIE=".parse().unwrap(),
542 max_access_count: None,
543 access_count: 0,
544 password: None,
545 disabled: false,
546 revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(),
547 expiration_date: None,
548 deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(),
549 hide_email: false,
550 emails: None,
551 auth_type: AuthType::None,
552 };
553
554 let view: SendView = crypto.decrypt(&send).unwrap();
555
556 let expected = SendView {
557 id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(),
558 access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()),
559 name: "Test".to_string(),
560 notes: None,
561 key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
562 new_password: None,
563 has_password: false,
564 r#type: SendType::Text,
565 file: None,
566 text: Some(SendTextView {
567 text: Some("This is a test".to_owned()),
568 hidden: false,
569 }),
570 max_access_count: None,
571 access_count: 0,
572 disabled: false,
573 hide_email: false,
574 revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(),
575 deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(),
576 expiration_date: None,
577 emails: Vec::new(),
578 auth_type: AuthType::None,
579 };
580
581 assert_eq!(view, expected);
582 }
583
584 #[test]
585 pub fn test_encrypt() {
586 let user_key: SymmetricCryptoKey = "bYCsk857hl8QJJtxyRK65tjUrbxKC4aDifJpsml+NIv4W9cVgFvi3qVD+yJTUU2T4UwNKWYtt9pqWf7Q+2WCCg==".to_string().try_into().unwrap();
587 let crypto = create_test_crypto_with_user_key(user_key);
588
589 let view = SendView {
590 id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(),
591 access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()),
592 name: "Test".to_string(),
593 notes: None,
594 key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
595 new_password: None,
596 has_password: false,
597 r#type: SendType::Text,
598 file: None,
599 text: Some(SendTextView {
600 text: Some("This is a test".to_owned()),
601 hidden: false,
602 }),
603 max_access_count: None,
604 access_count: 0,
605 disabled: false,
606 hide_email: false,
607 revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(),
608 deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(),
609 expiration_date: None,
610 emails: Vec::new(),
611 auth_type: AuthType::None,
612 };
613
614 let v: SendView = crypto
616 .decrypt(&crypto.encrypt(view.clone()).unwrap())
617 .unwrap();
618 assert_eq!(v, view);
619 }
620
621 #[test]
622 pub fn test_create() {
623 let user_key: SymmetricCryptoKey = "bYCsk857hl8QJJtxyRK65tjUrbxKC4aDifJpsml+NIv4W9cVgFvi3qVD+yJTUU2T4UwNKWYtt9pqWf7Q+2WCCg==".to_string().try_into().unwrap();
624 let crypto = create_test_crypto_with_user_key(user_key);
625
626 let view = SendView {
627 id: None,
628 access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()),
629 name: "Test".to_string(),
630 notes: None,
631 key: None,
632 new_password: None,
633 has_password: false,
634 r#type: SendType::Text,
635 file: None,
636 text: Some(SendTextView {
637 text: Some("This is a test".to_owned()),
638 hidden: false,
639 }),
640 max_access_count: None,
641 access_count: 0,
642 disabled: false,
643 hide_email: false,
644 revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(),
645 deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(),
646 expiration_date: None,
647 emails: Vec::new(),
648 auth_type: AuthType::None,
649 };
650
651 let v: SendView = crypto
653 .decrypt(&crypto.encrypt(view.clone()).unwrap())
654 .unwrap();
655
656 let t = SendView { key: None, ..v };
658 assert_eq!(t, view);
659 }
660
661 #[test]
662 pub fn test_create_password() {
663 let user_key: SymmetricCryptoKey = "bYCsk857hl8QJJtxyRK65tjUrbxKC4aDifJpsml+NIv4W9cVgFvi3qVD+yJTUU2T4UwNKWYtt9pqWf7Q+2WCCg==".to_string().try_into().unwrap();
664 let crypto = create_test_crypto_with_user_key(user_key);
665
666 let view = SendView {
667 id: None,
668 access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()),
669 name: "Test".to_owned(),
670 notes: None,
671 key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
672 new_password: Some("abc123".to_owned()),
673 has_password: false,
674 r#type: SendType::Text,
675 file: None,
676 text: Some(SendTextView {
677 text: Some("This is a test".to_owned()),
678 hidden: false,
679 }),
680 max_access_count: None,
681 access_count: 0,
682 disabled: false,
683 hide_email: false,
684 revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(),
685 deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(),
686 expiration_date: None,
687 emails: Vec::new(),
688 auth_type: AuthType::Password,
689 };
690
691 let send: Send = crypto.encrypt(view).unwrap();
692
693 assert_eq!(
694 send.password,
695 Some("vTIDfdj3FTDbejmMf+mJWpYdMXsxfeSd1Sma3sjCtiQ=".to_owned())
696 );
697 assert_eq!(send.auth_type, AuthType::Password);
698
699 let v: SendView = crypto.decrypt(&send).unwrap();
700 assert_eq!(v.new_password, None);
701 assert!(v.has_password);
702 assert_eq!(v.auth_type, AuthType::Password);
703 }
704
705 #[test]
706 pub fn test_create_email_otp() {
707 let user_key: SymmetricCryptoKey = "bYCsk857hl8QJJtxyRK65tjUrbxKC4aDifJpsml+NIv4W9cVgFvi3qVD+yJTUU2T4UwNKWYtt9pqWf7Q+2WCCg==".to_string().try_into().unwrap();
708 let crypto = create_test_crypto_with_user_key(user_key);
709
710 let view = SendView {
711 id: None,
712 access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()),
713 name: "Test".to_owned(),
714 notes: None,
715 key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
716 new_password: None,
717 has_password: false,
718 r#type: SendType::Text,
719 file: None,
720 text: Some(SendTextView {
721 text: Some("This is a test".to_owned()),
722 hidden: false,
723 }),
724 max_access_count: None,
725 access_count: 0,
726 disabled: false,
727 hide_email: false,
728 revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(),
729 deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(),
730 expiration_date: None,
731 emails: vec![
732 String::from("[email protected]"),
733 String::from("[email protected]"),
734 ],
735 auth_type: AuthType::Email,
736 };
737
738 let send: Send = crypto.encrypt(view.clone()).unwrap();
739
740 let v: SendView = crypto.decrypt(&send).unwrap();
742
743 assert_eq!(v, view);
744 }
745}