Factor out restmail client code

This commit is contained in:
Edouard Oger 2020-07-02 15:21:51 -04:00
Родитель b48746ea35
Коммит 231abf8c32
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: A2F740742307674A
9 изменённых файлов: 137 добавлений и 80 удалений

12
Cargo.lock сгенерированный
Просмотреть файл

@ -2183,6 +2183,17 @@ dependencies = [
"winreg",
]
[[package]]
name = "restmail-client"
version = "0.1.0"
dependencies = [
"log 0.4.8",
"serde_json",
"thiserror",
"url",
"viaduct",
]
[[package]]
name = "rusqlite"
version = "0.23.1"
@ -2471,6 +2482,7 @@ dependencies = [
"log 0.4.8",
"logins",
"rand 0.7.3",
"restmail-client",
"serde",
"serde_derive",
"serde_json",

Просмотреть файл

@ -14,6 +14,7 @@ members = [
"components/support/ffi",
"components/support/guid",
"components/support/interrupt",
"components/support/restmail-client",
"components/support/rc_crypto",
"components/support/rc_crypto/nss",
"components/support/rc_crypto/nss/nss_build_common",

Просмотреть файл

@ -0,0 +1,13 @@
[package]
name = "restmail-client"
version = "0.1.0"
authors = ["Edouard Oger <eoger@fastmail.com>"]
edition = "2018"
license = "MPL-2.0"
[dependencies]
thiserror = "1.0"
viaduct = { path = "../../viaduct" }
log = "0.4"
serde_json = "1"
url = "2.1"

Просмотреть файл

@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use thiserror::Error;
#[derive(Error, Debug)]
pub enum RestmailClientError {
#[error("Not a restmail account (doesn't end with @restmail.net)")]
NotARestmailEmail,
#[error("Max tries reached and the email couldn't be found.")]
HitRetryMax,
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Error parsing URL: {0}")]
Disconnect(#[from] url::ParseError),
#[error("Network error: {0}")]
RequestError(#[from] viaduct::Error),
}

Просмотреть файл

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use error::RestmailClientError;
use serde_json::Value as EmailJson;
use url::Url;
use viaduct::Request;
mod error;
type Result<T> = std::result::Result<T, RestmailClientError>;
/// For a given restmail email, find the first email that satisfies the given predicate.
/// If no email is found, this function sleeps for a few seconds then tries again, up
/// to `max_tries` times.
pub fn find_email<F>(email: &str, predicate: F, max_tries: u8) -> Result<EmailJson>
where
F: Fn(&EmailJson) -> bool,
{
let mail_url = url_for_email(email)?;
log::info!("Checking {} up to {} times.", email, max_tries);
for i in 0..max_tries {
let resp: Vec<serde_json::Value> = Request::get(mail_url.clone()).send()?.json()?;
let mut matching_emails: Vec<serde_json::Value> =
resp.into_iter().filter(|email| predicate(email)).collect();
if matching_emails.is_empty() {
log::info!(
"Failed to find matching email. Waiting {} seconds and retrying.",
i + 1
);
std::thread::sleep(std::time::Duration::from_secs((i + 1).into()));
continue;
}
if matching_emails.len() > 1 {
log::info!(
"Found {} emails that applies (taking latest)",
matching_emails.len()
);
matching_emails.sort_by(|a, b| {
let a_time = a["receivedAt"].as_u64();
let b_time = b["receivedAt"].as_u64();
match (a_time, b_time) {
(Some(a_time), Some(b_time)) => b_time.cmp(&a_time),
_ => {
log::warn!(
"Could not de-serialize receivedAt for at least one of the emails."
);
std::cmp::Ordering::Equal
}
}
})
}
return Ok(matching_emails[0].clone());
}
log::info!("Error: Failed to find email after {} tries!", max_tries);
Err(RestmailClientError::HitRetryMax)
}
pub fn clear_mailbox(email: &str) -> Result<()> {
let mail_url = url_for_email(email)?;
log::info!("Clearing restmail for {}.", email);
Request::delete(mail_url).send()?;
Ok(())
}
fn username_from_email(email: &str) -> Result<String> {
let user = email.replace("@restmail.net", "");
if user.len() == email.len() {
return Err(RestmailClientError::NotARestmailEmail);
}
Ok(user)
}
fn url_for_email(email: &str) -> Result<Url> {
let restmail_user = username_from_email(email)?;
let path = format!("/mail/{}", restmail_user);
Ok(Url::parse("https://restmail.net")?.join(&path)?)
}

Просмотреть файл

@ -11,6 +11,7 @@ viaduct = { path = "../../components/viaduct"}
logins = { path = "../../components/logins" }
sync15 = { path = "../../components/sync15" }
sync15-traits = { path = "../../components/support/sync15-traits" }
restmail-client = { path = "../../components/support/restmail-client" }
tabs = { path = "../../components/tabs" }
fxa-client = { path = "../../components/fxa-client", features = ["integration_test"] }
sync-guid = { path = "../../components/support/guid", features = ["rusqlite_support", "random"]}

Просмотреть файл

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
use crate::{restmail, Opts};
use crate::Opts;
use anyhow::Result;
use fxa_client::{self, auth, Config as FxaConfig, FirefoxAccount};
use logins::PasswordEngine;
@ -61,7 +61,7 @@ impl TestAccount {
) -> Result<Arc<TestAccount>> {
log::info!("Creating temporary fx account");
restmail::clear_mailbox(&email);
restmail_client::clear_mailbox(&email).unwrap();
let create_endpoint = cfg.auth_url_path("v1/account/create?keys=true").unwrap();
let body = json!({
@ -128,9 +128,14 @@ impl TestAccount {
}
fn verify_account(email_in: &str, config: &FxaConfig, uid: &str) -> Result<()> {
let verification_email = restmail::find_email(email_in, |email| {
email["headers"]["x-uid"] == uid && email["headers"]["x-template-name"] == "verify"
});
let verification_email = restmail_client::find_email(
email_in,
|email| {
email["headers"]["x-uid"] == uid && email["headers"]["x-template-name"] == "verify"
},
10,
)
.unwrap();
let code = verification_email["headers"]["x-verify-code"]
.as_str()
.unwrap();

Просмотреть файл

@ -9,7 +9,6 @@ use structopt::StructOpt;
mod auth;
mod logins;
mod restmail;
mod sync15;
mod tabs;
mod testing;

Просмотреть файл

@ -1,74 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
use serde_json::Value as EmailJson;
use url::Url;
use viaduct::Request;
fn restmail_username(email: &str) -> String {
let user = email.replace("@restmail.net", "");
if user.len() == email.len() {
panic!("Not a restmail account (doesn't end with @restmail.net)");
}
user
}
fn restmail_url(email: &str) -> Url {
let restmail_user = restmail_username(email);
let path = format!("/mail/{}", restmail_user);
Url::parse("https://restmail.net")
.unwrap()
.join(&path)
.unwrap()
}
/// For a given restmail email,
/// find the first email that satisfies the given predicate.
pub fn find_email<F>(email: &str, predicate: F) -> EmailJson
where
F: Fn(&EmailJson) -> bool,
{
let max_tries = 10;
let mail_url = restmail_url(email);
log::info!("Checking {} up to {} times.", email, max_tries);
for i in 0..max_tries {
let resp: Vec<serde_json::Value> = Request::get(mail_url.clone())
.send()
.unwrap()
.json()
.unwrap();
let mut matching_emails: Vec<serde_json::Value> =
resp.into_iter().filter(|email| predicate(email)).collect();
if matching_emails.is_empty() {
log::info!(
"Failed to find matching email. Waiting {} seconds and retrying.",
i + 1
);
std::thread::sleep(std::time::Duration::from_secs(i + 1));
continue;
}
if matching_emails.len() > 1 {
log::info!(
"Found {} emails that applies (taking latest)",
matching_emails.len()
);
matching_emails.sort_by(|a, b| {
let a_time = a["receivedAt"].as_u64().unwrap();
let b_time = b["receivedAt"].as_u64().unwrap();
b_time.cmp(&a_time)
})
}
return matching_emails[0].clone();
}
log::info!("Error: Failed to find email after {} tries!", max_tries);
panic!("Hit retry max!");
}
pub fn clear_mailbox(email: &str) {
let mail_url = restmail_url(email);
log::info!("Clearing restmail for {}.", email);
Request::delete(mail_url).send().unwrap();
}