mirror of
https://github.com/LukeMathWalker/zero-to-production.git
synced 2024-11-14 12:41:03 +00:00
Password hashing using SHA3-256.
This commit is contained in:
parent
f78f25c358
commit
312ee4aa89
7 changed files with 71 additions and 28 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
@ -329,9 +329,16 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||
|
||||
[[package]]
|
||||
name = "brotli-sys"
|
||||
version = "0.3.2"
|
||||
|
@ -1087,6 +1094,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
|
@ -1812,6 +1825,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.1"
|
||||
|
@ -2690,7 +2715,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
|
|
@ -32,10 +32,10 @@ serde-aux = "1.0.1"
|
|||
unicode-segmentation = "1.7.1"
|
||||
validator = "0.12.0"
|
||||
rand = { version = "0.8", features=["std_rng"] }
|
||||
sha2 = { version = "0.9" }
|
||||
tracing-actix-web = "0.4.0-beta.8"
|
||||
anyhow = "1.0.40"
|
||||
base64 = "0.13.0"
|
||||
sha3 = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
once_cell = "1.7.2"
|
||||
|
|
1
migrations/20210822143736_rename_password_column.sql
Normal file
1
migrations/20210822143736_rename_password_column.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users RENAME password TO password_hash;
|
|
@ -4,6 +4,7 @@ use crate::routes::error_chain_fmt;
|
|||
use actix_web::http::{HeaderMap, HeaderValue, StatusCode};
|
||||
use actix_web::{web, HttpResponse, ResponseError};
|
||||
use anyhow::Context;
|
||||
use sha3::Digest;
|
||||
use sqlx::PgPool;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
|
@ -87,14 +88,16 @@ async fn validate_credentials(
|
|||
credentials: Credentials,
|
||||
pool: &PgPool,
|
||||
) -> 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!(
|
||||
r#"
|
||||
SELECT user_id
|
||||
FROM users
|
||||
WHERE username = $1 AND password = $2
|
||||
WHERE username = $1 AND password_hash = $2
|
||||
"#,
|
||||
credentials.username,
|
||||
credentials.password
|
||||
password_hash
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use sha3::Digest;
|
||||
use sqlx::{Connection, Executor, PgConnection, PgPool};
|
||||
use uuid::Uuid;
|
||||
use wiremock::MockServer;
|
||||
|
@ -24,6 +25,7 @@ pub struct TestApp {
|
|||
pub port: u16,
|
||||
pub db_pool: PgPool,
|
||||
pub email_server: MockServer,
|
||||
pub test_user: TestUser,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let (username, password) = self.test_user().await;
|
||||
reqwest::Client::new()
|
||||
.post(&format!("{}/newsletters", &self.address))
|
||||
.basic_auth(username, Some(password))
|
||||
.basic_auth(&self.test_user.username, Some(&self.test_user.password))
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
|
@ -77,14 +78,6 @@ impl TestApp {
|
|||
let plain_text = get_link(&body["TextBody"].as_str().unwrap());
|
||||
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 {
|
||||
|
@ -122,26 +115,14 @@ pub async fn spawn_app() -> TestApp {
|
|||
.await
|
||||
.expect("Failed to connect to the database"),
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
// Create database
|
||||
let mut connection = PgConnection::connect_with(&config.without_db())
|
||||
|
@ -163,3 +144,34 @@ async fn configure_database(config: &DatabaseSettings) -> PgPool {
|
|||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,3 +3,4 @@ mod helpers;
|
|||
mod newsletter;
|
||||
mod subscriptions;
|
||||
mod subscriptions_confirm;
|
||||
mod test_user;
|
||||
|
|
1
tests/api/test_user.rs
Normal file
1
tests/api/test_user.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
Loading…
Reference in a new issue