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::{
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)]
87pub struct PasswordLoginRequest {
89 pub email: String,
91 pub password: String,
93 pub two_factor: Option<TwoFactorRequest>,
95 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 pub reset_master_password: bool,
105 pub force_password_reset: bool,
107 pub two_factor: Option<TwoFactorProviders>,
110 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}