diff options
Diffstat (limited to 'src/api.rs')
-rw-r--r-- | src/api.rs | 76 |
1 files changed, 64 insertions, 12 deletions
@@ -148,9 +148,10 @@ struct PreloginRes { struct ConnectPasswordReq { grant_type: String, username: String, - password: String, + password: Option<String>, scope: String, client_id: String, + client_secret: Option<String>, #[serde(rename = "deviceType")] device_type: u32, #[serde(rename = "deviceIdentifier")] @@ -178,7 +179,7 @@ struct ConnectPasswordRes { #[derive(serde::Deserialize, Debug)] struct ConnectErrorRes { error: String, - error_description: String, + error_description: Option<String>, #[serde(rename = "ErrorModel")] error_model: Option<ConnectErrorResErrorModel>, #[serde(rename = "TwoFactorProviders")] @@ -575,23 +576,62 @@ impl Client { Ok(prelogin_res.kdf_iterations) } + pub async fn register( + &self, + email: &str, + device_id: &str, + apikey: &crate::locked::ApiKey, + ) -> Result<()> { + let connect_req = ConnectPasswordReq { + grant_type: "client_credentials".to_string(), + username: email.to_string(), + password: None, + scope: "api".to_string(), + // XXX unwraps here are not necessarily safe + client_id: String::from_utf8(apikey.client_id().to_vec()) + .unwrap(), + client_secret: Some( + String::from_utf8(apikey.client_secret().to_vec()).unwrap(), + ), + device_type: 8, + device_identifier: device_id.to_string(), + device_name: "rbw".to_string(), + device_push_token: "".to_string(), + two_factor_token: None, + two_factor_provider: None, + }; + let client = reqwest::Client::new(); + let res = client + .post(&self.identity_url("/connect/token")) + .form(&connect_req) + .send() + .await + .map_err(|source| Error::Reqwest { source })?; + if let reqwest::StatusCode::OK = res.status() { + Ok(()) + } else { + let code = res.status().as_u16(); + Err(classify_login_error(&res.json_with_path().await?, code)) + } + } + pub async fn login( &self, email: &str, - master_password_hash: &crate::locked::PasswordHash, + device_id: &str, + password_hash: &crate::locked::PasswordHash, two_factor_token: Option<&str>, two_factor_provider: Option<TwoFactorProviderType>, ) -> Result<(String, String, String)> { let connect_req = ConnectPasswordReq { grant_type: "password".to_string(), username: email.to_string(), - password: base64::encode(master_password_hash.hash()), + password: Some(base64::encode(password_hash.hash())), scope: "api offline_access".to_string(), client_id: "desktop".to_string(), + client_secret: None, device_type: 8, - device_identifier: uuid::Uuid::new_v4() - .to_hyphenated() - .to_string(), + device_identifier: device_id.to_string(), device_name: "rbw".to_string(), device_push_token: "".to_string(), two_factor_token: two_factor_token @@ -602,6 +642,10 @@ impl Client { let res = client .post(&self.identity_url("/connect/token")) .form(&connect_req) + .header( + "auth-email", + base64::encode_config(email, base64::URL_SAFE_NO_PAD), + ) .send() .await .map_err(|source| Error::Reqwest { source })?; @@ -708,7 +752,7 @@ impl Client { password: password.clone(), totp: totp.clone(), uris, - }) + }); } crate::db::EntryData::Card { cardholder_name, @@ -1046,15 +1090,17 @@ impl Client { } fn classify_login_error(error_res: &ConnectErrorRes, code: u16) -> Error { + let error_desc = error_res.error_description.clone(); + let error_desc = error_desc.as_deref(); match error_res.error.as_str() { - "invalid_grant" => match error_res.error_description.as_str() { - "invalid_username_or_password" => { + "invalid_grant" => match error_desc { + Some("invalid_username_or_password") => { if let Some(error_model) = error_res.error_model.as_ref() { let message = error_model.message.as_str().to_string(); return Error::IncorrectPassword { message }; } } - "Two factor required." => { + Some("Two factor required.") => { if let Some(providers) = error_res.two_factor_providers.as_ref() { @@ -1063,12 +1109,18 @@ fn classify_login_error(error_res: &ConnectErrorRes, code: u16) -> Error { }; } } + Some("Captcha required.") => { + return Error::RegistrationRequired; + } _ => {} }, + "invalid_client" => { + return Error::IncorrectApiKey; + } "" => { // bitwarden_rs returns an empty error and error_description for // this case, for some reason - if error_res.error_description.is_empty() { + if error_desc.is_none() || error_desc == Some("") { if let Some(error_model) = error_res.error_model.as_ref() { let message = error_model.message.as_str().to_string(); match message.as_str() { |