Skip to main content

bitwarden_vault/
password_history.rs

1use 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
12/// Maximum number of password history entries to retain
13pub(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}