Compare commits
No commits in common. "419f37b108075eab5c67ad03c51bbfd8dc7e4a42" and "4cbe551061d5a0feb82eb4a993629cbd707adcc0" have entirely different histories.
419f37b108
...
4cbe551061
9 changed files with 115 additions and 64 deletions
|
@ -1,7 +0,0 @@
|
|||
-- 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)
|
||||
);
|
|
@ -1,2 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
DROP TABLE email_tokens;
|
|
@ -1,7 +0,0 @@
|
|||
-- 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)
|
||||
);
|
|
@ -1,2 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
DROP TABLE password_reset_tokens;
|
|
@ -26,11 +26,13 @@ struct Query {
|
|||
///
|
||||
#[get("/reset-password")]
|
||||
pub async fn get(query: web::Query<Query>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
if let Ok(password_reset_token) =
|
||||
PasswordResetToken::get_with_identifier(&data, query.identifier.clone()).await
|
||||
PasswordResetToken::get_with_identifier(&mut conn, query.identifier.clone()).await
|
||||
{
|
||||
if Utc::now().signed_duration_since(password_reset_token.created_at) > Duration::hours(1) {
|
||||
password_reset_token.delete(&data).await?;
|
||||
password_reset_token.delete(&mut conn).await?;
|
||||
} else {
|
||||
return Err(Error::TooManyRequests(
|
||||
"Please allow 1 hour before sending a new email".to_string(),
|
||||
|
@ -72,8 +74,15 @@ pub async fn post(
|
|||
reset_password: web::Json<ResetPassword>,
|
||||
data: web::Data<Data>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
let password_reset_token =
|
||||
PasswordResetToken::get(&data, reset_password.token.clone()).await?;
|
||||
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());
|
||||
}
|
||||
|
||||
password_reset_token
|
||||
.set_password(&data, reset_password.password.clone())
|
||||
|
|
|
@ -46,15 +46,20 @@ pub async fn get(
|
|||
|
||||
let me = Me::get(&mut conn, uuid).await?;
|
||||
|
||||
let email_token = EmailToken::get(&data, me.uuid).await?;
|
||||
let email_token = EmailToken::get(&mut conn, me.uuid).await?;
|
||||
|
||||
if query.token != email_token.token {
|
||||
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?;
|
||||
|
||||
email_token.delete(&data).await?;
|
||||
email_token.delete(&mut conn).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
@ -85,9 +90,9 @@ pub async fn post(req: HttpRequest, data: web::Data<Data>) -> Result<HttpRespons
|
|||
return Ok(HttpResponse::NoContent().finish());
|
||||
}
|
||||
|
||||
if let Ok(email_token) = EmailToken::get(&data, me.uuid).await {
|
||||
if let Ok(email_token) = EmailToken::get(&mut conn, me.uuid).await {
|
||||
if Utc::now().signed_duration_since(email_token.created_at) > Duration::hours(1) {
|
||||
email_token.delete(&data).await?;
|
||||
email_token.delete(&mut conn).await?;
|
||||
} else {
|
||||
return Err(Error::TooManyRequests(
|
||||
"Please allow 1 hour before sending a new email".to_string(),
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
use chrono::Utc;
|
||||
use diesel::{
|
||||
ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, delete, dsl::now,
|
||||
insert_into,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lettre::message::MultiPart;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Data, error::Error, utils::generate_refresh_token};
|
||||
use crate::{Conn, Data, error::Error, schema::email_tokens, utils::generate_refresh_token};
|
||||
|
||||
use super::Me;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Selectable, Queryable)]
|
||||
#[diesel(table_name = email_tokens)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct EmailToken {
|
||||
user_uuid: Uuid,
|
||||
pub token: String,
|
||||
|
@ -15,8 +21,13 @@ pub struct EmailToken {
|
|||
}
|
||||
|
||||
impl EmailToken {
|
||||
pub async fn get(data: &Data, user_uuid: Uuid) -> Result<EmailToken, Error> {
|
||||
let email_token = serde_json::from_str(&data.get_cache_key(format!("{}_email_verify", user_uuid)).await?)?;
|
||||
pub async fn get(conn: &mut Conn, user_uuid: Uuid) -> Result<EmailToken, Error> {
|
||||
use email_tokens::dsl;
|
||||
let email_token = dsl::email_tokens
|
||||
.filter(dsl::user_uuid.eq(user_uuid))
|
||||
.select(EmailToken::as_select())
|
||||
.get_result(conn)
|
||||
.await?;
|
||||
|
||||
Ok(email_token)
|
||||
}
|
||||
|
@ -25,14 +36,17 @@ impl EmailToken {
|
|||
pub async fn new(data: &Data, me: Me) -> Result<(), Error> {
|
||||
let token = generate_refresh_token()?;
|
||||
|
||||
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()
|
||||
};
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
data.set_cache_key(format!("{}_email_verify", me.uuid), email_token, 86400).await?;
|
||||
use email_tokens::dsl;
|
||||
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")?;
|
||||
|
||||
|
@ -53,8 +67,13 @@ impl EmailToken {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(&self, data: &Data) -> Result<(), Error> {
|
||||
data.del_cache_key(format!("{}_email_verify", self.user_uuid)).await?;
|
||||
pub async fn delete(&self, conn: &mut Conn) -> Result<(), Error> {
|
||||
use email_tokens::dsl;
|
||||
delete(email_tokens::table)
|
||||
.filter(dsl::user_uuid.eq(self.user_uuid))
|
||||
.filter(dsl::token.eq(&self.token))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -4,21 +4,23 @@ use argon2::{
|
|||
};
|
||||
use chrono::Utc;
|
||||
use diesel::{
|
||||
ExpressionMethods, QueryDsl, update,
|
||||
ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, delete, dsl::now,
|
||||
insert_into, update,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lettre::message::MultiPart;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
Data,
|
||||
Conn, Data,
|
||||
error::Error,
|
||||
schema::users,
|
||||
schema::{password_reset_tokens, users},
|
||||
utils::{PASSWORD_REGEX, generate_refresh_token, global_checks, user_uuid_from_identifier},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Selectable, Queryable)]
|
||||
#[diesel(table_name = password_reset_tokens)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct PasswordResetToken {
|
||||
user_uuid: Uuid,
|
||||
pub token: String,
|
||||
|
@ -26,22 +28,29 @@ pub struct PasswordResetToken {
|
|||
}
|
||||
|
||||
impl PasswordResetToken {
|
||||
pub async fn get(data: &Data, token: String) -> Result<PasswordResetToken, Error> {
|
||||
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?)?;
|
||||
pub async fn get(conn: &mut Conn, token: String) -> Result<PasswordResetToken, Error> {
|
||||
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?;
|
||||
|
||||
Ok(password_reset_token)
|
||||
}
|
||||
|
||||
pub async fn get_with_identifier(
|
||||
data: &Data,
|
||||
conn: &mut Conn,
|
||||
identifier: String,
|
||||
) -> Result<PasswordResetToken, Error> {
|
||||
let mut conn = data.pool.get().await?;
|
||||
let user_uuid = user_uuid_from_identifier(conn, &identifier).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?)?;
|
||||
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?;
|
||||
|
||||
Ok(password_reset_token)
|
||||
}
|
||||
|
@ -63,14 +72,15 @@ impl PasswordResetToken {
|
|||
.get_result(&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?;
|
||||
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 mut reset_endpoint = data.config.web.frontend_url.join("reset-password")?;
|
||||
|
||||
|
@ -134,12 +144,16 @@ impl PasswordResetToken {
|
|||
|
||||
data.mail_client.send_mail(email).await?;
|
||||
|
||||
self.delete(&data).await
|
||||
self.delete(&mut 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?;
|
||||
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?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -31,6 +31,15 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
email_tokens (token, user_uuid) {
|
||||
#[max_length = 64]
|
||||
token -> Varchar,
|
||||
user_uuid -> Uuid,
|
||||
created_at -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
guild_members (uuid) {
|
||||
uuid -> Uuid,
|
||||
|
@ -80,6 +89,15 @@ 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]
|
||||
|
@ -137,6 +155,7 @@ diesel::joinable!(access_tokens -> refresh_tokens (refresh_token));
|
|||
diesel::joinable!(access_tokens -> users (uuid));
|
||||
diesel::joinable!(channel_permissions -> channels (channel_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 -> users (user_uuid));
|
||||
diesel::joinable!(guilds -> users (owner_uuid));
|
||||
|
@ -145,6 +164,7 @@ 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));
|
||||
|
@ -153,11 +173,13 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
access_tokens,
|
||||
channel_permissions,
|
||||
channels,
|
||||
email_tokens,
|
||||
guild_members,
|
||||
guilds,
|
||||
instance_permissions,
|
||||
invites,
|
||||
messages,
|
||||
password_reset_tokens,
|
||||
refresh_tokens,
|
||||
role_members,
|
||||
roles,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue