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