bitwarden_core/auth/login/
password.rs1#[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#[cfg(feature = "internal")]
86#[derive(Serialize, Deserialize, Debug, JsonSchema)]
87#[serde(rename_all = "camelCase", deny_unknown_fields)]
88pub struct PasswordLoginRequest {
89 pub email: String,
91 pub password: String,
93 pub two_factor: Option<TwoFactorRequest>,
95 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 pub reset_master_password: bool,
106 pub force_password_reset: bool,
108 pub two_factor: Option<TwoFactorProviders>,
111 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}