Dont use sha hash for password reset token (fixes #3491) (#3795)

This commit is contained in:
Nutomic 2023-08-02 19:02:53 +02:00 committed by GitHub
parent 27be1efb74
commit 2d0f77af59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 13 additions and 40 deletions

2
Cargo.lock generated
View file

@ -2678,7 +2678,6 @@ dependencies = [
"serde_json", "serde_json",
"serde_with", "serde_with",
"serial_test", "serial_test",
"sha2",
"strum_macros", "strum_macros",
"task-local-extensions", "task-local-extensions",
"tokio", "tokio",
@ -2711,7 +2710,6 @@ dependencies = [
"serde_json", "serde_json",
"serde_with", "serde_with",
"serial_test", "serial_test",
"sha2",
"strum", "strum",
"strum_macros", "strum_macros",
"tokio", "tokio",

View file

@ -108,7 +108,6 @@ diesel_ltree = "0.3.0"
typed-builder = "0.15.0" typed-builder = "0.15.0"
serial_test = "2.0.0" serial_test = "2.0.0"
tokio = { version = "1.29.1", features = ["full"] } tokio = { version = "1.29.1", features = ["full"] }
sha2 = "0.10.7"
regex = "1.9.0" regex = "1.9.0"
once_cell = "1.18.0" once_cell = "1.18.0"
diesel-derive-newtype = "2.1.0" diesel-derive-newtype = "2.1.0"

View file

@ -342,9 +342,8 @@ pub async fn send_password_reset_email(
let token = uuid::Uuid::new_v4().to_string(); let token = uuid::Uuid::new_v4().to_string();
// Insert the row // Insert the row
let token2 = token.clone();
let local_user_id = user.local_user.id; let local_user_id = user.local_user.id;
PasswordResetRequest::create_token(pool, local_user_id, &token2).await?; PasswordResetRequest::create_token(pool, local_user_id, token.clone()).await?;
let email = &user.local_user.email.clone().expect("email"); let email = &user.local_user.email.clone().expect("email");
let lang = get_interface_language(user); let lang = get_interface_language(user);

View file

@ -33,7 +33,6 @@ http = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }
sha2 = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }

View file

@ -22,7 +22,6 @@ full = [
"bcrypt", "bcrypt",
"lemmy_utils", "lemmy_utils",
"activitypub_federation", "activitypub_federation",
"sha2",
"regex", "regex",
"once_cell", "once_cell",
"serde_json", "serde_json",
@ -60,7 +59,6 @@ diesel-async = { workspace = true, features = [
"postgres", "postgres",
"deadpool", "deadpool",
], optional = true } ], optional = true }
sha2 = { workspace = true, optional = true }
regex = { workspace = true, optional = true } regex = { workspace = true, optional = true }
once_cell = { workspace = true, optional = true } once_cell = { workspace = true, optional = true }
diesel_ltree = { workspace = true, optional = true } diesel_ltree = { workspace = true, optional = true }

View file

@ -1,11 +1,6 @@
use crate::{ use crate::{
newtypes::LocalUserId, newtypes::LocalUserId,
schema::password_reset_request::dsl::{ schema::password_reset_request::dsl::{local_user_id, password_reset_request, published, token},
local_user_id,
password_reset_request,
published,
token_encrypted,
},
source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm}, source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm},
traits::Crud, traits::Crud,
utils::{get_conn, DbPool}, utils::{get_conn, DbPool},
@ -17,7 +12,6 @@ use diesel::{
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use sha2::{Digest, Sha256};
#[async_trait] #[async_trait]
impl Crud for PasswordResetRequest { impl Crud for PasswordResetRequest {
@ -49,29 +43,22 @@ impl PasswordResetRequest {
pub async fn create_token( pub async fn create_token(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
from_local_user_id: LocalUserId, from_local_user_id: LocalUserId,
token: &str, token_: String,
) -> Result<PasswordResetRequest, Error> { ) -> Result<PasswordResetRequest, Error> {
let mut hasher = Sha256::new();
hasher.update(token);
let token_hash: String = bytes_to_hex(hasher.finalize().to_vec());
let form = PasswordResetRequestForm { let form = PasswordResetRequestForm {
local_user_id: from_local_user_id, local_user_id: from_local_user_id,
token_encrypted: token_hash, token: token_,
}; };
Self::create(pool, &form).await Self::create(pool, &form).await
} }
pub async fn read_from_token( pub async fn read_from_token(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
token: &str, token_: &str,
) -> Result<PasswordResetRequest, Error> { ) -> Result<PasswordResetRequest, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let mut hasher = Sha256::new();
hasher.update(token);
let token_hash: String = bytes_to_hex(hasher.finalize().to_vec());
password_reset_request password_reset_request
.filter(token_encrypted.eq(token_hash)) .filter(token.eq(token_))
.filter(published.gt(now - 1.days())) .filter(published.gt(now - 1.days()))
.first::<Self>(conn) .first::<Self>(conn)
.await .await
@ -91,14 +78,6 @@ impl PasswordResetRequest {
} }
} }
fn bytes_to_hex(bytes: Vec<u8>) -> String {
let mut str = String::new();
for byte in bytes {
str = format!("{str}{byte:02x}");
}
str
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)] #![allow(clippy::unwrap_used)]
@ -142,17 +121,16 @@ mod tests {
let inserted_local_user = LocalUser::create(pool, &new_local_user).await.unwrap(); let inserted_local_user = LocalUser::create(pool, &new_local_user).await.unwrap();
let token = "nope"; let token = "nope";
let token_encrypted_ = "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce";
let inserted_password_reset_request = let inserted_password_reset_request =
PasswordResetRequest::create_token(pool, inserted_local_user.id, token) PasswordResetRequest::create_token(pool, inserted_local_user.id, token.to_string())
.await .await
.unwrap(); .unwrap();
let expected_password_reset_request = PasswordResetRequest { let expected_password_reset_request = PasswordResetRequest {
id: inserted_password_reset_request.id, id: inserted_password_reset_request.id,
local_user_id: inserted_local_user.id, local_user_id: inserted_local_user.id,
token_encrypted: token_encrypted_.to_string(), token: token.to_string(),
published: inserted_password_reset_request.published, published: inserted_password_reset_request.published,
}; };

View file

@ -535,7 +535,7 @@ diesel::table! {
diesel::table! { diesel::table! {
password_reset_request (id) { password_reset_request (id) {
id -> Int4, id -> Int4,
token_encrypted -> Text, token -> Text,
published -> Timestamp, published -> Timestamp,
local_user_id -> Int4, local_user_id -> Int4,
} }

View file

@ -7,7 +7,7 @@ use crate::schema::password_reset_request;
#[cfg_attr(feature = "full", diesel(table_name = password_reset_request))] #[cfg_attr(feature = "full", diesel(table_name = password_reset_request))]
pub struct PasswordResetRequest { pub struct PasswordResetRequest {
pub id: i32, pub id: i32,
pub token_encrypted: String, pub token: String,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub local_user_id: LocalUserId, pub local_user_id: LocalUserId,
} }
@ -16,5 +16,5 @@ pub struct PasswordResetRequest {
#[cfg_attr(feature = "full", diesel(table_name = password_reset_request))] #[cfg_attr(feature = "full", diesel(table_name = password_reset_request))]
pub struct PasswordResetRequestForm { pub struct PasswordResetRequestForm {
pub local_user_id: LocalUserId, pub local_user_id: LocalUserId,
pub token_encrypted: String, pub token: String,
} }

View file

@ -0,0 +1 @@
alter table password_reset_request rename column token to token_encrypted;

View file

@ -0,0 +1 @@
alter table password_reset_request rename column token_encrypted to token;