From 967d67eabcf06f96b8f87baaf0f933cb7be865a7 Mon Sep 17 00:00:00 2001 From: Rajdeep Sengupta Date: Sun, 12 May 2024 13:19:45 +0530 Subject: [PATCH] added blocktime --- src/core/auth.rs | 23 ++++- src/core/user.rs | 144 +++++++++++++++++++++++++++++++ src/errors.rs | 12 +++ src/utils/refresh_token_utils.rs | 50 ----------- src/utils/session_utils.rs | 4 +- 5 files changed, 178 insertions(+), 55 deletions(-) delete mode 100644 src/utils/refresh_token_utils.rs diff --git a/src/core/auth.rs b/src/core/auth.rs index cdef0b3..7305a16 100644 --- a/src/core/auth.rs +++ b/src/core/auth.rs @@ -1,4 +1,4 @@ -use bson::doc; +use bson::{doc, DateTime}; use mongodb::{Client, Collection}; use crate::{ @@ -91,6 +91,19 @@ impl Auth { Err(e) => return Err(e), }; + // check if the user has a blocked_until date greater than the current date check in milliseconds from DateTime type + match user.blocked_until { + Some(blocked_until_time) => { + let current_time = DateTime::now().timestamp_millis(); + if blocked_until_time.timestamp_millis() > current_time { + return Err(Error::UserBlocked { + message: "User is blocked".to_string(), + }); + } + } + None => {} + } + let dek_data = match Dek::get(&mongo_client, &user.uid).await { Ok(dek_data) => dek_data, Err(e) => return Err(e), @@ -122,8 +135,12 @@ impl Auth { Ok(res) } else { - Err(Error::UserNotFound { - message: "User not found".to_string(), + match User::increase_failed_login_attempt(&mongo_client, &user.email).await { + Ok(_) => {} + Err(e) => return Err(e), + } + Err(Error::WrongCredentials { + message: "Invalid credentials".to_string(), }) } } diff --git a/src/core/user.rs b/src/core/user.rs index 02ec4ec..acfcf8a 100644 --- a/src/core/user.rs +++ b/src/core/user.rs @@ -25,6 +25,8 @@ pub struct User { pub password: String, pub email_verified: bool, pub is_active: bool, + pub failed_login_attempts: i32, + pub blocked_until: Option, pub created_at: Option, pub updated_at: Option, } @@ -40,6 +42,8 @@ impl User { password: password.to_string(), email_verified: false, is_active: true, + failed_login_attempts: 0, + blocked_until: None, created_at: Some(DateTime::now()), updated_at: Some(DateTime::now()), } @@ -288,6 +292,146 @@ impl User { } } + pub async fn increase_failed_login_attempt( + mongo_client: &Client, + email: &str, + ) -> Result { + let db = mongo_client.database("test"); + let collection: Collection = db.collection("users"); + let dek_data = match Dek::get(&mongo_client, email).await { + Ok(dek) => dek, + Err(e) => { + return Err(e); + } + }; + + // find the user in the users collection using the uid + match collection + .update_one( + doc! { + "uid": Encryption::encrypt_data(&dek_data.uid, &dek_data.dek), + }, + doc! { + "$inc": { + "failed_login_attempts": 1 + }, + "$set": { + "updated_at": DateTime::now(), + } + }, + None, + ) + .await + { + Ok(cursor) => { + let modified_count = cursor.modified_count; + + // Return Error if User is not there + if modified_count == 0 { + // send back a 404 to + return Err(Error::UserNotFound { + message: "User not found".to_string(), + }); + } + + // check if the failed login attempts is greater than 5 then add_block_user_until 180 seconds and if it is 10 then for 10 minutes if its 15 then 1hr + let user = match User::get_from_email(&mongo_client, email).await { + Ok(user) => user, + Err(e) => { + return Err(e); + } + }; + + if user.failed_login_attempts == 5 { + let blocked_until = DateTime::now().timestamp_millis() + 180000; + match collection + .update_one( + doc! { + "uid": Encryption::encrypt_data(&dek_data.uid, &dek_data.dek), + }, + doc! { + "$set": { + "blocked_until": DateTime::from_millis(blocked_until), + "updated_at": DateTime::now(), + } + }, + None, + ) + .await + { + Ok(_) => { + return Ok(5); + } + Err(_) => { + return Err(Error::ServerError { + message: "Failed to update User".to_string(), + }); + } + } + } else if user.failed_login_attempts == 10 { + let blocked_until = DateTime::now().timestamp_millis() + 600000; + match collection + .update_one( + doc! { + "uid": Encryption::encrypt_data(&dek_data.uid, &dek_data.dek), + }, + doc! { + "$set": { + "blocked_until": DateTime::from_millis(blocked_until), + "updated_at": DateTime::now(), + } + }, + None, + ) + .await + { + Ok(_) => { + return Ok(10); + } + Err(_) => { + return Err(Error::ServerError { + message: "Failed to update User".to_string(), + }); + } + } + } else if user.failed_login_attempts == 15 { + let blocked_until = DateTime::now().timestamp_millis() + 3600000; + match collection + .update_one( + doc! { + "uid": Encryption::encrypt_data(&dek_data.uid, &dek_data.dek), + }, + doc! { + "$set": { + "blocked_until": DateTime::from_millis(blocked_until), + "updated_at": DateTime::now(), + } + }, + None, + ) + .await + { + Ok(_) => { + return Ok(15); + } + Err(_) => { + return Err(Error::ServerError { + message: "Failed to update User".to_string(), + }); + } + } + } else { + return Ok(user.failed_login_attempts); + } + } + Err(_) => { + return Err(Error::ServerError { + message: "Failed to update User".to_string(), + }) + } + } + } + pub async fn change_password(mongo_client: &Client, email: &str, old_password: &str, new_password: &str) -> Result { let db = mongo_client.database("test"); let collection: Collection = db.collection("users"); diff --git a/src/errors.rs b/src/errors.rs index 18ad2e3..e45a28c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -15,6 +15,8 @@ pub enum Error { // -- User Errors UserNotFound { message: String }, UserAlreadyExists { message: String }, + WrongCredentials { message: String }, + UserBlocked { message: String }, // -- Password Errors InvalidPassword { message: String }, @@ -75,6 +77,14 @@ impl Error { (StatusCode::UNAUTHORIZED, ClientError::INVALID_PASSWORD) } + Self::WrongCredentials { message: _ } => { + (StatusCode::UNAUTHORIZED, ClientError::WRONG_CREDENTIALS) + } + + Self::UserBlocked { message: _ } => { + (StatusCode::UNAUTHORIZED, ClientError::USER_BLOCKED) + } + Self::KeyNotFound { message: _ } => ( StatusCode::INTERNAL_SERVER_ERROR, ClientError::SERVICE_ERROR, @@ -149,6 +159,8 @@ pub enum ClientError { SERVICE_ERROR, USER_ALREADY_EXISTS, INVALID_PASSWORD, + WRONG_CREDENTIALS, + USER_BLOCKED, RESET_PASSWORD_LINK_EXPIRED, INVALID_TOKEN, SIGNATURE_VERIFICATION_ERROR, diff --git a/src/utils/refresh_token_utils.rs b/src/utils/refresh_token_utils.rs deleted file mode 100644 index 0d56096..0000000 --- a/src/utils/refresh_token_utils.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct RefreshToken { - uid: String, - iss: String, - iat: usize, - exp: usize, - scope: String, - data: Option>, -} - -impl RefreshToken { - pub fn new(uid: &str, scope: &str) -> Self { - let server_url = - std::env::var("SERVER_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()); - Self { - uid: uid.to_string(), - iss: server_url, - iat: chrono::Utc::now().timestamp() as usize, - exp: chrono::Utc::now().timestamp() as usize + (3600 * 24 * 30), // 30 days - scope: scope.to_string(), - data: None, - } - } - - pub fn sign(&self) -> String { - let private_key = load_private_key().unwrap(); - let rsa = Rsa::private_key_from_pem(&private_key).unwrap(); - let private_key = PKey::from_rsa(rsa).unwrap(); - let mut claims = ClaimsSet:: { - registered: RegisteredClaims { - issuer: Some(From::from(self.iss.clone())), - subject: Some(From::from(self.uid.clone())), - issued_at: Some(From::from(self.iat)), - expiration: Some(From::from(self.exp)), - not_before: None, - ..Default::default() - }, - private: self.clone(), - }; - let header = Header { - algorithm: Algorithm::RS256, - ..Default::default() - }; - encode(&header, &claims, &private_key).unwrap() - } -} \ No newline at end of file diff --git a/src/utils/session_utils.rs b/src/utils/session_utils.rs index e96a020..4f68b43 100644 --- a/src/utils/session_utils.rs +++ b/src/utils/session_utils.rs @@ -17,7 +17,7 @@ pub struct IDToken { pub data: Option>, } -fn load_private_key() -> Result, Error> { +pub fn load_private_key() -> Result, Error> { let private_key_content = fs::read("private_key.pem"); let rsa = Rsa::private_key_from_pem(&private_key_content.unwrap()).unwrap(); let private_key = PKey::from_rsa(rsa).unwrap(); @@ -32,7 +32,7 @@ fn load_private_key() -> Result, Error> { } // Load public key from the private key -fn load_public_key() -> Result, Error> { +pub fn load_public_key() -> Result, Error> { let private_key_content = fs::read("private_key.pem"); let rsa = Rsa::private_key_from_pem(&private_key_content.unwrap()).unwrap(); let private_key = PKey::from_rsa(rsa).unwrap();