bitwarden_core/auth/login/
password.rs#[cfg(feature = "internal")]
use bitwarden_crypto::Kdf;
#[cfg(feature = "internal")]
use log::info;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::auth::{
api::response::IdentityTokenResponse,
login::{
response::{captcha_response::CaptchaResponse, two_factor::TwoFactorProviders},
LoginError,
},
};
#[cfg(feature = "internal")]
use crate::{
auth::{api::request::PasswordTokenRequest, login::TwoFactorRequest},
client::LoginMethod,
Client,
};
#[cfg(feature = "internal")]
pub(crate) async fn login_password(
client: &Client,
input: &PasswordLoginRequest,
) -> Result<PasswordLoginResponse, LoginError> {
use bitwarden_crypto::{EncString, HashPurpose, MasterKey};
use crate::{client::UserLoginMethod, require};
info!("password logging in");
let master_key = MasterKey::derive(&input.password, &input.email, &input.kdf)?;
let password_hash = master_key
.derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization)?;
let response = request_identity_tokens(client, input, &password_hash).await?;
if let IdentityTokenResponse::Authenticated(r) = &response {
client.internal.set_tokens(
r.access_token.clone(),
r.refresh_token.clone(),
r.expires_in,
);
client
.internal
.set_login_method(LoginMethod::User(UserLoginMethod::Username {
client_id: "web".to_owned(),
email: input.email.to_owned(),
kdf: input.kdf.to_owned(),
}));
let user_key: EncString = require!(r.key.as_deref()).parse()?;
let private_key: EncString = require!(r.private_key.as_deref()).parse()?;
client
.internal
.initialize_user_crypto_master_key(master_key, user_key, private_key)?;
}
Ok(PasswordLoginResponse::process_response(response))
}
#[cfg(feature = "internal")]
async fn request_identity_tokens(
client: &Client,
input: &PasswordLoginRequest,
password_hash: &str,
) -> Result<IdentityTokenResponse, LoginError> {
use crate::DeviceType;
let config = client.internal.get_api_configurations().await;
PasswordTokenRequest::new(
&input.email,
password_hash,
DeviceType::ChromeBrowser,
"b86dd6ab-4265-4ddf-a7f1-eb28d5677f33",
&input.two_factor,
)
.send(&config)
.await
}
#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PasswordLoginRequest {
pub email: String,
pub password: String,
pub two_factor: Option<TwoFactorRequest>,
pub kdf: Kdf,
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PasswordLoginResponse {
pub authenticated: bool,
pub reset_master_password: bool,
pub force_password_reset: bool,
pub two_factor: Option<TwoFactorProviders>,
pub captcha: Option<CaptchaResponse>,
}
impl PasswordLoginResponse {
pub(crate) fn process_response(response: IdentityTokenResponse) -> PasswordLoginResponse {
match response {
IdentityTokenResponse::Authenticated(success) => PasswordLoginResponse {
authenticated: true,
reset_master_password: success.reset_master_password,
force_password_reset: success.force_password_reset,
two_factor: None,
captcha: None,
},
IdentityTokenResponse::Payload(_) => PasswordLoginResponse {
authenticated: true,
reset_master_password: false,
force_password_reset: false,
two_factor: None,
captcha: None,
},
IdentityTokenResponse::TwoFactorRequired(two_factor) => PasswordLoginResponse {
authenticated: false,
reset_master_password: false,
force_password_reset: false,
two_factor: Some(two_factor.two_factor_providers.into()),
captcha: two_factor.captcha_token.map(Into::into),
},
IdentityTokenResponse::CaptchaRequired(captcha) => PasswordLoginResponse {
authenticated: false,
reset_master_password: false,
force_password_reset: false,
two_factor: None,
captcha: Some(captcha.site_key.into()),
},
IdentityTokenResponse::Refreshed(_) => {
unreachable!("Got a `refresh_token` answer to a login request")
}
}
}
}