Skip to main content

bitwarden_auth/login/models/
login_success_response.rs

1use std::fmt::Debug;
2
3use bitwarden_core::{
4    MissingFieldError,
5    key_management::{
6        MasterPasswordError,
7        account_cryptographic_state::{
8            AccountKeysResponseParseError, WrappedAccountCryptographicState,
9        },
10    },
11    require,
12};
13use bitwarden_policies::MasterPasswordPolicyData;
14use thiserror::Error;
15
16use crate::login::{api::response::LoginSuccessApiResponse, models::UserDecryptionOptionsResponse};
17
18/// SDK response model for a successful login.
19/// This is the model that will be exposed to consuming applications.
20#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
21#[serde(rename_all = "camelCase")]
22#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
23#[bitwarden_ffi::wasm_record]
24pub struct LoginSuccessResponse {
25    /// The access token string.
26    pub access_token: String,
27
28    /// The duration in seconds until the token expires.
29    pub expires_in: u64,
30
31    /// The timestamp in milliseconds when the token expires.
32    /// We calculate this for more convenient token expiration handling.
33    pub expires_at: i64,
34
35    /// The scope of the access token.
36    /// OAuth 2.0 RFC reference: <https://datatracker.ietf.org/doc/html/rfc6749#section-3.3>
37    pub scope: String,
38
39    /// The type of the token.
40    /// This will be "Bearer" for send access tokens.
41    /// OAuth 2.0 RFC reference: <https://datatracker.ietf.org/doc/html/rfc6749#section-7.1>
42    pub token_type: String,
43
44    /// The optional refresh token string.
45    /// This token can be used to obtain new access tokens when the current one expires.
46    pub refresh_token: Option<String>,
47
48    /// The user key wrapped user private key.
49    /// Note: previously known as "private_key".
50    pub user_key_wrapped_user_private_key: Option<String>,
51
52    /// Two-factor authentication token for future requests.
53    pub two_factor_token: Option<String>,
54
55    /// Indicates whether an admin has reset the user's master password,
56    /// requiring them to set a new password upon next login.
57    pub force_password_reset: Option<bool>,
58
59    /// Indicates whether the user uses Key Connector and if the client should have a locally
60    /// configured Key Connector URL in their environment.
61    /// Note: This is currently only applicable for client_credential grant type logins and
62    /// is only expected to be relevant for the CLI
63    pub api_use_key_connector: Option<bool>,
64
65    /// The user's decryption options for unlocking their vault.
66    pub user_decryption_options: UserDecryptionOptionsResponse,
67
68    /// If the user is subject to an organization master password policy,
69    /// this field contains the requirements of that policy.
70    pub master_password_policy: Option<MasterPasswordPolicyData>,
71
72    /// The user's account cryptographic keys (wrapped with the user key).
73    pub wrapped_account_crypto_state: Option<WrappedAccountCryptographicState>,
74}
75
76impl TryFrom<LoginSuccessApiResponse> for LoginSuccessResponse {
77    type Error = LoginResponseError;
78    fn try_from(response: LoginSuccessApiResponse) -> Result<Self, Self::Error> {
79        // We want to convert the expires_in from seconds to a millisecond timestamp to have a
80        // concrete time the token will expire. This makes it easier to build logic around a
81        // concrete time rather than a duration. We keep expires_in as well for backward
82        // compatibility and convenience.
83        let expires_at =
84            chrono::Utc::now().timestamp_millis() + (response.expires_in * 1000) as i64;
85
86        Ok(LoginSuccessResponse {
87            access_token: response.access_token,
88            expires_in: response.expires_in,
89            expires_at,
90            scope: response.scope,
91            token_type: response.token_type,
92            refresh_token: response.refresh_token,
93            user_key_wrapped_user_private_key: response.private_key,
94            two_factor_token: response.two_factor_token,
95            force_password_reset: response.force_password_reset,
96            api_use_key_connector: response.api_use_key_connector,
97            // User decryption options are required on successful login responses
98            user_decryption_options: require!(response.user_decryption_options).try_into()?,
99            master_password_policy: response.master_password_policy.map(|policy| policy.into()),
100            wrapped_account_crypto_state: response
101                .account_keys
102                .as_ref()
103                .map(TryInto::try_into)
104                .transpose()?,
105        })
106    }
107}
108
109/// Error that can occur during login and response parsing.
110#[derive(Debug, Error)]
111pub enum LoginResponseError {
112    /// Error from master password related operations.
113    #[error(transparent)]
114    MasterPassword(#[from] MasterPasswordError),
115
116    /// Error parsing account cryptographic state from API response.
117    #[error("Failed to parse account keys: {0}")]
118    AccountKeys(#[from] AccountKeysResponseParseError),
119}
120
121impl From<MissingFieldError> for LoginResponseError {
122    fn from(value: MissingFieldError) -> Self {
123        LoginResponseError::MasterPassword(value.into())
124    }
125}