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:
parent
fe83e89da1
commit
24e739d886
4 changed files with 40 additions and 20 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue