Working on emails, updated swagger, added geometry column to airports
This commit is contained in:
161
api/src/account/email_token.rs
Normal file
161
api/src/account/email_token.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use crate::account::hash;
|
||||
use crate::db::redis_async_connection;
|
||||
use crate::error::{ApiResult, Error};
|
||||
use crate::smtp;
|
||||
use chrono::{Datelike, Utc};
|
||||
use redis::{AsyncCommands, RedisResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::{env, fs};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct EmailToken {
|
||||
pub email: String,
|
||||
pub token: String,
|
||||
pub ip_address: String,
|
||||
}
|
||||
|
||||
impl EmailToken {
|
||||
pub fn new(email: String, token: String, ip_address: &str) -> Self {
|
||||
Self {
|
||||
email,
|
||||
token,
|
||||
ip_address: hash(&ip_address).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn store(&self, ttl_secs: i64) -> ApiResult<()> {
|
||||
let mut conn = redis_async_connection().await?;
|
||||
let key = self.token.clone();
|
||||
let value = serde_json::to_string(self)?;
|
||||
let now = Utc::now();
|
||||
let expires_at = now + chrono::Duration::seconds(ttl_secs);
|
||||
let ttl = expires_at.timestamp() - now.timestamp();
|
||||
let result: RedisResult<()> = conn.set_ex(key, &value, ttl as u64).await;
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(token: &str) -> ApiResult<Self> {
|
||||
let mut conn = redis_async_connection().await?;
|
||||
let result: RedisResult<Option<String>> = conn.get(token).await;
|
||||
match result {
|
||||
Ok(Some(value)) => Ok(serde_json::from_str(&value)?),
|
||||
Ok(None) => Err(Error::new(404, format!("Missing email token {}", token))),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(token: &str) -> ApiResult<()> {
|
||||
let mut conn = redis_async_connection().await?;
|
||||
let result: RedisResult<()> = conn.del(token).await;
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SimpleEmailCtx {
|
||||
pub logo_url: String,
|
||||
pub link: String,
|
||||
pub domain: String,
|
||||
pub year: i32,
|
||||
}
|
||||
|
||||
pub fn send_password_reset_email(
|
||||
email: &str,
|
||||
email_token: &EmailToken,
|
||||
ip_address: &str,
|
||||
) -> ApiResult<()> {
|
||||
let base_url = env::var("EXTERNAL_URL")?;
|
||||
let link = format!("{base_url}/profile/reset?token={}", email_token.token);
|
||||
let subject = "Reset your password";
|
||||
|
||||
let plain = format!(
|
||||
"Hello,\n\n\
|
||||
We received a password reset request. Click the link below:\n\n\
|
||||
{link}\n\n\
|
||||
This link expires in 24 hours. If you didn't request this, please ignore.\n\n\
|
||||
Cheers,\n\
|
||||
The Aviation Data Team",
|
||||
link = link
|
||||
);
|
||||
|
||||
let ctx = SimpleEmailCtx {
|
||||
logo_url: format!("{}/logo.svg", base_url),
|
||||
link: link.clone(),
|
||||
domain: base_url,
|
||||
year: Utc::now().year(),
|
||||
};
|
||||
|
||||
let template_dir = env::var("TEMPLATE_DIR")?;
|
||||
let tpl_path = Path::new(&template_dir).join("password_reset.html");
|
||||
let template_html = fs::read_to_string(&tpl_path)?;
|
||||
let html = smtp::registry()
|
||||
.render_template(&template_html, &ctx)
|
||||
.unwrap();
|
||||
|
||||
match smtp::send_email(&email, subject, plain, html) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Invalid password reset attempt [Email: {}] [IP Address: {}]: {}",
|
||||
email,
|
||||
ip_address,
|
||||
err
|
||||
);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_confirm_email(
|
||||
email: &str,
|
||||
email_token: &EmailToken,
|
||||
ip_address: &str,
|
||||
) -> ApiResult<()> {
|
||||
let base_url = env::var("EXTERNAL_URL")?;
|
||||
let link = format!("{base_url}/profile/confirm?token={}", email_token.token);
|
||||
let subject = "Confirm your email address";
|
||||
|
||||
let plain = format!(
|
||||
"Hello,\n\n\
|
||||
Thanks for registering! Click the link below to confirm your email address:\n\n\
|
||||
{link}\n\n\
|
||||
If you didn’t sign up for an Aviation Data account, please ignore this.\n\n\
|
||||
Cheers,\n\
|
||||
The Aviation Data Team",
|
||||
link = link
|
||||
);
|
||||
|
||||
let ctx = SimpleEmailCtx {
|
||||
logo_url: format!("{}/logo.svg", base_url),
|
||||
link: link.clone(),
|
||||
domain: base_url,
|
||||
year: Utc::now().year(),
|
||||
};
|
||||
|
||||
let template_dir = env::var("TEMPLATE_DIR")?;
|
||||
let tpl_path = Path::new(&template_dir).join("confirm_email.html");
|
||||
let template_html = fs::read_to_string(&tpl_path)?;
|
||||
let html = smtp::registry()
|
||||
.render_template(&template_html, &ctx)
|
||||
.unwrap();
|
||||
|
||||
match smtp::send_email(&email, subject, plain, html) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Invalid email confirmation attempt [Email: {}] [IP Address: {}]: {}",
|
||||
email,
|
||||
ip_address,
|
||||
err
|
||||
);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user