aboutsummaryrefslogtreecommitdiffstats
path: root/src/api.rs
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-04-05 02:17:25 -0400
committerJesse Luehrs <doy@tozt.net>2020-04-05 02:17:25 -0400
commitcd894c27e0b0d5746b95b9c2933da3ba6e9a3f5b (patch)
tree94de4da0e8ac1cea7a855f8fb1d16d6f320e7e72 /src/api.rs
parent070315ce5f80e82fcb5f39c15cd7bbf1682fdf8b (diff)
downloadrbw-cd894c27e0b0d5746b95b9c2933da3ba6e9a3f5b.tar.gz
rbw-cd894c27e0b0d5746b95b9c2933da3ba6e9a3f5b.zip
basic implementation of the cryptographic stuff
Diffstat (limited to 'src/api.rs')
-rw-r--r--src/api.rs161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/api.rs b/src/api.rs
new file mode 100644
index 0000000..ae2e550
--- /dev/null
+++ b/src/api.rs
@@ -0,0 +1,161 @@
+use crate::prelude::*;
+
+#[derive(serde::Serialize, Debug)]
+struct PreloginReq {
+ email: String,
+}
+
+#[derive(serde::Deserialize, Debug)]
+struct PreloginRes {
+ #[serde(rename = "Kdf")]
+ kdf: u32,
+ #[serde(rename = "KdfIterations")]
+ kdf_iterations: u32,
+}
+
+#[derive(serde::Serialize, Debug)]
+struct ConnectReq {
+ grant_type: String,
+ username: String,
+ password: String,
+ scope: String,
+ client_id: String,
+ #[serde(rename = "deviceType")]
+ device_type: u32,
+ #[serde(rename = "deviceIdentifier")]
+ device_identifier: String,
+ #[serde(rename = "deviceName")]
+ device_name: String,
+ #[serde(rename = "devicePushToken")]
+ device_push_token: String,
+}
+
+#[derive(serde::Deserialize, Debug)]
+struct ConnectRes {
+ access_token: String,
+ expires_in: u32,
+ token_type: String,
+ refresh_token: String,
+ #[serde(rename = "Key")]
+ key: String,
+}
+
+#[derive(serde::Deserialize, Debug)]
+struct SyncRes {
+ #[serde(rename = "Ciphers")]
+ ciphers: Vec<Cipher>,
+ #[serde(rename = "Profile")]
+ profile: Profile,
+}
+
+#[derive(serde::Deserialize, Debug)]
+struct Profile {
+ #[serde(rename = "Key")]
+ key: String,
+}
+
+#[derive(serde::Deserialize, Debug)]
+pub struct Cipher {
+ #[serde(rename = "Name")]
+ pub name: String,
+ #[serde(rename = "Login")]
+ pub login: Login,
+}
+
+#[derive(serde::Deserialize, Debug)]
+pub struct Login {
+ #[serde(rename = "Username")]
+ pub username: String,
+ #[serde(rename = "Password")]
+ pub password: String,
+}
+
+#[derive(Debug)]
+pub struct Client {
+ api_url_base: String,
+ identity_url_base: String,
+}
+
+impl Client {
+ #[allow(dead_code)]
+ pub fn new() -> Self {
+ Self {
+ api_url_base: "https://api.bitwarden.com".to_string(),
+ identity_url_base: "https://identity.bitwarden.com".to_string(),
+ }
+ }
+
+ pub fn new_self_hosted(base_url: &str) -> Self {
+ Self {
+ api_url_base: format!("{}/api", base_url),
+ identity_url_base: format!("{}/identity", base_url),
+ }
+ }
+
+ pub fn prelogin(&self, email: &str) -> Result<u32> {
+ let prelogin = PreloginReq {
+ email: email.to_string(),
+ };
+ let client = reqwest::blocking::Client::new();
+ let res = client
+ .post(&self.api_url("/accounts/prelogin"))
+ .json(&prelogin)
+ .send()
+ .context(crate::error::Reqwest)?;
+ let prelogin_res: PreloginRes =
+ res.json().context(crate::error::Reqwest)?;
+ Ok(prelogin_res.kdf_iterations)
+ }
+
+ pub fn login(
+ &self,
+ email: &str,
+ master_password_hash: &[u8],
+ ) -> Result<(String, String, String)> {
+ let connect_req = ConnectReq {
+ grant_type: "password".to_string(),
+ username: email.to_string(),
+ password: base64::encode(&master_password_hash),
+ scope: "api offline_access".to_string(),
+ client_id: "desktop".to_string(),
+ device_type: 8,
+ device_identifier: uuid::Uuid::new_v4()
+ .to_hyphenated()
+ .to_string(),
+ device_name: "test cli".to_string(),
+ device_push_token: "".to_string(),
+ };
+ let client = reqwest::blocking::Client::new();
+ let res = client
+ .post(&self.identity_url("/connect/token"))
+ .form(&connect_req)
+ .send()
+ .context(crate::error::Reqwest)?;
+ let connect_res: ConnectRes =
+ res.json().context(crate::error::Reqwest)?;
+ Ok((
+ connect_res.access_token,
+ connect_res.refresh_token,
+ connect_res.key,
+ ))
+ }
+
+ pub fn sync(&self, access_token: &str) -> Result<Vec<Cipher>> {
+ let client = reqwest::blocking::Client::new();
+ let res = client
+ .get(&self.api_url("/sync"))
+ .header("Authorization", format!("Bearer {}", access_token))
+ .send()
+ .context(crate::error::Reqwest)?;
+ let sync_res: SyncRes = res.json().context(crate::error::Reqwest)?;
+ Ok(sync_res.ciphers)
+ }
+
+ fn api_url(&self, path: &str) -> String {
+ format!("{}{}", self.api_url_base, path)
+ }
+
+ fn identity_url(&self, path: &str) -> String {
+ format!("{}{}", self.identity_url_base, path)
+ }
+}