diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..94d7924 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "rust-analyzer.checkOnSave.command": "clippy" +} \ No newline at end of file diff --git a/src/entities/account.rs b/src/entities/account.rs index 80febca..9c315a9 100644 --- a/src/entities/account.rs +++ b/src/entities/account.rs @@ -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::*; diff --git a/src/entities/session.rs b/src/entities/session.rs index 88a6591..a887752 100644 --- a/src/entities/session.rs +++ b/src/entities/session.rs @@ -49,7 +49,7 @@ impl Session { .await .map_err(|_| Error::DatabaseError { operation: "find_one", - with: "session" + with: "session", })? .ok_or_else(|| Error::InvalidCredentials) } diff --git a/src/logic/auth.rs b/src/logic/auth.rs index 9e440a2..f2a317e 100644 --- a/src/logic/auth.rs +++ b/src/logic/auth.rs @@ -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(|_| ()) diff --git a/src/util.rs b/src/util.rs index 29f9c02..f6a916a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -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; diff --git a/src/web/account/change_email.rs b/src/web/account/change_email.rs index fc73fe1..cc78e90 100644 --- a/src/web/account/change_email.rs +++ b/src/web/account/change_email.rs @@ -17,7 +17,7 @@ pub struct DataChangeEmail { } /// # Change Email -/// +/// /// Change the associated account email. #[openapi(tag = "Account")] #[patch("/change/email", data = "")] diff --git a/src/web/account/change_password.rs b/src/web/account/change_password.rs index 44e1f98..55cde40 100644 --- a/src/web/account/change_password.rs +++ b/src/web/account/change_password.rs @@ -17,7 +17,7 @@ pub struct DataChangePassword { } /// # Change Password -/// +/// /// Change the current account password. #[openapi(tag = "Account")] #[patch("/change/password", data = "")] diff --git a/src/web/account/create_account.rs b/src/web/account/create_account.rs index ae5553d..e665feb 100644 --- a/src/web/account/create_account.rs +++ b/src/web/account/create_account.rs @@ -20,11 +20,14 @@ pub struct DataCreateAccount { } /// # Create Account -/// +/// /// Create a new account. #[openapi(tag = "Account")] #[post("/create", data = "")] -pub async fn create_account(auth: &State, data: Json) -> Result { +pub async fn create_account( + auth: &State, + data: Json, +) -> Result { let data = data.into_inner(); // Perform validation on given data. @@ -36,16 +39,11 @@ pub async fn create_account(auth: &State, data: Json) - 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) diff --git a/src/web/account/fetch_account.rs b/src/web/account/fetch_account.rs index ff2cbf4..8c4a4b8 100644 --- a/src/web/account/fetch_account.rs +++ b/src/web/account/fetch_account.rs @@ -6,7 +6,7 @@ use crate::entities::*; use crate::util::Result; /// # Fetch Account -/// +/// /// Fetch account information from the current session. #[openapi(tag = "Account")] #[get("/")] diff --git a/src/web/account/password_reset.rs b/src/web/account/password_reset.rs index 78de65f..4dc9888 100644 --- a/src/web/account/password_reset.rs +++ b/src/web/account/password_reset.rs @@ -19,11 +19,14 @@ pub struct DataPasswordReset { } /// # Password Reset -/// +/// /// Confirm password reset and change the password. #[openapi(tag = "Account")] #[patch("/reset_password", data = "")] -pub async fn password_reset(auth: &State, data: Json) -> Result { +pub async fn password_reset( + auth: &State, + data: Json, +) -> Result { let data = data.into_inner(); let mut account = Account::find_one( diff --git a/src/web/account/resend_verification.rs b/src/web/account/resend_verification.rs index 859d51f..0e98844 100644 --- a/src/web/account/resend_verification.rs +++ b/src/web/account/resend_verification.rs @@ -19,11 +19,14 @@ pub struct DataResendVerification { } /// # Resend Verification -/// +/// /// Resend account creation verification email. #[openapi(tag = "Account")] #[post("/reverify", data = "")] -pub async fn resend_verification(auth: &State, data: Json) -> Result { +pub async fn resend_verification( + auth: &State, + data: Json, +) -> Result { let data = data.into_inner(); // Perform validation on given data. diff --git a/src/web/account/send_password_reset.rs b/src/web/account/send_password_reset.rs index 2d6ebfd..d5fa254 100644 --- a/src/web/account/send_password_reset.rs +++ b/src/web/account/send_password_reset.rs @@ -19,11 +19,14 @@ pub struct DataSendPasswordReset { } /// # Send Password Reset -/// +/// /// Send an email to reset account password. #[openapi(tag = "Account")] #[post("/reset_password", data = "")] -pub async fn send_password_reset(auth: &State, data: Json) -> Result { +pub async fn send_password_reset( + auth: &State, + data: Json, +) -> Result { let data = data.into_inner(); // Perform validation on given data. diff --git a/src/web/account/verify_email.rs b/src/web/account/verify_email.rs index 07c7701..86848c8 100644 --- a/src/web/account/verify_email.rs +++ b/src/web/account/verify_email.rs @@ -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/")] diff --git a/src/web/session/edit.rs b/src/web/session/edit.rs index 52fc5ce..5613543 100644 --- a/src/web/session/edit.rs +++ b/src/web/session/edit.rs @@ -15,7 +15,7 @@ pub struct DataEditSession { } /// # Edit Session -/// +/// /// Edit current session information. #[openapi(tag = "Session")] #[patch("/", data = "")] diff --git a/src/web/session/fetch_all.rs b/src/web/session/fetch_all.rs index 8f500c2..e5938b9 100644 --- a/src/web/session/fetch_all.rs +++ b/src/web/session/fetch_all.rs @@ -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")] diff --git a/src/web/session/login.rs b/src/web/session/login.rs index 13008b0..8f30a7b 100644 --- a/src/web/session/login.rs +++ b/src/web/session/login.rs @@ -40,7 +40,7 @@ pub enum ResponseLogin { } /// # Login -/// +/// /// Login to an account. #[openapi(tag = "Session")] #[post("/login", data = "")] diff --git a/src/web/session/logout.rs b/src/web/session/logout.rs index 9258d96..4e62e0f 100644 --- a/src/web/session/logout.rs +++ b/src/web/session/logout.rs @@ -7,7 +7,7 @@ use crate::logic::Auth; use crate::util::{EmptyResponse, Error, Result}; /// # Logout -/// +/// /// Delete current session. #[openapi(tag = "Session")] #[post("/logout")] diff --git a/src/web/session/revoke.rs b/src/web/session/revoke.rs index 14781b4..4752834 100644 --- a/src/web/session/revoke.rs +++ b/src/web/session/revoke.rs @@ -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("/")] diff --git a/src/web/session/revoke_all.rs b/src/web/session/revoke_all.rs index 5457fc9..2c61c7b 100644 --- a/src/web/session/revoke_all.rs +++ b/src/web/session/revoke_all.rs @@ -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?")]