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