Factor out restmail client code
This commit is contained in:
Родитель
b48746ea35
Коммит
231abf8c32
|
@ -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();
|
||||
}
|
Загрузка…
Ссылка в новой задаче