feat: move email tokens to valkey

No need to have them in permanent DB storage when they are temporary
This commit is contained in:
Radical 2025-06-03 11:01:33 +00:00
parent 4cbe551061
commit b223dff4ba
5 changed files with 27 additions and 53 deletions

View file

@ -0,0 +1,7 @@
-- This file should undo anything in `up.sql`
CREATE TABLE email_tokens (
token VARCHAR(64) NOT NULL,
user_uuid uuid UNIQUE NOT NULL REFERENCES users(uuid),
created_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (token, user_uuid)
);

View file

@ -0,0 +1,2 @@
-- Your SQL goes here
DROP TABLE email_tokens;

View file

@ -46,20 +46,15 @@ pub async fn get(
let me = Me::get(&mut conn, uuid).await?; let me = Me::get(&mut conn, uuid).await?;
let email_token = EmailToken::get(&mut conn, me.uuid).await?; let email_token = EmailToken::get(&data, me.uuid).await?;
if query.token != email_token.token { if query.token != email_token.token {
return Ok(HttpResponse::Unauthorized().finish()); return Ok(HttpResponse::Unauthorized().finish());
} }
if Utc::now().signed_duration_since(email_token.created_at) > Duration::hours(24) {
email_token.delete(&mut conn).await?;
return Ok(HttpResponse::Gone().finish());
}
me.verify_email(&mut conn).await?; me.verify_email(&mut conn).await?;
email_token.delete(&mut conn).await?; email_token.delete(&data).await?;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
@ -90,9 +85,9 @@ pub async fn post(req: HttpRequest, data: web::Data<Data>) -> Result<HttpRespons
return Ok(HttpResponse::NoContent().finish()); return Ok(HttpResponse::NoContent().finish());
} }
if let Ok(email_token) = EmailToken::get(&mut conn, me.uuid).await { if let Ok(email_token) = EmailToken::get(&data, me.uuid).await {
if Utc::now().signed_duration_since(email_token.created_at) > Duration::hours(1) { if Utc::now().signed_duration_since(email_token.created_at) > Duration::hours(1) {
email_token.delete(&mut conn).await?; email_token.delete(&data).await?;
} else { } else {
return Err(Error::TooManyRequests( return Err(Error::TooManyRequests(
"Please allow 1 hour before sending a new email".to_string(), "Please allow 1 hour before sending a new email".to_string(),

View file

@ -1,19 +1,13 @@
use chrono::Utc; use chrono::Utc;
use diesel::{
ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, delete, dsl::now,
insert_into,
};
use diesel_async::RunQueryDsl;
use lettre::message::MultiPart; use lettre::message::MultiPart;
use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use crate::{Conn, Data, error::Error, schema::email_tokens, utils::generate_refresh_token}; use crate::{Data, error::Error, utils::generate_refresh_token};
use super::Me; use super::Me;
#[derive(Selectable, Queryable)] #[derive(Serialize, Deserialize)]
#[diesel(table_name = email_tokens)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct EmailToken { pub struct EmailToken {
user_uuid: Uuid, user_uuid: Uuid,
pub token: String, pub token: String,
@ -21,13 +15,8 @@ pub struct EmailToken {
} }
impl EmailToken { impl EmailToken {
pub async fn get(conn: &mut Conn, user_uuid: Uuid) -> Result<EmailToken, Error> { pub async fn get(data: &Data, user_uuid: Uuid) -> Result<EmailToken, Error> {
use email_tokens::dsl; let email_token = serde_json::from_str(&data.get_cache_key(format!("{}_email_verify", user_uuid)).await?)?;
let email_token = dsl::email_tokens
.filter(dsl::user_uuid.eq(user_uuid))
.select(EmailToken::as_select())
.get_result(conn)
.await?;
Ok(email_token) Ok(email_token)
} }
@ -36,17 +25,14 @@ impl EmailToken {
pub async fn new(data: &Data, me: Me) -> Result<(), Error> { pub async fn new(data: &Data, me: Me) -> Result<(), Error> {
let token = generate_refresh_token()?; let token = generate_refresh_token()?;
let mut conn = data.pool.get().await?; let email_token = EmailToken {
user_uuid: me.uuid,
token: token.clone(),
// TODO: Check if this can be replaced with something built into valkey
created_at: Utc::now()
};
use email_tokens::dsl; data.set_cache_key(format!("{}_email_verify", me.uuid), email_token, 86400).await?;
insert_into(email_tokens::table)
.values((
dsl::user_uuid.eq(me.uuid),
dsl::token.eq(&token),
dsl::created_at.eq(now),
))
.execute(&mut conn)
.await?;
let mut verify_endpoint = data.config.web.frontend_url.join("verify-email")?; let mut verify_endpoint = data.config.web.frontend_url.join("verify-email")?;
@ -67,13 +53,8 @@ impl EmailToken {
Ok(()) Ok(())
} }
pub async fn delete(&self, conn: &mut Conn) -> Result<(), Error> { pub async fn delete(&self, data: &Data) -> Result<(), Error> {
use email_tokens::dsl; data.del_cache_key(format!("{}_email_verify", self.user_uuid)).await?;
delete(email_tokens::table)
.filter(dsl::user_uuid.eq(self.user_uuid))
.filter(dsl::token.eq(&self.token))
.execute(conn)
.await?;
Ok(()) Ok(())
} }

View file

@ -31,15 +31,6 @@ diesel::table! {
} }
} }
diesel::table! {
email_tokens (token, user_uuid) {
#[max_length = 64]
token -> Varchar,
user_uuid -> Uuid,
created_at -> Timestamptz,
}
}
diesel::table! { diesel::table! {
guild_members (uuid) { guild_members (uuid) {
uuid -> Uuid, uuid -> Uuid,
@ -155,7 +146,6 @@ diesel::joinable!(access_tokens -> refresh_tokens (refresh_token));
diesel::joinable!(access_tokens -> users (uuid)); diesel::joinable!(access_tokens -> users (uuid));
diesel::joinable!(channel_permissions -> channels (channel_uuid)); diesel::joinable!(channel_permissions -> channels (channel_uuid));
diesel::joinable!(channels -> guilds (guild_uuid)); diesel::joinable!(channels -> guilds (guild_uuid));
diesel::joinable!(email_tokens -> users (user_uuid));
diesel::joinable!(guild_members -> guilds (guild_uuid)); diesel::joinable!(guild_members -> guilds (guild_uuid));
diesel::joinable!(guild_members -> users (user_uuid)); diesel::joinable!(guild_members -> users (user_uuid));
diesel::joinable!(guilds -> users (owner_uuid)); diesel::joinable!(guilds -> users (owner_uuid));
@ -173,7 +163,6 @@ diesel::allow_tables_to_appear_in_same_query!(
access_tokens, access_tokens,
channel_permissions, channel_permissions,
channels, channels,
email_tokens,
guild_members, guild_members,
guilds, guilds,
instance_permissions, instance_permissions,