bitwarden_vault/
password_history.rs1use bitwarden_api_api::models::CipherPasswordHistoryModel;
2use bitwarden_core::key_management::{KeySlotIds, SymmetricKeySlotId};
3use bitwarden_crypto::{
4 CompositeEncryptable, CryptoError, Decryptable, EncString, IdentifyKey, KeyStoreContext,
5 PrimitiveEncryptable,
6};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10use crate::VaultParseError;
11
12pub(crate) const MAX_PASSWORD_HISTORY_ENTRIES: usize = 5;
14
15#[allow(missing_docs)]
16#[derive(Serialize, Deserialize, Debug, Clone)]
17#[serde(rename_all = "camelCase", deny_unknown_fields)]
18#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
19#[bitwarden_ffi::wasm_record]
20pub struct PasswordHistory {
21 password: EncString,
22 last_used_date: DateTime<Utc>,
23}
24
25#[allow(missing_docs)]
26#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
27#[serde(rename_all = "camelCase", deny_unknown_fields)]
28#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
29#[bitwarden_ffi::wasm_record]
30pub struct PasswordHistoryView {
31 pub password: String,
32 pub last_used_date: DateTime<Utc>,
33}
34
35impl IdentifyKey<SymmetricKeySlotId> for PasswordHistory {
36 fn key_identifier(&self) -> SymmetricKeySlotId {
37 SymmetricKeySlotId::LocalUserData
38 }
39}
40impl IdentifyKey<SymmetricKeySlotId> for PasswordHistoryView {
41 fn key_identifier(&self) -> SymmetricKeySlotId {
42 SymmetricKeySlotId::LocalUserData
43 }
44}
45
46impl CompositeEncryptable<KeySlotIds, SymmetricKeySlotId, PasswordHistory> for PasswordHistoryView {
47 fn encrypt_composite(
48 &self,
49 ctx: &mut KeyStoreContext<KeySlotIds>,
50 key: SymmetricKeySlotId,
51 ) -> Result<PasswordHistory, CryptoError> {
52 Ok(PasswordHistory {
53 password: self.password.encrypt(ctx, key)?,
54 last_used_date: self.last_used_date,
55 })
56 }
57}
58
59impl Decryptable<KeySlotIds, SymmetricKeySlotId, PasswordHistoryView> for PasswordHistory {
60 fn decrypt(
61 &self,
62 ctx: &mut KeyStoreContext<KeySlotIds>,
63 key: SymmetricKeySlotId,
64 ) -> Result<PasswordHistoryView, CryptoError> {
65 Ok(PasswordHistoryView {
66 password: self.password.decrypt(ctx, key).ok().unwrap_or_default(),
67 last_used_date: self.last_used_date,
68 })
69 }
70}
71
72impl TryFrom<CipherPasswordHistoryModel> for PasswordHistory {
73 type Error = VaultParseError;
74
75 fn try_from(model: CipherPasswordHistoryModel) -> Result<Self, Self::Error> {
76 Ok(Self {
77 password: model.password.parse()?,
78 last_used_date: model.last_used_date.parse()?,
79 })
80 }
81}
82
83impl From<PasswordHistory> for CipherPasswordHistoryModel {
84 fn from(history: PasswordHistory) -> Self {
85 Self {
86 password: history.password.to_string(),
87 last_used_date: history.last_used_date.to_rfc3339(),
88 }
89 }
90}
91
92impl PasswordHistoryView {
93 pub(crate) fn new_password(old_password: &str) -> Self {
94 Self {
95 password: old_password.to_string(),
96 last_used_date: Utc::now(),
97 }
98 }
99
100 pub(crate) fn new_field(field_name: &str, old_value: &str) -> Self {
101 Self {
102 password: format!("{field_name}: {old_value}"),
103 last_used_date: Utc::now(),
104 }
105 }
106}