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