mirror of
https://github.com/LukeMathWalker/zero-to-production.git
synced 2024-11-22 00:31:01 +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"
|
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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
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::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
|
||||||
|
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
1
tests/api/test_user.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
Loading…
Reference in a new issue