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::{
11        response::{captcha_response::CaptchaResponse, two_factor::TwoFactorProviders},
12        LoginError,
13    },
14};
15#[cfg(feature = "internal")]
16use crate::{
17    auth::{api::request::PasswordTokenRequest, login::TwoFactorRequest},
18    client::LoginMethod,
19    Client,
20};
21
22#[cfg(feature = "internal")]
23pub(crate) async fn login_password(
24    client: &Client,
25    input: &PasswordLoginRequest,
26) -> Result<PasswordLoginResponse, LoginError> {
27    use bitwarden_crypto::{EncString, HashPurpose, MasterKey};
28
29    use crate::{client::UserLoginMethod, require};
30
31    info!("password logging in");
32
33    let master_key = MasterKey::derive(&input.password, &input.email, &input.kdf)?;
34    let password_hash = master_key
35        .derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization)?;
36
37    let response = request_identity_tokens(client, input, &password_hash).await?;
38
39    if let IdentityTokenResponse::Authenticated(r) = &response {
40        client.internal.set_tokens(
41            r.access_token.clone(),
42            r.refresh_token.clone(),
43            r.expires_in,
44        );
45        client
46            .internal
47            .set_login_method(LoginMethod::User(UserLoginMethod::Username {
48                client_id: "web".to_owned(),
49                email: input.email.to_owned(),
50                kdf: input.kdf.to_owned(),
51            }));
52
53        let user_key: EncString = require!(r.key.as_deref()).parse()?;
54        let private_key: EncString = require!(r.private_key.as_deref()).parse()?;
55
56        client
57            .internal
58            .initialize_user_crypto_master_key(master_key, user_key, private_key)?;
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#[cfg(feature = "internal")]
85#[derive(Serialize, Deserialize, Debug, JsonSchema)]
86#[serde(rename_all = "camelCase", deny_unknown_fields)]
87/// Login to Bitwarden with Username and Password
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#[derive(Serialize, Deserialize, Debug, JsonSchema)]
100#[serde(rename_all = "camelCase", deny_unknown_fields)]
101pub struct PasswordLoginResponse {
102    pub authenticated: bool,
103    /// TODO: What does this do?
104    pub reset_master_password: bool,
105    /// Whether or not the user is required to update their master password
106    pub force_password_reset: bool,
107    /// The available two factor authentication options. Present only when authentication fails due
108    /// to requiring a second authentication factor.
109    pub two_factor: Option<TwoFactorProviders>,
110    /// The information required to present the user with a captcha challenge. Only present when
111    /// authentication fails due to requiring validation of a captcha challenge.
112    pub captcha: Option<CaptchaResponse>,
113}
114
115impl PasswordLoginResponse {
116    pub(crate) fn process_response(response: IdentityTokenResponse) -> PasswordLoginResponse {
117        match response {
118            IdentityTokenResponse::Authenticated(success) => PasswordLoginResponse {
119                authenticated: true,
120                reset_master_password: success.reset_master_password,
121                force_password_reset: success.force_password_reset,
122                two_factor: None,
123                captcha: None,
124            },
125            IdentityTokenResponse::Payload(_) => PasswordLoginResponse {
126                authenticated: true,
127                reset_master_password: false,
128                force_password_reset: false,
129                two_factor: None,
130                captcha: None,
131            },
132            IdentityTokenResponse::TwoFactorRequired(two_factor) => PasswordLoginResponse {
133                authenticated: false,
134                reset_master_password: false,
135                force_password_reset: false,
136                two_factor: Some(two_factor.two_factor_providers.into()),
137                captcha: two_factor.captcha_token.map(Into::into),
138            },
139            IdentityTokenResponse::CaptchaRequired(captcha) => PasswordLoginResponse {
140                authenticated: false,
141                reset_master_password: false,
142                force_password_reset: false,
143                two_factor: None,
144                captcha: Some(captcha.site_key.into()),
145            },
146            IdentityTokenResponse::Refreshed(_) => {
147                unreachable!("Got a `refresh_token` answer to a login request")
148            }
149        }
150    }
151}