aboutsummaryrefslogtreecommitdiffstats
path: root/src/api.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api.rs')
-rw-r--r--src/api.rs76
1 files changed, 64 insertions, 12 deletions
diff --git a/src/api.rs b/src/api.rs
index ef0f73d..14c11fd 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -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() {