diff --git a/Cargo.lock b/Cargo.lock index 6be611e..d1f1349 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,18 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "bcrypt" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2cab630912253fb9dc92c0e2fabd0a7b51f5a5a4007177cfa31e517015b7204" +dependencies = [ + "base64 0.12.3", + "blowfish", + "byteorder", + "getrandom", +] + [[package]] name = "bigdecimal" version = "0.1.2" @@ -496,7 +508,16 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "block-cipher" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f337a3e6da609650eb74e02bc9fac7b735049f7623ab12f2e4c719316fcc7e80" +dependencies = [ + "generic-array 0.14.4", ] [[package]] @@ -508,6 +529,17 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "blowfish" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f06850ba969bc59388b2cc0a4f186fc6d9d37208863b15b84ae3866ac90ac06" +dependencies = [ + "block-cipher", + "byteorder", + "opaque-debug 0.3.0", +] + [[package]] name = "brotli-sys" version = "0.3.2" @@ -621,6 +653,7 @@ dependencies = [ "actix-rt", "actix-web", "actix-web-actors", + "bcrypt", "bytes", "chrono", "dashmap", @@ -802,7 +835,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array", + "generic-array 0.12.3", ] [[package]] @@ -1121,6 +1154,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -1845,6 +1888,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.29" @@ -2361,7 +2410,7 @@ dependencies = [ "block-buffer", "digest", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7c6bd73..a1fcedd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,5 @@ pdf = "0.6.3" regex = "1.4.2" dashmap = "3.11.10" reqwest = { version = "0.10.6", features = ["json", "blocking"] } -webrtc-sdp = "0.3.6" \ No newline at end of file +webrtc-sdp = "0.3.6" +bcrypt = "0.8.0" \ No newline at end of file diff --git a/src/helpers/account_helper.rs b/src/helpers/account_helper.rs index 6a568c1..94a3079 100644 --- a/src/helpers/account_helper.rs +++ b/src/helpers/account_helper.rs @@ -1,20 +1,22 @@ +use bcrypt::{DEFAULT_COST, hash_with_result, verify}; + use crate::constants::{PASSWORD_RESET_TOKEN_LENGTH, PASSWORD_RESET_TOKEN_LIFETIME}; use crate::constants::database_tables_names::{USER_ACCESS_TOKENS_TABLE, USERS_TABLE}; use crate::controllers::user_ws_controller; use crate::data::account_export::AccountExport; use crate::data::api_client::APIClient; -use crate::data::error::{ExecError, ResultBoxError}; +use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::general_settings::GeneralSettings; use crate::data::lang_settings::LangSettings; use crate::data::new_account::NewAccount; use crate::data::security_settings::SecuritySettings; -use crate::data::user::{AccountImageVisibility, UserID, UserPageStatus}; +use crate::data::user::{AccountImageVisibility, User, UserID, UserPageStatus}; use crate::data::user_token::UserAccessToken; use crate::helpers::{comments_helper, conversations_helper, custom_emojies_helper, database, events_helper, friends_helper, groups_helper, likes_helper, movies_helper, notifications_helper, posts_helper, survey_helper, user_helper}; use crate::helpers::database::{DeleteQuery, InsertQuery, QueryInfo}; use crate::helpers::events_helper::Event; use crate::helpers::likes_helper::LikeType; -use crate::utils::crypt_utils::{crypt_pass, rand_str}; +use crate::utils::crypt_utils::{legacy_crypt_pass, rand_str}; use crate::utils::date_utils::{mysql_date, time}; use crate::utils::user_data_utils::user_data_path; @@ -29,7 +31,7 @@ pub fn create(new_account: &NewAccount) -> ResultBoxError { .add_str("prenom", &new_account.last_name) .add_str("date_creation", &mysql_date()) .add_str("mail", &new_account.email) - .add_str("password", &crypt_pass(&new_account.password)?) + .add_str("password", &hash_password(&new_account.password)?) .insert_drop_result() } @@ -41,8 +43,7 @@ pub fn login_user(email: &str, password: &str, client: &APIClient) -> ResultBoxE let user = user_helper::find_user_by_email(email)?; // Validate user password - let password = crypt_pass(password)?; - if !user.password.eq(&password) { + if !validate_password(&user, password)? { return Err(ExecError::boxed_new("The user gave an invalid password!")); } @@ -161,20 +162,16 @@ pub fn get_user_id_from_password_reset_token(token: &str) -> ResultBoxError ResultBoxError { - let crypt_pass = crypt_pass(password)?; + let user = user_helper::find_user_by_id(user_id)?; - database::QueryInfo::new(USERS_TABLE) - .cond_user_id("ID", user_id) - .cond("password", &crypt_pass) - .exec_count() - .map(|r| r > 0) + validate_password(&user, password) } /// Change the password of a user -pub fn change_password(user_id: &UserID, new_password: &String) -> ResultBoxError { +pub fn change_password(user_id: &UserID, new_password: &str) -> ResultBoxError { database::UpdateInfo::new(USERS_TABLE) .cond_user_id("ID", user_id) - .set_str("password", &crypt_pass(new_password)?) + .set_str("password", &hash_password(new_password)?) .exec() } @@ -360,4 +357,34 @@ pub fn delete(user_id: &UserID) -> ResultBoxError { .exec()?; Ok(()) +} + +/// Hash the password to store it inside the database +fn hash_password(pass: &str) -> Res { + Ok(hash_with_result(pass, DEFAULT_COST)?.to_string()) +} + +/// Validate user password. +/// +/// If the password is encoded using the legacy method, it is automatically upgraded in case of +/// success +fn validate_password(user: &User, password: &str) -> Res { + + // We check if the password use the new storage mechanism + if user.password.starts_with("$") { + return Ok(verify(password, &user.password)?); + } + + // We need to upgrade the password + let crypt_pass = legacy_crypt_pass(password)?; + + // If the password is not valid + if !user.password.eq(&crypt_pass) { + return Ok(false); + } + + // Upgrade the password + change_password(&user.id, password)?; + + Ok(true) } \ No newline at end of file diff --git a/src/utils/crypt_utils.rs b/src/utils/crypt_utils.rs index 92ea1f2..070a6fa 100644 --- a/src/utils/crypt_utils.rs +++ b/src/utils/crypt_utils.rs @@ -22,18 +22,17 @@ pub fn sha1_str(input: &str) -> String { /// One-way user password crypt /// -/// This method will have to be replaced by a stronger algorithm once this implementation is -/// completely built. +/// This method is just transitional /// /// It currently depends on PHP /// /// ``` -/// use comunic_server::utils::crypt_utils::crypt_pass; +/// use comunic_server::utils::crypt_utils::legacy_crypt_pass; /// -/// let res = crypt_pass("secret").unwrap(); +/// let res = legacy_crypt_pass("secret").unwrap(); /// assert_eq!(res, "e5GUe5kdeUMGs"); /// ``` -pub fn crypt_pass(pass: &str) -> ResultBoxError { +pub fn legacy_crypt_pass(pass: &str) -> ResultBoxError { let hash = sha1_str(pass); let result = std::process::Command::new("/usr/bin/php")