Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7658346

Browse files
Chore: Improved error tracing and logging
1 parent 41af00c commit 7658346

File tree

5 files changed

+73
-96
lines changed

5 files changed

+73
-96
lines changed

‎Cargo.lock

Lines changed: 6 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ rand = { version = "0.8", features = ["std_rng"] }
3434
thiserror = "1"
3535
anyhow = "1"
3636
base64 = "0.13.0"
37-
sha3 = "0.9"
3837
argon2 = { version = "0.3", features = ["std"] }
3938

4039
[dev-dependencies]

‎src/routes/newsletters.rs

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use crate::{domain::SubscriberEmail, email_client::EmailClient, routes::error_chain_fmt};
1+
use crate::{
2+
domain::SubscriberEmail, email_client::EmailClient, routes::error_chain_fmt,
3+
telemetry::spawn_blocking_with_tracing,
4+
};
25
use actix_web::http::header::{HeaderMap, HeaderValue};
36
use actix_web::http::{header, StatusCode};
47
use actix_web::{web, HttpRequest, HttpResponse, ResponseError};
58
use anyhow::Context;
6-
use argon2::{Algorithm,Argon2, Params,PasswordHash, PasswordVerifier,Version};
9+
use argon2::{Argon2, PasswordHash, PasswordVerifier};
710
use secrecy::{ExposeSecret, Secret};
8-
use sha3::Digest;
911
use sqlx::PgPool;
1012

1113
#[derive(serde::Deserialize)]
@@ -149,44 +151,59 @@ fn basic_authentication(headers: &HeaderMap) -> Result<Credentials, anyhow::Erro
149151
})
150152
}
151153

154+
#[tracing::instrument(name = "Validate auth credentials", skip(credentials, pool))]
152155
async fn validate_credentials(
153156
credentials: Credentials,
154157
pool: &PgPool,
155158
) -> Result<uuid::Uuid, PublishError> {
156-
let hasher = Argon2::new(
157-
Algorithm::Argon2id,
158-
Version::V0x13,
159-
Params::new(15000, 2, 1, None)
160-
.context("Failed to build Argon2 params")
161-
.map_err(PublishError::UnexpectedError)?,
162-
);
163-
164-
let row: Option<_> = sqlx::query!(
165-
r#"SELECT user_id, password_hash, salt FROM users WHERE username = 1ドル"#,
166-
credentials.username,
167-
)
168-
.fetch_optional(pool)
159+
let (user_id, expected_password_hash) = get_stored_credentials(&credentials.username, pool)
160+
.await
161+
.map_err(PublishError::UnexpectedError)?
162+
.ok_or_else(|| PublishError::AuthError(anyhow::anyhow!("Unknown username")))?;
163+
164+
let current_span = tracing::Span::current();
165+
tokio::task::spawn_blocking(move || {
166+
current_span.in_scope(|| verify_password_hash(expected_password_hash, credentials.password))
167+
})
169168
.await
170-
.context("Failed to perform the query to validate auth credentials")
169+
.context("Failed to spawn blocking task")
171170
.map_err(PublishError::UnexpectedError)?;
172171

173-
let (expected_password_hash, user_id, salt) = match row {
174-
Some(row) => (row.password_hash, row.user_id, row.salt),
175-
None => {
176-
return Err(PublishError::AuthError(anyhow::anyhow!(
177-
"Invalid username or password"
178-
)))
179-
}
180-
};
172+
Ok(user_id)
173+
}
181174

182-
let expected_password_hash = PasswordHash::new(&expected_password_hash)
183-
.context("Failed to parse hash in PHC string format")
175+
#[tracing::instrument(
176+
name = "Verify Password Hash",
177+
skip(expected_password_hash, password_candidate)
178+
)]
179+
async fn verify_password_hash(
180+
expected_password_hash: Secret<String>,
181+
password_candidate: Secret<String>,
182+
) -> Result<(), PublishError> {
183+
let expected_password_hash = PasswordHash::new(expected_password_hash.expose_secret())
184+
.context("Failed to parse hash in PHC string format.")
184185
.map_err(PublishError::UnexpectedError)?;
186+
Argon2::default()
187+
.verify_password(
188+
password_candidate.expose_secret().as_bytes(),
189+
&expected_password_hash,
190+
)
191+
.context("Invalid password.")
192+
.map_err(PublishError::AuthError)
193+
}
185194

186-
Argon2::default().verify_password(
187-
credentials.password.expose_secret().as_bytes(),
188-
&expected_password_hash,
189-
).context("Failed to verify password").map_err(PublishError::UnexpectedError)?;
190-
191-
Ok(user_id)
195+
#[tracing::instrument(name = "Get stored credentials", skip(username, pool))]
196+
async fn get_stored_credentials(
197+
username: &str,
198+
pool: &PgPool,
199+
) -> Result<Option<(uuid::Uuid, Secret<String>)>, anyhow::Error> {
200+
let row = sqlx::query!(
201+
r#"SELECT user_id, password_hash FROM users WHERE username = 1ドル"#,
202+
username,
203+
)
204+
.fetch_optional(pool)
205+
.await
206+
.context("Failed to perform the query to validate auth credentials")?
207+
.map(|row| (row.user_id, Secret::new(row.password_hash)));
208+
Ok(row)
192209
}

‎src/telemetry.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use tokio::task::JoinHandle;
12
use tracing::subscriber::set_global_default;
23
use tracing::Subscriber;
34
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
@@ -26,3 +27,12 @@ pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) {
2627
LogTracer::init().expect("Failed to set logger");
2728
set_global_default(subscriber).expect("Failed to set subscriber");
2829
}
30+
31+
pub fn spawn_blocking_with_tracing<F, R>(f: F) -> JoinHandle<R>
32+
where
33+
F: FnOnce() -> R + Send + 'static,
34+
R: Send + 'static,
35+
{
36+
let current_span = tracing::Span::current();
37+
tokio::task::spawn_blocking(move || current_span.in_scope(f))
38+
}

‎tests/api/helpers.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use argon2::password_hash::SaltString;
2+
use argon2::{Argon2, PasswordHasher};
13
use once_cell::sync::Lazy;
24
use robust_rust::{
35
configuration::{get_configuration, DatabaseSettings},
46
startup::{get_connection_pool, Application},
57
telemetry::{get_subscriber, init_subscriber},
68
};
7-
use sha3::Digest;
89
use sqlx::{Connection, Executor, PgConnection, PgPool};
910
use uuid::Uuid;
1011
use wiremock::MockServer;
@@ -94,8 +95,11 @@ impl TestUser {
9495
}
9596

9697
async fn store(&self, pool: &PgPool) {
97-
let password_harsh = sha3::Sha3_256::digest(self.password.as_bytes());
98-
let password_hash = format!("{:x}", password_harsh);
98+
let salt = SaltString::generate(&mut rand::thread_rng());
99+
let password_hash = Argon2::default()
100+
.hash_password(self.password.as_bytes(), salt.as_ref())
101+
.unwrap()
102+
.to_string();
99103

100104
sqlx::query!(
101105
r#"

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /