fix: resend verification / reset password if account exists on create

This commit is contained in:
Paul Makles
2022-05-16 19:07:41 +01:00
parent fdc7afc4f0
commit 001a9698c5
19 changed files with 93 additions and 55 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"rust-analyzer.checkOnSave.command": "clippy"
}
+2 -2
View File
@@ -1,4 +1,4 @@
use okapi::openapi3::{SecuritySchemeData, SecurityScheme};
use okapi::openapi3::{SecurityScheme, SecuritySchemeData};
use rocket::http::Status;
use rocket::outcome::Outcome;
use rocket::request::{self, FromRequest};
@@ -6,7 +6,7 @@ use rocket::Request;
use mongodb::bson::DateTime;
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::request::{RequestHeaderInput, OpenApiFromRequest};
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use wither::bson::doc;
use wither::prelude::*;
+1 -1
View File
@@ -49,7 +49,7 @@ impl Session {
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "session"
with: "session",
})?
.ok_or_else(|| Error::InvalidCredentials)
}
+52 -25
View File
@@ -5,7 +5,9 @@ use chrono::{Duration, Utc};
use lettre::transport::smtp::authentication::Credentials;
use lettre::transport::smtp::client::Tls;
use lettre::SmtpTransport;
use mongodb::bson::DateTime;
use mongodb::options::FindOneOptions;
use mongodb::Database;
use nanoid::nanoid;
@@ -203,11 +205,6 @@ impl Auth {
// #region Operations
/// Create a new account without validating fields.
///
/// You should NOT handle errors from this function unless
/// if you're debugging this library, it can open you up to
/// potential attacks such as email enumeration. Although,
/// for something like an admin panel, that's fine.
pub async fn create_account(
&self,
email: String,
@@ -223,31 +220,61 @@ impl Auth {
// Hash the user's password.
let password = self.hash_password(plaintext_password)?;
// Send email verification.
let verification = if verify_email {
self.generate_email_verification(email.clone()).await
// Check if the account exists first.
if let Some(mut account) = Account::find_one(
&self.db,
doc! {
"email_normalised": &email_normalised
},
FindOneOptions::builder().build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "account",
})? {
if let AccountVerification::Pending { .. } = &account.verification {
account.verification = self
.generate_email_verification(account.email.clone())
.await;
account.save_to_db(&self.db).await?;
} else {
account.password_reset = self
.generate_email_password_reset(account.email.clone())
.await;
account.save_to_db(&self.db).await?;
}
Ok(account)
} else {
AccountVerification::Verified
};
// Send email verification.
let verification = if verify_email {
self.generate_email_verification(email.clone()).await
} else {
AccountVerification::Verified
};
// Construct new Account.
let mut account = Account {
id: None,
// Construct new Account.
let mut account = Account {
id: None,
email,
email_normalised,
password,
email,
email_normalised,
password,
disabled: None,
verification,
password_reset: None,
mfa: MultiFactorAuthentication::default(),
};
disabled: None,
verification,
password_reset: None,
mfa: MultiFactorAuthentication::default(),
};
// Commit to database.
account.save_to_db(&self.db).await?;
// Commit to database.
account.save_to_db(&self.db).await?;
Ok(account)
Ok(account)
}
}
/// Create a new session / login to an account.
@@ -306,7 +333,7 @@ impl Auth {
doc! {
"$set": update
},
None
None,
)
.await
.map(|_| ())
+1 -1
View File
@@ -1,4 +1,4 @@
use okapi::openapi3::{SchemaObject, RefOr};
use okapi::openapi3::{RefOr, SchemaObject};
use regex::Regex;
use rocket::http::{ContentType, Status};
use rocket::request::Request;
+7 -9
View File
@@ -24,7 +24,10 @@ pub struct DataCreateAccount {
/// Create a new account.
#[openapi(tag = "Account")]
#[post("/create", data = "<data>")]
pub async fn create_account(auth: &State<Auth>, data: Json<DataCreateAccount>) -> Result<EmptyResponse> {
pub async fn create_account(
auth: &State<Auth>,
data: Json<DataCreateAccount>,
) -> Result<EmptyResponse> {
let data = data.into_inner();
// Perform validation on given data.
@@ -36,16 +39,11 @@ pub async fn create_account(auth: &State<Auth>, data: Json<DataCreateAccount>) -
let invite = auth.check_invite(data.invite).await?;
// Create an account but quietly fail any errors.
let account = auth
.create_account(data.email, data.password, true)
.await
.ok();
let account = auth.create_account(data.email, data.password, true).await?;
// Make sure to use up the invite.
if let Some(account) = account {
if let Some(invite) = invite {
invite.claim(&auth.db, account.id.unwrap()).await.ok();
}
if let Some(invite) = invite {
invite.claim(&auth.db, account.id.unwrap()).await.ok();
}
Ok(EmptyResponse)
+4 -1
View File
@@ -23,7 +23,10 @@ pub struct DataPasswordReset {
/// Confirm password reset and change the password.
#[openapi(tag = "Account")]
#[patch("/reset_password", data = "<data>")]
pub async fn password_reset(auth: &State<Auth>, data: Json<DataPasswordReset>) -> Result<EmptyResponse> {
pub async fn password_reset(
auth: &State<Auth>,
data: Json<DataPasswordReset>,
) -> Result<EmptyResponse> {
let data = data.into_inner();
let mut account = Account::find_one(
+4 -1
View File
@@ -23,7 +23,10 @@ pub struct DataResendVerification {
/// Resend account creation verification email.
#[openapi(tag = "Account")]
#[post("/reverify", data = "<data>")]
pub async fn resend_verification(auth: &State<Auth>, data: Json<DataResendVerification>) -> Result<EmptyResponse> {
pub async fn resend_verification(
auth: &State<Auth>,
data: Json<DataResendVerification>,
) -> Result<EmptyResponse> {
let data = data.into_inner();
// Perform validation on given data.
+4 -1
View File
@@ -23,7 +23,10 @@ pub struct DataSendPasswordReset {
/// Send an email to reset account password.
#[openapi(tag = "Account")]
#[post("/reset_password", data = "<data>")]
pub async fn send_password_reset(auth: &State<Auth>, data: Json<DataSendPasswordReset>) -> Result<EmptyResponse> {
pub async fn send_password_reset(
auth: &State<Auth>,
data: Json<DataSendPasswordReset>,
) -> Result<EmptyResponse> {
let data = data.into_inner();
// Perform validation on given data.