From 76f733b216b996cec6f5dad9ae69da20d2bd60f8 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Thu, 15 Feb 2024 13:50:15 -0500 Subject: [PATCH] feat(db): Implement password reset DAO (#1456) * feat(db, webserver): Implement password reset flow * [autofix.ci] apply automated fixes * Remove webserver logic from this PR * Use ID instead of email * Create delete_expired_password_reset * Rename id to user_id * Add rowid * Rename parameters * Rename db methods * Unrename one db method --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../migrations/0014_password-reset.down.sql | 1 + .../migrations/0014_password-reset.up.sql | 6 ++ ee/tabby-db/schema.sqlite | Bin 98304 -> 106496 bytes ee/tabby-db/src/lib.rs | 1 + ee/tabby-db/src/password_reset.rs | 58 ++++++++++++++++++ ee/tabby-db/src/users.rs | 11 ++++ .../email_templates/password_reset.html | 9 +++ 7 files changed, 86 insertions(+) create mode 100644 ee/tabby-db/migrations/0014_password-reset.down.sql create mode 100644 ee/tabby-db/migrations/0014_password-reset.up.sql create mode 100644 ee/tabby-db/src/password_reset.rs create mode 100644 ee/tabby-webserver/email_templates/password_reset.html diff --git a/ee/tabby-db/migrations/0014_password-reset.down.sql b/ee/tabby-db/migrations/0014_password-reset.down.sql new file mode 100644 index 000000000000..818ffa3e6bda --- /dev/null +++ b/ee/tabby-db/migrations/0014_password-reset.down.sql @@ -0,0 +1 @@ +DROP TABLE password_reset; diff --git a/ee/tabby-db/migrations/0014_password-reset.up.sql b/ee/tabby-db/migrations/0014_password-reset.up.sql new file mode 100644 index 000000000000..34edbe729cde --- /dev/null +++ b/ee/tabby-db/migrations/0014_password-reset.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE password_reset( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL UNIQUE, + code VARCHAR(36) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')) +); diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index a5673c2840df4a865c4f7a82e37a433c0739116c..cdf23331d4b039a52b8adde17a1a065fe141353c 100644 GIT binary patch delta 759 zcmZo@U~4$QHbI_OiU9!xHtO8qSK#AX#lSz4-;VDtUp=29?=Ie0p5HuEc+9x3aHn!} zaIM;GDA2|g7sJQSsASy2wvS|2%}F`3Sj>(Rtl0mZ|A?3;X7P8r2`Zh4^kw@H)j|7X>lpa|@PFg~!2gQ>3I9F*8~m5}&+s4PKfu3>e+&P*&4LD9 z{A{eujEsDI&0F-hZ_#HAFp%PA6=L8&%xA@Of$KKcBCbqM0roX)Y^*{&lYvqFg~h$G zP@G*{Qu<{_vM>gO34>Z-$~ z00hbTDXEj$IMpT};N_ak!K%usEXFRbuFlxTI{jld`0+b`rW8VD~65Lo0O005gB;rajo delta 511 zcmZoTz}C>fHbI_Of&l^eH|pHrm*eH;VBnw0Z^w6+ubxklcNcFg&u^Y7JZ9WixKp_~ zHXABD!=#QO$WLYsj>Wb+V!ys0Uj-7^l9_y0P8r2`Zh4S1 zcTcvHmqigz1Byp2PMSPZ9@W~j@&>9=@?~7WVCLoD!odHB{~P}Y{#X1@`0w%G;J?Iw zhW{A<0sdY5TQ&MUz{ZttS^Svu*#J#wg0fkynzLn3JBLzg-}c@fH)~%gJ|{ S?sBk+v4JCc`-L1v17QG7j)=|x diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 04c5a352ffdf..0ab038d32b6a 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -14,6 +14,7 @@ mod github_oauth_credential; mod google_oauth_credential; mod invitations; mod job_runs; +mod password_reset; mod path; mod refresh_tokens; mod repositories; diff --git a/ee/tabby-db/src/password_reset.rs b/ee/tabby-db/src/password_reset.rs new file mode 100644 index 000000000000..a1a43f88c89f --- /dev/null +++ b/ee/tabby-db/src/password_reset.rs @@ -0,0 +1,58 @@ +use anyhow::Result; +use chrono::{DateTime, Duration, Utc}; +use sqlx::{prelude::FromRow, query}; +use uuid::Uuid; + +use crate::DbConn; + +#[derive(FromRow)] +pub struct PasswordResetDAO { + pub user_id: i32, + pub code: String, + pub created_at: DateTime, +} + +impl DbConn { + pub async fn create_password_reset(&self, user_id: i32) -> Result { + let code = Uuid::new_v4().to_string(); + let time = Utc::now(); + query!( + "INSERT INTO password_reset (user_id, code, created_at) VALUES ($1, $2, $3) + ON CONFLICT(user_id) DO UPDATE SET code= $2, created_at = $3;", + user_id, + code, + time + ) + .execute(&self.pool) + .await?; + Ok(code) + } + + pub async fn delete_password_reset_by_user_id(&self, user_id: i32) -> Result<()> { + query!("DELETE FROM password_reset WHERE user_id = ?", user_id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_password_reset_by_user_id( + &self, + user_id: i32, + ) -> Result> { + let password_reset = sqlx::query_as( + "SELECT user_id, code, created_at FROM password_reset WHERE user_id = ?;", + ) + .bind(user_id) + .fetch_optional(&self.pool) + .await?; + Ok(password_reset) + } + + pub async fn delete_expired_password_resets(&self) -> Result<()> { + let time = Utc::now() - Duration::hours(1); + query!("DELETE FROM password_reset WHERE created_at < ?", time) + .execute(&self.pool) + .await?; + Ok(()) + } +} diff --git a/ee/tabby-db/src/users.rs b/ee/tabby-db/src/users.rs index 32513ad740f7..432defb3e34c 100644 --- a/ee/tabby-db/src/users.rs +++ b/ee/tabby-db/src/users.rs @@ -191,6 +191,17 @@ impl DbConn { Ok(()) } } + + pub async fn update_user_password(&self, id: i32, password_encrypted: String) -> Result<()> { + query!( + "UPDATE users SET password_encrypted = ? WHERE id = ?", + password_encrypted, + id + ) + .execute(&self.pool) + .await?; + Ok(()) + } } fn generate_auth_token() -> String { diff --git a/ee/tabby-webserver/email_templates/password_reset.html b/ee/tabby-webserver/email_templates/password_reset.html new file mode 100644 index 000000000000..83d374ddcdb0 --- /dev/null +++ b/ee/tabby-webserver/email_templates/password_reset.html @@ -0,0 +1,9 @@ +Reset your Tabby password +--- +You recently requested a password reset for your TabbyML account {{EMAIL}}. Please click the link below to reset your password. + +{{EXTERNAL_URL}}/passwordReset?code={{CODE}} + +Best regards, + +The Tabby Team