mirror of
https://github.com/LukeMathWalker/zero-to-production.git
synced 2024-11-25 10:11:00 +00:00
Implement password hashing using Argon2, with PHC string format as storage encoding.
This commit is contained in:
parent
312ee4aa89
commit
7d515138d5
5 changed files with 78 additions and 13 deletions
52
Cargo.lock
generated
52
Cargo.lock
generated
|
@ -250,6 +250,17 @@ version = "1.0.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
|
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d805bb12b532be9ce066df7913311f43716b41d8d780e9322113e8a6ae7c41ab"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"password-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -305,6 +316,12 @@ version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -323,6 +340,17 @@ dependencies = [
|
||||||
"wyz",
|
"wyz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-mac 0.8.0",
|
||||||
|
"digest",
|
||||||
|
"opaque-debug",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -537,6 +565,16 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-mac"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-mac"
|
name = "crypto-mac"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -930,7 +968,7 @@ version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
|
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-mac",
|
"crypto-mac 0.10.0",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1380,6 +1418,17 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ad7268ef9bc463fddde8361d358fbfae1aeeb1fb62eca111cd8c763bf1c5891"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core 0.6.2",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
@ -2701,6 +2750,7 @@ dependencies = [
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"argon2",
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"claim",
|
"claim",
|
||||||
|
|
|
@ -36,6 +36,7 @@ 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"
|
sha3 = "0.9"
|
||||||
|
argon2 = { version = "0.3", features = ["std"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.7.2"
|
||||||
|
|
1
migrations/20210829175741_add_salt_to_users.sql
Normal file
1
migrations/20210829175741_add_salt_to_users.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE users ADD COLUMN salt TEXT NOT NULL;
|
1
migrations/20210829200701_remove_salt_from_users.sql
Normal file
1
migrations/20210829200701_remove_salt_from_users.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE users DROP COLUMN salt;
|
|
@ -4,7 +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 argon2::{Argon2, PasswordHash, PasswordVerifier};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
|
@ -88,26 +88,38 @@ 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 row: Option<_> = sqlx::query!(
|
||||||
let password_hash = format!("{:x}", password_hash);
|
|
||||||
let user_id: Option<_> = sqlx::query!(
|
|
||||||
r#"
|
r#"
|
||||||
SELECT user_id
|
SELECT user_id, password_hash
|
||||||
FROM users
|
FROM users
|
||||||
WHERE username = $1 AND password_hash = $2
|
WHERE username = $1
|
||||||
"#,
|
"#,
|
||||||
credentials.username,
|
credentials.username,
|
||||||
password_hash
|
|
||||||
)
|
)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await
|
.await
|
||||||
.context("Failed to performed a query to validate auth credentials.")
|
.context("Failed to performed a query to retrieve stored credentials.")
|
||||||
.map_err(PublishError::UnexpectedError)?;
|
.map_err(PublishError::UnexpectedError)?;
|
||||||
|
|
||||||
user_id
|
let (expected_password_hash, user_id) = match row {
|
||||||
.map(|row| row.user_id)
|
Some(row) => (row.password_hash, row.user_id),
|
||||||
.ok_or_else(|| anyhow::anyhow!("Invalid username or password."))
|
None => {
|
||||||
.map_err(PublishError::AuthError)
|
return Err(PublishError::AuthError(anyhow::anyhow!(
|
||||||
|
"Unknown username."
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let expected_password_hash = PasswordHash::new(&expected_password_hash)
|
||||||
|
.context("Failed to parse hash in PHC string format.")
|
||||||
|
.map_err(PublishError::UnexpectedError)?;
|
||||||
|
|
||||||
|
Argon2::default()
|
||||||
|
.verify_password(credentials.password.as_bytes(), &expected_password_hash)
|
||||||
|
.context("Invalid password.")
|
||||||
|
.map_err(PublishError::AuthError)?;
|
||||||
|
|
||||||
|
Ok(user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
|
|
Loading…
Reference in a new issue