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;
+1 -1
View File
@@ -17,7 +17,7 @@ pub struct DataChangeEmail {
}
/// # Change Email
///
///
/// Change the associated account email.
#[openapi(tag = "Account")]
#[patch("/change/email", data = "<data>")]
+1 -1
View File
@@ -17,7 +17,7 @@ pub struct DataChangePassword {
}
/// # Change Password
///
///
/// Change the current account password.
#[openapi(tag = "Account")]
#[patch("/change/password", data = "<data>")]
+8 -10
View File
@@ -20,11 +20,14 @@ pub struct DataCreateAccount {
}
/// # Create Account
///
///
/// 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)
+1 -1
View File
@@ -6,7 +6,7 @@ use crate::entities::*;
use crate::util::Result;
/// # Fetch Account
///
///
/// Fetch account information from the current session.
#[openapi(tag = "Account")]
#[get("/")]
+5 -2
View File
@@ -19,11 +19,14 @@ pub struct DataPasswordReset {
}
/// # Password Reset
///
///
/// 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(
+5 -2
View File
@@ -19,11 +19,14 @@ pub struct DataResendVerification {
}
/// # Resend Verification
///
///
/// 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.
+5 -2
View File
@@ -19,11 +19,14 @@ pub struct DataSendPasswordReset {
}
/// # Send Password Reset
///
///
/// 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.
+1 -1
View File
@@ -9,7 +9,7 @@ use crate::logic::Auth;
use crate::util::{EmptyResponse, Error, Result};
/// # Verify Email
///
///
/// Verify an email address.
#[openapi(tag = "Account")]
#[post("/verify/<code>")]
+1 -1
View File
@@ -15,7 +15,7 @@ pub struct DataEditSession {
}
/// # Edit Session
///
///
/// Edit current session information.
#[openapi(tag = "Session")]
#[patch("/<id>", data = "<data>")]
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::logic::Auth;
use crate::util::{Error, Result};
/// # Fetch Sessions
///
///
/// Fetch all sessions associated with this account.
#[openapi(tag = "Session")]
#[get("/all")]
+1 -1
View File
@@ -40,7 +40,7 @@ pub enum ResponseLogin {
}
/// # Login
///
///
/// Login to an account.
#[openapi(tag = "Session")]
#[post("/login", data = "<data>")]
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::logic::Auth;
use crate::util::{EmptyResponse, Error, Result};
/// # Logout
///
///
/// Delete current session.
#[openapi(tag = "Session")]
#[post("/logout")]
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::logic::Auth;
use crate::util::{EmptyResponse, Error, Result};
/// # Revoke Session
///
///
/// Delete a specific active session.
#[openapi(tag = "Session")]
#[delete("/<id>")]
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::logic::Auth;
use crate::util::{EmptyResponse, Error, Result};
/// # Delete All Sessions
///
///
/// Delete all active sessions, optionally including current one.
#[openapi(tag = "Session")]
#[delete("/all?<revoke_self>")]