bitwarden_vault/
password_history.rs1use 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
14pub(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}