Password hashing using SHA3-256.

This commit is contained in:
LukeMathWalker 2021-08-22 16:54:41 +01:00
parent f78f25c358
commit 312ee4aa89
7 changed files with 71 additions and 28 deletions

27
Cargo.lock generated
View file

@ -329,9 +329,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [ dependencies = [
"block-padding",
"generic-array", "generic-array",
] ]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]] [[package]]
name = "brotli-sys" name = "brotli-sys"
version = "0.3.2" version = "0.3.2"
@ -1087,6 +1094,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "keccak"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
[[package]] [[package]]
name = "language-tags" name = "language-tags"
version = "0.3.2" version = "0.3.2"
@ -1812,6 +1825,18 @@ dependencies = [
"opaque-debug", "opaque-debug",
] ]
[[package]]
name = "sha3"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
dependencies = [
"block-buffer",
"digest",
"keccak",
"opaque-debug",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.1" version = "0.1.1"
@ -2690,7 +2715,7 @@ dependencies = [
"serde", "serde",
"serde-aux", "serde-aux",
"serde_json", "serde_json",
"sha2", "sha3",
"sqlx", "sqlx",
"thiserror", "thiserror",
"tokio", "tokio",

View file

@ -32,10 +32,10 @@ serde-aux = "1.0.1"
unicode-segmentation = "1.7.1" unicode-segmentation = "1.7.1"
validator = "0.12.0" validator = "0.12.0"
rand = { version = "0.8", features=["std_rng"] } rand = { version = "0.8", features=["std_rng"] }
sha2 = { version = "0.9" }
tracing-actix-web = "0.4.0-beta.8" tracing-actix-web = "0.4.0-beta.8"
anyhow = "1.0.40" anyhow = "1.0.40"
base64 = "0.13.0" base64 = "0.13.0"
sha3 = "0.9"
[dev-dependencies] [dev-dependencies]
once_cell = "1.7.2" once_cell = "1.7.2"

View file

@ -0,0 +1 @@
ALTER TABLE users RENAME password TO password_hash;

View file

@ -4,6 +4,7 @@ use crate::routes::error_chain_fmt;
use actix_web::http::{HeaderMap, HeaderValue, StatusCode}; use actix_web::http::{HeaderMap, HeaderValue, StatusCode};
use actix_web::{web, HttpResponse, ResponseError}; use actix_web::{web, HttpResponse, ResponseError};
use anyhow::Context; use anyhow::Context;
use sha3::Digest;
use sqlx::PgPool; use sqlx::PgPool;
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
@ -87,14 +88,16 @@ async fn validate_credentials(
credentials: Credentials, credentials: Credentials,
pool: &PgPool, pool: &PgPool,
) -> Result<uuid::Uuid, PublishError> { ) -> Result<uuid::Uuid, PublishError> {
let password_hash = sha3::Sha3_256::digest(credentials.password.as_bytes());
let password_hash = format!("{:x}", password_hash);
let user_id: Option<_> = sqlx::query!( let user_id: Option<_> = sqlx::query!(
r#" r#"
SELECT user_id SELECT user_id
FROM users FROM users
WHERE username = $1 AND password = $2 WHERE username = $1 AND password_hash = $2
"#, "#,
credentials.username, credentials.username,
credentials.password password_hash
) )
.fetch_optional(pool) .fetch_optional(pool)
.await .await

View file

@ -1,4 +1,5 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use sha3::Digest;
use sqlx::{Connection, Executor, PgConnection, PgPool}; use sqlx::{Connection, Executor, PgConnection, PgPool};
use uuid::Uuid; use uuid::Uuid;
use wiremock::MockServer; use wiremock::MockServer;
@ -24,6 +25,7 @@ pub struct TestApp {
pub port: u16, pub port: u16,
pub db_pool: PgPool, pub db_pool: PgPool,
pub email_server: MockServer, pub email_server: MockServer,
pub test_user: TestUser,
} }
/// Confirmation links embedded in the request to the email API. /// Confirmation links embedded in the request to the email API.
@ -44,10 +46,9 @@ impl TestApp {
} }
pub async fn post_newsletters(&self, body: serde_json::Value) -> reqwest::Response { pub async fn post_newsletters(&self, body: serde_json::Value) -> reqwest::Response {
let (username, password) = self.test_user().await;
reqwest::Client::new() reqwest::Client::new()
.post(&format!("{}/newsletters", &self.address)) .post(&format!("{}/newsletters", &self.address))
.basic_auth(username, Some(password)) .basic_auth(&self.test_user.username, Some(&self.test_user.password))
.json(&body) .json(&body)
.send() .send()
.await .await
@ -77,14 +78,6 @@ impl TestApp {
let plain_text = get_link(&body["TextBody"].as_str().unwrap()); let plain_text = get_link(&body["TextBody"].as_str().unwrap());
ConfirmationLinks { html, plain_text } ConfirmationLinks { html, plain_text }
} }
pub async fn test_user(&self) -> (String, String) {
let row = sqlx::query!("SELECT username, password FROM users LIMIT 1",)
.fetch_one(&self.db_pool)
.await
.expect("Failed to create test users.");
(row.username, row.password)
}
} }
pub async fn spawn_app() -> TestApp { pub async fn spawn_app() -> TestApp {
@ -122,26 +115,14 @@ pub async fn spawn_app() -> TestApp {
.await .await
.expect("Failed to connect to the database"), .expect("Failed to connect to the database"),
email_server, email_server,
test_user: TestUser::generate(),
}; };
add_test_user(&test_app.db_pool).await; test_app.test_user.store(&test_app.db_pool).await;
test_app test_app
} }
async fn add_test_user(pool: &PgPool) {
sqlx::query!(
"INSERT INTO users (user_id, username, password)
VALUES ($1, $2, $3)",
Uuid::new_v4(),
Uuid::new_v4().to_string(),
Uuid::new_v4().to_string(),
)
.execute(pool)
.await
.expect("Failed to create test users.");
}
async fn configure_database(config: &DatabaseSettings) -> PgPool { async fn configure_database(config: &DatabaseSettings) -> PgPool {
// Create database // Create database
let mut connection = PgConnection::connect_with(&config.without_db()) let mut connection = PgConnection::connect_with(&config.without_db())
@ -163,3 +144,34 @@ async fn configure_database(config: &DatabaseSettings) -> PgPool {
connection_pool connection_pool
} }
pub struct TestUser {
user_id: Uuid,
username: String,
password: String,
}
impl TestUser {
pub fn generate() -> Self {
Self {
user_id: Uuid::new_v4(),
username: Uuid::new_v4().to_string(),
password: Uuid::new_v4().to_string(),
}
}
async fn store(&self, pool: &PgPool) {
let password_hash = sha3::Sha3_256::digest(self.password.as_bytes());
let password_hash = format!("{:x}", password_hash);
sqlx::query!(
"INSERT INTO users (user_id, username, password_hash)
VALUES ($1, $2, $3)",
self.user_id,
self.username,
password_hash,
)
.execute(pool)
.await
.expect("Failed to store test user.");
}
}

View file

@ -3,3 +3,4 @@ mod helpers;
mod newsletter; mod newsletter;
mod subscriptions; mod subscriptions;
mod subscriptions_confirm; mod subscriptions_confirm;
mod test_user;

1
tests/api/test_user.rs Normal file
View file

@ -0,0 +1 @@