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 97f774c

Browse files
chore: tidying up codebase
1 parent 40b733d commit 97f774c

File tree

5 files changed

+183
-15
lines changed

5 files changed

+183
-15
lines changed

‎Cargo.lock

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

‎Cargo.toml

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

3840
[dev-dependencies]
3941
once_cell = "1"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Add migration script here
2+
ALTER TABLE users RENAME password TO password_hash;

‎src/routes/newsletters.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use actix_web::http::{header, StatusCode};
44
use actix_web::{web, HttpRequest, HttpResponse, ResponseError};
55
use anyhow::Context;
66
use secrecy::{ExposeSecret, Secret};
7+
use sha3::Digest;
78
use sqlx::PgPool;
89

910
#[derive(serde::Deserialize)]
@@ -71,13 +72,22 @@ impl std::fmt::Debug for PublishError {
7172
}
7273
}
7374

75+
#[tracing::instrument(
76+
name = "Publish a newsletter issue",
77+
skip(body, pool, email_client, request),
78+
fields(
79+
username = tracing::field::Empty,
80+
user_id = tracing::field::Empty,
81+
)
82+
)]
7483
pub async fn publish_newsletter(
7584
body: web::Json<BodyData>,
7685
pool: web::Data<PgPool>,
7786
email_client: web::Data<EmailClient>,
7887
request: HttpRequest,
7988
) -> Result<HttpResponse, PublishError> {
8089
let credentials = basic_authentication(request.headers()).map_err(PublishError::AuthError)?;
90+
let user_id = validate_credentials(credentials, &pool);
8191
let subscribers = get_confirmed_subscribers(&pool).await?;
8292
for subscriber in subscribers {
8393
match subscriber {
@@ -137,3 +147,26 @@ fn basic_authentication(headers: &HeaderMap) -> Result<Credentials, anyhow::Erro
137147
password: Secret::new(password.to_owned()),
138148
})
139149
}
150+
151+
async fn validate_credentials(
152+
credentials: Credentials,
153+
pool: &PgPool,
154+
) -> Result<uuid::Uuid, PublishError> {
155+
let password_hash = sha3::Sha3_256::digest(credentials.password.expose_secret().as_bytes());
156+
let password_hash = format!("{:x}", password_hash);
157+
158+
let user_id: Option<_> = sqlx::query!(
159+
r#"SELECT user_id FROM users WHERE username = 1ドル AND password_hash = 2ドル"#,
160+
credentials.username,
161+
password_hash,
162+
)
163+
.fetch_optional(pool)
164+
.await
165+
.context("Failed to perform the query to validate auth credentials")
166+
.map_err(PublishError::UnexpectedError)?;
167+
168+
user_id
169+
.map(|row| row.user_id)
170+
.ok_or_else(|| anyhow::anyhow!("Invalid username or password"))
171+
.map_err(PublishError::AuthError)
172+
}

‎tests/api/helpers.rs

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use robust_rust::{
44
startup::{get_connection_pool, Application},
55
telemetry::{get_subscriber, init_subscriber},
66
};
7+
use sha3::Digest;
78
use sqlx::{Connection, Executor, PgConnection, PgPool};
89
use uuid::Uuid;
910
use wiremock::MockServer;
@@ -27,11 +28,7 @@ pub struct TestApp {
2728
pub db_pool: PgPool,
2829
pub mock_server: MockServer,
2930
pub port: u16,
30-
}
31-
32-
pub struct ConfirmationLinks {
33-
pub html: reqwest::Url,
34-
pub plain_text: reqwest::Url,
31+
test_user: TestUser,
3532
}
3633

3734
impl TestApp {
@@ -68,14 +65,53 @@ impl TestApp {
6865
pub async fn post_newsletters(&self, body: serde_json::Value) -> reqwest::Response {
6966
reqwest::Client::new()
7067
.post(&format!("{}/newsletters", &self.address))
71-
.basic_auth(Uuid::new_v4().to_string(), Some(Uuid::new_v4().to_string()))
68+
.basic_auth(&self.test_user.username, Some(&self.test_user.password))
7269
.json(&body)
7370
.send()
7471
.await
7572
.expect("Failed to execute request.")
7673
}
7774
}
7875

76+
pub struct ConfirmationLinks {
77+
pub html: reqwest::Url,
78+
pub plain_text: reqwest::Url,
79+
}
80+
81+
pub struct TestUser {
82+
pub user_id: Uuid,
83+
pub username: String,
84+
pub password: String,
85+
}
86+
87+
impl TestUser {
88+
pub fn generate() -> Self {
89+
Self {
90+
user_id: Uuid::new_v4(),
91+
username: Uuid::new_v4().to_string(),
92+
password: Uuid::new_v4().to_string(),
93+
}
94+
}
95+
96+
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);
99+
100+
sqlx::query!(
101+
r#"
102+
INSERT INTO users (user_id, username, password_hash)
103+
VALUES (1,ドル 2,ドル 3ドル)
104+
"#,
105+
self.user_id,
106+
self.username,
107+
password_hash
108+
)
109+
.execute(pool)
110+
.await
111+
.expect("Failed to store test user.");
112+
}
113+
}
114+
79115
#[allow(clippy::let_underscore_future)]
80116
pub async fn spawn_app() -> TestApp {
81117
Lazy::force(&TRACING);
@@ -105,12 +141,16 @@ pub async fn spawn_app() -> TestApp {
105141

106142
let _ = tokio::spawn(application.run_until_stopped());
107143

108-
TestApp {
144+
let test_app = TestApp {
109145
address,
110146
db_pool: get_connection_pool(&configuration.database),
111147
mock_server,
112148
port: application_port,
113-
}
149+
test_user: TestUser::generate(),
150+
};
151+
152+
test_app.test_user.store(&test_app.db_pool).await;
153+
test_app
114154
}
115155

116156
async fn configure_database(config: &DatabaseSettings) -> PgPool {

0 commit comments

Comments
(0)

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