bitwarden_core/auth/login/
password.rs

1#[cfg(feature = "internal")]
2use bitwarden_crypto::Kdf;
3#[cfg(feature = "internal")]
4use log::info;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::auth::{
9    api::response::IdentityTokenResponse,
10    login::response::{captcha_response::CaptchaResponse, two_factor::TwoFactorProviders},
11};
12#[cfg(feature = "internal")]
13use crate::{
14    auth::{api::request::PasswordTokenRequest, login::LoginError, login::TwoFactorRequest},
15    client::LoginMethod,
16    Client,
17};
18
19#[cfg(feature = "internal")]
20pub(crate) async fn login_password(
21    client: &Client,
22    input: &PasswordLoginRequest,
23) -> Result<PasswordLoginResponse, LoginError> {
24    use bitwarden_crypto::{EncString, HashPurpose, MasterKey};
25
26    use crate::{client::UserLoginMethod, require};
27
28    info!("password logging in");
29
30    let master_key = MasterKey::derive(&input.password, &input.email, &input.kdf)?;
31    let password_hash = master_key
32        .derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization)?;
33
34    let response = request_identity_tokens(client, input, &password_hash).await?;
35
36    if let IdentityTokenResponse::Authenticated(r) = &response {
37        client.internal.set_tokens(
38            r.access_token.clone(),
39            r.refresh_token.clone(),
40            r.expires_in,
41        );
42        client
43            .internal
44            .set_login_method(LoginMethod::User(UserLoginMethod::Username {
45                client_id: "web".to_owned(),
46                email: input.email.to_owned(),
47                kdf: input.kdf.to_owned(),
48            }));
49
50        let user_key: EncString = require!(r.key.as_deref()).parse()?;
51        let private_key: EncString = require!(r.private_key.as_deref()).parse()?;
52
53        client.internal.initialize_user_crypto_master_key(
54            master_key,
55            user_key,
56            private_key,
57            None,
58        )?;
59    }
60
61    Ok(PasswordLoginResponse::process_response(response))
62}
63
64#[cfg(feature = "internal")]
65async fn request_identity_tokens(
66    client: &Client,
67    input: &PasswordLoginRequest,
68    password_hash: &str,
69) -> Result<IdentityTokenResponse, LoginError> {
70    use crate::DeviceType;
71
72    let config = client.internal.get_api_configurations().await;
73    PasswordTokenRequest::new(
74        &input.email,
75        password_hash,
76        DeviceType::ChromeBrowser,
77        "b86dd6ab-4265-4ddf-a7f1-eb28d5677f33",
78        &input.two_factor,
79    )
80    .send(&config)
81    .await
82}
83
84/// Login to Bitwarden with Username and Password
85#[cfg(feature = "internal")]
86#[derive(Serialize, Deserialize, Debug, JsonSchema)]
87#[serde(rename_all = "camelCase", deny_unknown_fields)]
88pub struct PasswordLoginRequest {
89    /// Bitwarden account email address
90    pub email: String,
91    /// Bitwarden account master password
92    pub password: String,
93    /// Two-factor authentication
94    pub two_factor: Option<TwoFactorRequest>,
95    /// Kdf from prelogin
96    pub kdf: Kdf,
97}
98
99#[allow(missing_docs)]
100#[derive(Serialize, Deserialize, Debug, JsonSchema)]
101#[serde(rename_all = "camelCase", deny_unknown_fields)]
102pub struct PasswordLoginResponse {
103    pub authenticated: bool,
104    /// TODO: What does this do?
105    pub reset_master_password: bool,
106    /// Whether or not the user is required to update their master password
107    pub force_password_reset: bool,
108    /// The available two factor authentication options. Present only when authentication fails due
109    /// to requiring a second authentication factor.
110    pub two_factor: Option<TwoFactorProviders>,
111    /// The information required to present the user with a captcha challenge. Only present when
112    /// authentication fails due to requiring validation of a captcha challenge.
113    pub captcha: Option<CaptchaResponse>,
114}
115
116impl PasswordLoginResponse {
117    pub(crate) fn process_response(response: IdentityTokenResponse) -> PasswordLoginResponse {
118        match response {
119            IdentityTokenResponse::Authenticated(success) => PasswordLoginResponse {
120                authenticated: true,
121                reset_master_password: success.reset_master_password,
122                force_password_reset: success.force_password_reset,
123                two_factor: None,
124                captcha: None,
125            },
126            IdentityTokenResponse::Payload(_) => PasswordLoginResponse {
127                authenticated: true,
128                reset_master_password: false,
129                force_password_reset: false,
130                two_factor: None,
131                captcha: None,
132            },
133            IdentityTokenResponse::TwoFactorRequired(two_factor) => PasswordLoginResponse {
134                authenticated: false,
135                reset_master_password: false,
136                force_password_reset: false,
137                two_factor: Some(two_factor.two_factor_providers.into()),
138                captcha: two_factor.captcha_token.map(Into::into),
139            },
140            IdentityTokenResponse::CaptchaRequired(captcha) => PasswordLoginResponse {
141                authenticated: false,
142                reset_master_password: false,
143                force_password_reset: false,
144                two_factor: None,
145                captcha: Some(captcha.site_key.into()),
146            },
147            IdentityTokenResponse::Refreshed(_) => {
148                unreachable!("Got a `refresh_token` answer to a login request")
149            }
150        }
151    }
152}