From 419f37b108075eab5c67ad03c51bbfd8dc7e4a42 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 3 Jun 2025 11:03:52 +0000 Subject: [PATCH] feat: move password reset tokens to valkey Also just as useless to keep in DB --- .../down.sql | 7 ++ .../up.sql | 2 + src/api/v1/auth/reset_password.rs | 15 +---- src/objects/password_reset_token.rs | 64 ++++++++----------- src/schema.rs | 11 ---- 5 files changed, 37 insertions(+), 62 deletions(-) create mode 100644 migrations/2025-06-03-110142_remove_password_reset_tokens/down.sql create mode 100644 migrations/2025-06-03-110142_remove_password_reset_tokens/up.sql diff --git a/migrations/2025-06-03-110142_remove_password_reset_tokens/down.sql b/migrations/2025-06-03-110142_remove_password_reset_tokens/down.sql new file mode 100644 index 0000000..009d9e4 --- /dev/null +++ b/migrations/2025-06-03-110142_remove_password_reset_tokens/down.sql @@ -0,0 +1,7 @@ +-- This file should undo anything in `up.sql` +CREATE TABLE password_reset_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) +); diff --git a/migrations/2025-06-03-110142_remove_password_reset_tokens/up.sql b/migrations/2025-06-03-110142_remove_password_reset_tokens/up.sql new file mode 100644 index 0000000..181d7c5 --- /dev/null +++ b/migrations/2025-06-03-110142_remove_password_reset_tokens/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +DROP TABLE password_reset_tokens; diff --git a/src/api/v1/auth/reset_password.rs b/src/api/v1/auth/reset_password.rs index 4373a82..444266c 100644 --- a/src/api/v1/auth/reset_password.rs +++ b/src/api/v1/auth/reset_password.rs @@ -26,13 +26,11 @@ struct Query { /// #[get("/reset-password")] pub async fn get(query: web::Query, data: web::Data) -> Result { - let mut conn = data.pool.get().await?; - if let Ok(password_reset_token) = - PasswordResetToken::get_with_identifier(&mut conn, query.identifier.clone()).await + PasswordResetToken::get_with_identifier(&data, query.identifier.clone()).await { if Utc::now().signed_duration_since(password_reset_token.created_at) > Duration::hours(1) { - password_reset_token.delete(&mut conn).await?; + password_reset_token.delete(&data).await?; } else { return Err(Error::TooManyRequests( "Please allow 1 hour before sending a new email".to_string(), @@ -74,15 +72,8 @@ pub async fn post( reset_password: web::Json, data: web::Data, ) -> Result { - let mut conn = data.pool.get().await?; - let password_reset_token = - PasswordResetToken::get(&mut conn, reset_password.token.clone()).await?; - - if Utc::now().signed_duration_since(password_reset_token.created_at) > Duration::hours(24) { - password_reset_token.delete(&mut conn).await?; - return Ok(HttpResponse::Gone().finish()); - } + PasswordResetToken::get(&data, reset_password.token.clone()).await?; password_reset_token .set_password(&data, reset_password.password.clone()) diff --git a/src/objects/password_reset_token.rs b/src/objects/password_reset_token.rs index e3c7bca..0376d88 100644 --- a/src/objects/password_reset_token.rs +++ b/src/objects/password_reset_token.rs @@ -4,23 +4,21 @@ use argon2::{ }; use chrono::Utc; use diesel::{ - ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, delete, dsl::now, - insert_into, update, + ExpressionMethods, QueryDsl, update, }; use diesel_async::RunQueryDsl; use lettre::message::MultiPart; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - Conn, Data, + Data, error::Error, - schema::{password_reset_tokens, users}, + schema::users, utils::{PASSWORD_REGEX, generate_refresh_token, global_checks, user_uuid_from_identifier}, }; -#[derive(Selectable, Queryable)] -#[diesel(table_name = password_reset_tokens)] -#[diesel(check_for_backend(diesel::pg::Pg))] +#[derive(Serialize, Deserialize)] pub struct PasswordResetToken { user_uuid: Uuid, pub token: String, @@ -28,29 +26,22 @@ pub struct PasswordResetToken { } impl PasswordResetToken { - pub async fn get(conn: &mut Conn, token: String) -> Result { - use password_reset_tokens::dsl; - let password_reset_token = dsl::password_reset_tokens - .filter(dsl::token.eq(token)) - .select(PasswordResetToken::as_select()) - .get_result(conn) - .await?; + pub async fn get(data: &Data, token: String) -> Result { + let user_uuid: Uuid = serde_json::from_str(&data.get_cache_key(format!("{}", token)).await?)?; + let password_reset_token = serde_json::from_str(&data.get_cache_key(format!("{}_password_reset", user_uuid)).await?)?; Ok(password_reset_token) } pub async fn get_with_identifier( - conn: &mut Conn, + data: &Data, identifier: String, ) -> Result { - let user_uuid = user_uuid_from_identifier(conn, &identifier).await?; + let mut conn = data.pool.get().await?; - use password_reset_tokens::dsl; - let password_reset_token = dsl::password_reset_tokens - .filter(dsl::user_uuid.eq(user_uuid)) - .select(PasswordResetToken::as_select()) - .get_result(conn) - .await?; + let user_uuid = user_uuid_from_identifier(&mut conn, &identifier).await?; + + let password_reset_token = serde_json::from_str(&data.get_cache_key(format!("{}_password_reset", user_uuid)).await?)?; Ok(password_reset_token) } @@ -72,15 +63,14 @@ impl PasswordResetToken { .get_result(&mut conn) .await?; - use password_reset_tokens::dsl; - insert_into(password_reset_tokens::table) - .values(( - dsl::user_uuid.eq(user_uuid), - dsl::token.eq(&token), - dsl::created_at.eq(now), - )) - .execute(&mut conn) - .await?; + let password_reset_token = PasswordResetToken { + user_uuid, + token: token.clone(), + created_at: Utc::now(), + }; + + data.set_cache_key(format!("{}_password_reset", user_uuid), password_reset_token, 86400).await?; + data.set_cache_key(token.clone(), user_uuid, 86400).await?; let mut reset_endpoint = data.config.web.frontend_url.join("reset-password")?; @@ -144,16 +134,12 @@ impl PasswordResetToken { data.mail_client.send_mail(email).await?; - self.delete(&mut conn).await + self.delete(&data).await } - pub async fn delete(&self, conn: &mut Conn) -> Result<(), Error> { - use password_reset_tokens::dsl; - delete(password_reset_tokens::table) - .filter(dsl::user_uuid.eq(self.user_uuid)) - .filter(dsl::token.eq(&self.token)) - .execute(conn) - .await?; + pub async fn delete(&self, data: &Data) -> Result<(), Error> { + data.del_cache_key(format!("{}_password_reset", &self.user_uuid)).await?; + data.del_cache_key(format!("{}", &self.token)).await?; Ok(()) } diff --git a/src/schema.rs b/src/schema.rs index 09fa08a..aaef9c1 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -80,15 +80,6 @@ diesel::table! { } } -diesel::table! { - password_reset_tokens (token, user_uuid) { - #[max_length = 64] - token -> Varchar, - user_uuid -> Uuid, - created_at -> Timestamptz, - } -} - diesel::table! { refresh_tokens (token) { #[max_length = 64] @@ -154,7 +145,6 @@ diesel::joinable!(invites -> guilds (guild_uuid)); diesel::joinable!(invites -> users (user_uuid)); diesel::joinable!(messages -> channels (channel_uuid)); diesel::joinable!(messages -> users (user_uuid)); -diesel::joinable!(password_reset_tokens -> users (user_uuid)); diesel::joinable!(refresh_tokens -> users (uuid)); diesel::joinable!(role_members -> guild_members (member_uuid)); diesel::joinable!(roles -> guilds (guild_uuid)); @@ -168,7 +158,6 @@ diesel::allow_tables_to_appear_in_same_query!( instance_permissions, invites, messages, - password_reset_tokens, refresh_tokens, role_members, roles,