From f78f25c358ba36f6214a5242484fe330ee49b54e Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 15 Aug 2021 13:26:16 +0100 Subject: [PATCH] Store user list, insecurely. --- .../20210815112026_create_users_table.sql | 6 ++++ src/routes/newsletters.rs | 33 +++++++++++++++++++ tests/api/helpers.rs | 32 ++++++++++++++++-- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 migrations/20210815112026_create_users_table.sql diff --git a/migrations/20210815112026_create_users_table.sql b/migrations/20210815112026_create_users_table.sql new file mode 100644 index 0000000..56c5c78 --- /dev/null +++ b/migrations/20210815112026_create_users_table.sql @@ -0,0 +1,6 @@ +-- Add migration script here +CREATE TABLE users( + user_id uuid PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL +); diff --git a/src/routes/newsletters.rs b/src/routes/newsletters.rs index 56bffa9..24717de 100644 --- a/src/routes/newsletters.rs +++ b/src/routes/newsletters.rs @@ -83,6 +83,35 @@ fn basic_authentication(headers: &HeaderMap) -> Result Result { + let user_id: Option<_> = sqlx::query!( + r#" + SELECT user_id + FROM users + WHERE username = $1 AND password = $2 + "#, + credentials.username, + credentials.password + ) + .fetch_optional(pool) + .await + .context("Failed to performed a query to validate auth credentials.") + .map_err(PublishError::UnexpectedError)?; + + user_id + .map(|row| row.user_id) + .ok_or_else(|| anyhow::anyhow!("Invalid username or password.")) + .map_err(PublishError::AuthError) +} + +#[tracing::instrument( + name = "Publish a newsletter issue", + skip(body, pool, email_client, request), + fields(username=tracing::field::Empty, user_id=tracing::field::Empty) +)] pub async fn publish_newsletter( body: web::Json, pool: web::Data, @@ -90,6 +119,10 @@ pub async fn publish_newsletter( request: web::HttpRequest, ) -> Result { let credentials = basic_authentication(request.headers()).map_err(PublishError::AuthError)?; + tracing::Span::current().record("username", &tracing::field::display(&credentials.username)); + let user_id = validate_credentials(credentials, &pool).await?; + tracing::Span::current().record("user_id", &tracing::field::display(&user_id)); + let subscribers = get_confirmed_subscribers(&pool).await?; for subscriber in subscribers { match subscriber { diff --git a/tests/api/helpers.rs b/tests/api/helpers.rs index 2b97571..768489f 100644 --- a/tests/api/helpers.rs +++ b/tests/api/helpers.rs @@ -44,9 +44,10 @@ 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(Uuid::new_v4().to_string(), Some(Uuid::new_v4().to_string())) + .basic_auth(username, Some(password)) .json(&body) .send() .await @@ -76,6 +77,14 @@ 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 { @@ -106,14 +115,31 @@ pub async fn spawn_app() -> TestApp { let application_port = application.port(); let _ = tokio::spawn(application.run_until_stopped()); - TestApp { + let test_app = TestApp { address: format!("http://localhost:{}", application_port), port: application_port, db_pool: get_connection_pool(&configuration.database) .await .expect("Failed to connect to the database"), email_server, - } + }; + + add_test_user(&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 {