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