bitwarden_vault/
password_history.rs

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