1
0
Fork 0
mirror of https://gitlab.com/hagrid-keyserver/hagrid.git synced 2023-02-13 20:55:02 -05:00

rate limit mails for /manage

This commit is contained in:
Vincent Breitmoser 2019-05-05 14:58:05 +02:00
parent fe83e89da1
commit 24e739d886
4 changed files with 40 additions and 20 deletions

View file

@ -14,6 +14,7 @@ keys_internal_dir = "state/keys-internal"
keys_external_dir = "state/keys-external"
token_dir = "state/tokens"
tmp_dir = "state/tmp"
mail_rate_limit = 60
[staging]
base-URI = "https://keys.openpgp.org"
@ -27,6 +28,7 @@ keys_external_dir = "public/keys"
assets_dir = "public/assets"
token_dir = "tokens"
tmp_dir = "tmp"
mail_rate_limit = 60
[production]
base-URI = "https://keys.openpgp.org"
@ -40,3 +42,4 @@ keys_external_dir = "public/keys"
assets_dir = "public/assets"
token_dir = "tokens"
tmp_dir = "tmp"
mail_rate_limit = 3600

View file

@ -83,14 +83,14 @@ impl Service {
};
self.send(
&vec![userid.clone()],
&vec![userid],
"Please verify your email address",
"verify",
ctx,
)
}
pub fn send_manage_token(&self, recipients: &[Email], uri: &str)
pub fn send_manage_token(&self, recipient: &Email, uri: &str)
-> Result<()> {
let ctx = context::Manage {
uri: uri.to_string(),
@ -99,14 +99,14 @@ impl Service {
};
self.send(
recipients,
&[recipient],
&format!("{}: Manage your key", &self.domain),
"manage",
ctx,
)
}
fn send<T>(&self, to: &[Email], subject: &str, template: &str, ctx: T)
fn send<T>(&self, to: &[&Email], subject: &str, template: &str, ctx: T)
-> Result<()>
where T: Serialize + Clone,
{

View file

@ -7,6 +7,7 @@ use failure::Fallible as Result;
use web::{HagridState, MyResponse, templates::General};
use database::{Database, KeyDatabase, types::Email, types::Fingerprint};
use mail;
use rate_limiter::RateLimiter;
use tokens::{self, StatelessSerializable};
#[derive(Debug,Serialize,Deserialize)]
@ -107,9 +108,10 @@ pub fn vks_manage_key(
#[post("/manage", data="<request>")]
pub fn vks_manage_post(
db: State<KeyDatabase>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
request: Form<forms::ManageRequest>,
token_service: rocket::State<tokens::Service>,
mail_service: Option<rocket::State<mail::Service>>,
) -> MyResponse {
use std::convert::TryInto;
@ -117,34 +119,39 @@ pub fn vks_manage_post(
Ok(email) => email,
Err(_) => return MyResponse::not_found(
Some("manage/manage"),
Some(format!("Malformed email address: {:?}", request.search_term)))
Some(format!("Malformed email address: {}", request.search_term)))
};
let tpk = match db.lookup(&database::Query::ByEmail(email.clone())) {
Ok(Some(tpk)) => tpk,
Ok(None) => return MyResponse::not_found(
Some("manage/manage"),
Some(format!("No key for address {:?}", request.search_term))),
Some(format!("No key for address {}", request.search_term))),
Err(e) => return MyResponse::ise(e),
};
let email_exists = tpk.userids()
.flat_map(|binding| binding.userid().to_string().parse::<Email>())
.any(|candidate| candidate == email);
if !email_exists {
return MyResponse::ise(failure::err_msg("Address check failed!"));
}
if !rate_limiter.action_perform(format!("manage-{}", &email)) {
return MyResponse::not_found(
Some("manage/manage"),
Some("A request was already sent for this address recently.".to_owned()));
}
let fpr: Fingerprint = tpk.fingerprint().try_into().unwrap();
let token = token_service.create(StatelessVerifyToken { fpr });
let token_uri = uri!(vks_manage_key: token).to_string();
if let Some(mail_service) = mail_service {
for binding in tpk.userids() {
let email_candidate = binding.userid().to_string().parse::<Email>();
if let Ok(email_candidate) = email_candidate {
if &email_candidate != &email {
continue;
}
if let Err(e) = mail_service.send_manage_token(
&[email_candidate], &token_uri) {
return MyResponse::ise(e);
}
}
}
if let Err(e) = mail_service.send_manage_token(&email, &token_uri) {
return MyResponse::ise(e);
}
let ctx = templates::ManageLinkSent {
address: email.to_string(),
};

View file

@ -13,12 +13,14 @@ use std::path::PathBuf;
pub mod upload;
use mail;
use tokens;
use rate_limiter::RateLimiter;
use database::{Database, KeyDatabase, Query};
use database::types::{Email, Fingerprint, KeyID};
use Result;
use std::str::FromStr;
use std::convert::TryInto;
mod hkp;
mod manage;
@ -343,6 +345,7 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
let stateful_token_service = configure_stateful_token_service(rocket.config())?;
let stateless_token_service = configure_stateless_token_service(rocket.config())?;
let mail_service = configure_mail_service(rocket.config())?;
let rate_limiter = configure_rate_limiter(rocket.config())?;
Ok(rocket
.attach(Template::fairing())
@ -351,6 +354,7 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
.manage(stateful_token_service)
.manage(mail_service)
.manage(db_service)
.manage(rate_limiter)
.mount("/", routes)
)
}
@ -421,6 +425,12 @@ fn configure_mail_service(config: &Config) -> Result<mail::Service> {
}
}
fn configure_rate_limiter(config: &Config) -> Result<RateLimiter> {
let timeout_secs = config.get_int("mail_rate_limit").unwrap_or(60);
let timeout_secs = timeout_secs.try_into()?;
Ok(RateLimiter::new(timeout_secs))
}
#[cfg(test)]
pub mod tests {
use regex;