You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
3.4 KiB
Rust
124 lines
3.4 KiB
Rust
use std::convert::Infallible;
|
|
use std::net::IpAddr;
|
|
use std::collections::HashMap;
|
|
|
|
use rocket::request::{self, FromRequest};
|
|
use rocket::serde::json::Json;
|
|
use rocket::http::Status;
|
|
use rocket::Request;
|
|
use mail_builder::MessageBuilder;
|
|
use serde::Serialize;
|
|
|
|
pub struct ClientIp(Option<IpAddr>);
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Response {
|
|
status: &'static str,
|
|
message: Option<String>,
|
|
}
|
|
|
|
#[rocket::async_trait]
|
|
impl<'r> FromRequest<'r> for ClientIp {
|
|
type Error = Infallible;
|
|
|
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
|
request::Outcome::Success(ClientIp(request.client_ip()))
|
|
}
|
|
}
|
|
|
|
#[post("/api/contact/<slug>", data = "<fields>")]
|
|
pub async fn handler(state: &rocket::State<crate::State>, client_ip: ClientIp, slug: &str, fields: Json<HashMap<&str, &str>>) -> Result<Json<Response>, (Status, Json<Response>)> {
|
|
{
|
|
let rate_limit = &state.rate_limit;
|
|
|
|
let client_ip = client_ip.0.ok_or(
|
|
(Status::InternalServerError, Json(Response {
|
|
status: "error",
|
|
message: Some("Could not identify client IP".to_string())
|
|
}))
|
|
)?;
|
|
|
|
let rate_limit_outcome = rate_limit.check_key(&client_ip);
|
|
|
|
if let Err(_not_until) = rate_limit_outcome {
|
|
return Err((Status::TooManyRequests, Json(Response {
|
|
status: "ratelimit",
|
|
message: Some("Too many messages, play nice friend".to_string()),
|
|
})));
|
|
}
|
|
|
|
println!("Passed rate limit");
|
|
};
|
|
|
|
{
|
|
let email_client = &state.smtp_builder;
|
|
let mail_conf = &state.config.mailer;
|
|
let form_conf = &state.config.forms.iter().find(|form| {
|
|
form.slug == slug
|
|
});
|
|
|
|
let form_conf = form_conf.ok_or(
|
|
(Status::NotFound, Json(Response {
|
|
status: "notfound",
|
|
message: Some(format!("Form ID {} not found", slug))
|
|
}))
|
|
)?;
|
|
|
|
let mut message_text = format!(
|
|
"You have recieved a message on {}:
|
|
|
|
Form fields:", form_conf.name);
|
|
|
|
for field in &form_conf.fields {
|
|
let value_opt = fields.get(field.name.as_str());
|
|
let required = field.required;
|
|
|
|
if value_opt.is_none() && required {
|
|
return Err((Status::BadRequest, Json(Response {
|
|
status: "error",
|
|
message: Some(format!("Missing field: {}", field.name))
|
|
})))
|
|
}
|
|
|
|
message_text.push_str(&format!("\n - {}: {}", field.name, value_opt.unwrap_or(&"[empty]")));
|
|
}
|
|
|
|
let message = MessageBuilder::new()
|
|
.from(mail_conf.from_address.clone())
|
|
.to(form_conf.recipient_email.clone())
|
|
.subject(form_conf.subject.clone())
|
|
.text_body(message_text);
|
|
|
|
println!("Message composed");
|
|
|
|
let connection_result = email_client.connect().await;
|
|
|
|
if let Err(err) = connection_result {
|
|
println!("Error connecting to server: {}", err.to_string());
|
|
|
|
return Err((Status::InternalServerError, Json(Response {
|
|
status: "error",
|
|
message: Some("Could not send message".to_string())
|
|
})));
|
|
} else {
|
|
let mut connection = connection_result.unwrap();
|
|
println!("Connected to mail server");
|
|
|
|
if let Err(err) = connection.send(message).await {
|
|
println!("Error sending email: {}", err.to_string());
|
|
|
|
return Err((Status::InternalServerError, Json(Response {
|
|
status: "error",
|
|
message: Some("Could not send message".to_string())
|
|
})));
|
|
}
|
|
}
|
|
|
|
println!("Message sent");
|
|
return Ok(Json(Response {
|
|
status: "Message sent",
|
|
message: None
|
|
}));
|
|
};
|
|
}
|