bitwarden_core/secrets_manager/
state.rs

1//! Secrets Manager state management.
2//!
3//! Secrets Manager supports persisting state to an encrypted file. This state file primarily
4//! contains the auth token which circumvents rate limiting on frequent logins.
5
6use std::{fmt::Debug, path::Path};
7
8use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable};
9use bitwarden_encoding::B64;
10use serde::{Deserialize, Serialize};
11
12use crate::auth::AccessToken;
13
14/// Current version of the state file. This should be incremented whenever backwards incompatible
15/// changes are done.
16const STATE_VERSION: u32 = 1;
17
18/// The content of the state file.
19#[cfg(feature = "secrets")]
20#[derive(Serialize, Deserialize, Debug)]
21pub(crate) struct ClientState {
22    pub(crate) version: u32,
23    pub(crate) token: String,
24    pub(crate) encryption_key: B64,
25}
26
27#[allow(missing_docs)]
28#[derive(Debug, thiserror::Error)]
29pub enum StateFileError {
30    #[error(transparent)]
31    Crypto(#[from] bitwarden_crypto::CryptoError),
32    #[error(transparent)]
33    Serde(#[from] serde_json::Error),
34    #[error(transparent)]
35    Io(#[from] std::io::Error),
36
37    #[error("The state file version is invalid")]
38    InvalidStateFileVersion,
39}
40
41impl ClientState {
42    pub fn new(token: String, encryption_key: B64) -> Self {
43        Self {
44            version: STATE_VERSION,
45            token,
46            encryption_key,
47        }
48    }
49}
50
51/// Reads, parses and decrypts a state file using the provided access token.
52pub(crate) fn get(
53    state_file: &Path,
54    access_token: &AccessToken,
55) -> Result<ClientState, StateFileError> {
56    let file_content = std::fs::read_to_string(state_file)?;
57
58    let encrypted_state: EncString = file_content.parse()?;
59    let decrypted_state: String = encrypted_state.decrypt_with_key(&access_token.encryption_key)?;
60    let client_state: ClientState = serde_json::from_str(&decrypted_state)?;
61
62    if client_state.version != STATE_VERSION {
63        return Err(StateFileError::InvalidStateFileVersion);
64    }
65
66    Ok(client_state)
67}
68
69/// Serializes and encrypts the state using the provided access token.
70pub(crate) fn set(
71    state_file: &Path,
72    access_token: &AccessToken,
73    state: ClientState,
74) -> Result<(), StateFileError> {
75    let serialized_state: String = serde_json::to_string(&state)?;
76    let encrypted_state: EncString =
77        serialized_state.encrypt_with_key(&access_token.encryption_key)?;
78    let state_string: String = encrypted_state.to_string();
79
80    Ok(std::fs::write(state_file, state_string)?)
81}