From f50abaa4a6bc34f84d5bbe62cf2d144a66992516 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Mon, 22 May 2023 16:57:08 -0700 Subject: [PATCH 01/12] re-org some handlers, handle '/'. --- src/generic_handlers.rs | 7 ++++++ src/handlers.rs | 55 ----------------------------------------- src/lib.rs | 2 +- src/main.rs | 3 +++ src/users.rs | 8 +++--- 5 files changed, 15 insertions(+), 60 deletions(-) create mode 100644 src/generic_handlers.rs delete mode 100644 src/handlers.rs diff --git a/src/generic_handlers.rs b/src/generic_handlers.rs new file mode 100644 index 0000000..7ad4bcd --- /dev/null +++ b/src/generic_handlers.rs @@ -0,0 +1,7 @@ +use axum::response::{IntoResponse, Redirect}; + +pub async fn handle_slash_redir() -> impl IntoResponse { + Redirect::temporary("/") +} + +pub async fn handle_slash() -> impl IntoResponse {} diff --git a/src/handlers.rs b/src/handlers.rs deleted file mode 100644 index 5ba9bd4..0000000 --- a/src/handlers.rs +++ /dev/null @@ -1,55 +0,0 @@ -use axum::{ - async_trait, - extract::{FromRef, FromRequestParts, State}, - http::{request::Parts, StatusCode}, -}; -use sqlx::SqlitePool; - -pub async fn using_connection_pool_extractor( - State(pool): State, -) -> Result { - sqlx::query_scalar("select 'hello world from sqlite get'") - .fetch_one(&pool) - .await - .map_err(internal_error) -} - -// we can also write a custom extractor that grabs a connection from the pool -// which setup is appropriate depends on your application -pub struct DatabaseConnection(sqlx::pool::PoolConnection); - -#[async_trait] -impl FromRequestParts for DatabaseConnection -where - SqlitePool: FromRef, - S: Send + Sync, -{ - type Rejection = (StatusCode, String); - - async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result { - let pool = SqlitePool::from_ref(state); - - let conn = pool.acquire().await.map_err(internal_error)?; - - Ok(Self(conn)) - } -} - -pub async fn using_connection_extractor( - DatabaseConnection(conn): DatabaseConnection, -) -> Result { - let mut conn = conn; - sqlx::query_scalar("select 'hello world from sqlite post'") - .fetch_one(&mut conn) - .await - .map_err(internal_error) -} - -/// Utility function for mapping any error into a `500 Internal Server Error` -/// response. -fn internal_error(err: E) -> (StatusCode, String) -where - E: std::error::Error, -{ - (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) -} diff --git a/src/lib.rs b/src/lib.rs index f97526d..bf90918 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,6 @@ extern crate justerror; pub mod db; -pub mod handlers; +pub mod generic_handlers; pub(crate) mod templates; pub mod users; diff --git a/src/main.rs b/src/main.rs index 51e79c6..f99d315 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use axum::{routing::get, Router}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use witch_watch::{ db, + generic_handlers::{handle_slash, handle_slash_redir}, users::{get_create_user, handle_signup_success, post_create_user}, }; @@ -21,11 +22,13 @@ async fn main() { // build our application with some routes let app = Router::new() + .route("/", get(handle_slash).post(handle_slash)) .route("/signup", get(get_create_user).post(post_create_user)) .route( "/signup_success/:id", get(handle_signup_success).post(handle_signup_success), ) + .fallback(handle_slash_redir) .with_state(pool); tracing::debug!("binding to 0.0.0.0:3000"); diff --git a/src/users.rs b/src/users.rs index a140739..e6f067d 100644 --- a/src/users.rs +++ b/src/users.rs @@ -145,8 +145,8 @@ pub async fn handle_signup_success( State(pool): State, ) -> Response { let user: User = { - let id = id; - let id = Uuid::try_parse(&id).unwrap_or_default(); + let id = id.trim(); + let id = Uuid::try_parse(id).unwrap_or_default(); let id_bytes = id.to_bytes_le(); sqlx::query_as(ID_QUERY) .bind(id_bytes.as_slice()) @@ -157,8 +157,8 @@ pub async fn handle_signup_success( let mut resp = CreateUserSuccess(user.clone()).into_response(); - if user.username.is_empty() { - // redirect to front page if we got here without a valid witch header + if user.username.is_empty() || id.is_empty() { + // redirect to front page if we got here without a valid witch ID *resp.status_mut() = StatusCode::TEMPORARY_REDIRECT; resp.headers_mut().insert("Location", "/".parse().unwrap()); } From 8237715066fd92943cd7e73de15290bb7ae2e490 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Mon, 22 May 2023 17:18:13 -0700 Subject: [PATCH 02/12] simplify uuid/db handling --- src/lib.rs | 10 ++++++++++ src/users.rs | 9 +++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bf90918..1f45553 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,13 @@ pub mod db; pub mod generic_handlers; pub(crate) mod templates; pub mod users; + +pub trait ToBlob { + fn blob(&self) -> &[u8]; +} + +impl ToBlob for uuid::Uuid { + fn blob(&self) -> &[u8] { + self.as_bytes().as_slice() + } +} diff --git a/src/users.rs b/src/users.rs index e6f067d..fa6fed1 100644 --- a/src/users.rs +++ b/src/users.rs @@ -14,7 +14,7 @@ use sqlx::{sqlite::SqliteRow, Row, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; -use crate::templates::CreateUser; +use crate::{templates::CreateUser, ToBlob}; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; @@ -147,9 +147,8 @@ pub async fn handle_signup_success( let user: User = { let id = id.trim(); let id = Uuid::try_parse(id).unwrap_or_default(); - let id_bytes = id.to_bytes_le(); sqlx::query_as(ID_QUERY) - .bind(id_bytes.as_slice()) + .bind(id.blob()) .fetch_one(&pool) .await .unwrap_or_default() @@ -181,10 +180,8 @@ async fn create_user( .to_string(); let id = Uuid::new_v4(); - let id_bytes = id.to_bytes_le(); - let id_bytes = id_bytes.as_slice(); let res = sqlx::query(CREATE_QUERY) - .bind(id_bytes) + .bind(id.blob()) .bind(username) .bind(displayname) .bind(email) From 0d6c9932d6d41478865bc861e2218b931767a546 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Wed, 24 May 2023 16:39:13 -0700 Subject: [PATCH 03/12] Add SqliteSessionStore module. Stolen with very minor mods from https://github.com/jbr/async-sqlx-session --- src/main.rs | 13 ++ src/session_store.rs | 507 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 520 insertions(+) create mode 100644 src/session_store.rs diff --git a/src/main.rs b/src/main.rs index f99d315..5e83589 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,13 @@ use std::net::SocketAddr; use axum::{routing::get, Router}; +use axum_login::axum_sessions::SessionLayer; +use rand_core::{OsRng, RngCore}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use witch_watch::{ db, generic_handlers::{handle_slash, handle_slash_redir}, + session_store::SqliteSessionStore, users::{get_create_user, handle_signup_success, post_create_user}, }; @@ -19,6 +22,15 @@ async fn main() { .init(); let pool = db::get_pool().await; + let store = SqliteSessionStore::from_client(pool.clone()); + store.migrate().await.expect("Could not migrate session DB"); + let secret = { + let mut bytes = [0u8; 128]; + let mut rng = OsRng; + rng.fill_bytes(&mut bytes); + bytes + }; + let session_layer = SessionLayer::new(store, &secret).with_secure(true); // build our application with some routes let app = Router::new() @@ -29,6 +41,7 @@ async fn main() { get(handle_signup_success).post(handle_signup_success), ) .fallback(handle_slash_redir) + .layer(session_layer) .with_state(pool); tracing::debug!("binding to 0.0.0.0:3000"); diff --git a/src/session_store.rs b/src/session_store.rs new file mode 100644 index 0000000..b97768b --- /dev/null +++ b/src/session_store.rs @@ -0,0 +1,507 @@ +use async_session::{async_trait, chrono::Utc, log, serde_json, Result, Session, SessionStore}; +use sqlx::{pool::PoolConnection, sqlite::SqlitePool, Sqlite}; + +// NOTE! This code was straight stolen from +// https://github.com/jbr/async-sqlx-session/blob/30d00bed44ab2034082698f098eba48b21600f36/src/sqlite.rs +// and used under the terms of the MIT license: + +/* +Copyright 2022 Jacob Rothstein + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the “Software”), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/// sqlx sqlite session store for async-sessions +/// +/// ```rust +/// use witch_watch::session_store::SqliteSessionStore; +/// use async_session::{Session, SessionStore, Result}; +/// use std::time::Duration; +/// +/// # #[tokio::main] +/// # async fn main() -> Result { +/// let store = SqliteSessionStore::new("sqlite::memory:").await?; +/// store.migrate().await?; +/// +/// let mut session = Session::new(); +/// session.insert("key", vec![1,2,3]); +/// +/// let cookie_value = store.store_session(session).await?.unwrap(); +/// let session = store.load_session(cookie_value).await?.unwrap(); +/// assert_eq!(session.get::>("key").unwrap(), vec![1,2,3]); +/// # Ok(()) } + +#[derive(Clone, Debug)] +pub struct SqliteSessionStore { + client: SqlitePool, + table_name: String, +} + +impl SqliteSessionStore { + /// constructs a new SqliteSessionStore from an existing + /// sqlx::SqlitePool. the default table name for this session + /// store will be "async_sessions". To override this, chain this + /// with [`with_table_name`](crate::SqliteSessionStore::with_table_name). + /// + /// ```rust + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::Result; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap(); + /// let store = SqliteSessionStore::from_client(pool) + /// .with_table_name("custom_table_name"); + /// store.migrate().await; + /// # Ok(()) } + /// ``` + pub fn from_client(client: SqlitePool) -> Self { + Self { + client, + table_name: "async_sessions".into(), + } + } + + /// Constructs a new SqliteSessionStore from a sqlite: database url. note + /// that this documentation uses the special `:memory:` sqlite + /// database for convenient testing, but a real application would + /// use a path like `sqlite:///path/to/database.db`. The default + /// table name for this session store will be "async_sessions". To + /// override this, either chain with + /// [`with_table_name`](crate::SqliteSessionStore::with_table_name) or + /// use + /// [`new_with_table_name`](crate::SqliteSessionStore::new_with_table_name) + /// + /// ```rust + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::Result; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let store = SqliteSessionStore::new("sqlite::memory:").await?; + /// store.migrate().await; + /// # Ok(()) } + /// ``` + pub async fn new(database_url: &str) -> sqlx::Result { + Ok(Self::from_client(SqlitePool::connect(database_url).await?)) + } + + /// constructs a new SqliteSessionStore from a sqlite: database url. the + /// default table name for this session store will be + /// "async_sessions". To override this, either chain with + /// [`with_table_name`](crate::SqliteSessionStore::with_table_name) or + /// use + /// [`new_with_table_name`](crate::SqliteSessionStore::new_with_table_name) + /// + /// ```rust + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::Result; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let store = SqliteSessionStore::new_with_table_name("sqlite::memory:", "custom_table_name").await?; + /// store.migrate().await; + /// # Ok(()) } + /// ``` + pub async fn new_with_table_name(database_url: &str, table_name: &str) -> sqlx::Result { + Ok(Self::new(database_url).await?.with_table_name(table_name)) + } + + /// Chainable method to add a custom table name. This will panic + /// if the table name is not `[a-zA-Z0-9_-]+`. + /// ```rust + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::Result; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let store = SqliteSessionStore::new("sqlite::memory:").await? + /// .with_table_name("custom_name"); + /// store.migrate().await; + /// # Ok(()) } + /// ``` + /// + /// ```should_panic + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::Result; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let store = SqliteSessionStore::new("sqlite::memory:").await? + /// .with_table_name("johnny (); drop users;"); + /// # Ok(()) } + /// ``` + pub fn with_table_name(mut self, table_name: impl AsRef) -> Self { + let table_name = table_name.as_ref(); + if table_name.is_empty() + || !table_name + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') + { + panic!( + "table name must be [a-zA-Z0-9_-]+, but {} was not", + table_name + ); + } + + self.table_name = table_name.to_owned(); + self + } + + /// Creates a session table if it does not already exist. If it + /// does, this will noop, making it safe to call repeatedly on + /// store initialization. In the future, this may make + /// exactly-once modifications to the schema of the session table + /// on breaking releases. + /// ```rust + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::{Result, SessionStore, Session}; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let store = SqliteSessionStore::new("sqlite::memory:").await?; + /// assert!(store.count().await.is_err()); + /// store.migrate().await?; + /// store.store_session(Session::new()).await?; + /// store.migrate().await?; // calling it a second time is safe + /// assert_eq!(store.count().await?, 1); + /// # Ok(()) } + /// ``` + pub async fn migrate(&self) -> sqlx::Result<()> { + log::info!("migrating sessions on `{}`", self.table_name); + + let mut conn = self.client.acquire().await?; + sqlx::query(&self.substitute_table_name( + r#" + CREATE TABLE IF NOT EXISTS %%TABLE_NAME%% ( + id TEXT PRIMARY KEY NOT NULL, + expires INTEGER NULL, + session TEXT NOT NULL + ) + "#, + )) + .execute(&mut conn) + .await?; + Ok(()) + } + + // private utility function because sqlite does not support + // parametrized table names + fn substitute_table_name(&self, query: &str) -> String { + query.replace("%%TABLE_NAME%%", &self.table_name) + } + + /// retrieve a connection from the pool + async fn connection(&self) -> sqlx::Result> { + self.client.acquire().await + } + + /// Performs a one-time cleanup task that clears out stale + /// (expired) sessions. You may want to call this from cron. + /// ```rust + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::{chrono::{Utc,Duration}, Result, SessionStore, Session}; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let store = SqliteSessionStore::new("sqlite::memory:").await?; + /// store.migrate().await?; + /// let mut session = Session::new(); + /// session.set_expiry(Utc::now() - Duration::seconds(5)); + /// store.store_session(session).await?; + /// assert_eq!(store.count().await?, 1); + /// store.cleanup().await?; + /// assert_eq!(store.count().await?, 0); + /// # Ok(()) } + /// ``` + pub async fn cleanup(&self) -> sqlx::Result<()> { + let mut connection = self.connection().await?; + sqlx::query(&self.substitute_table_name( + r#" + DELETE FROM %%TABLE_NAME%% + WHERE expires < ? + "#, + )) + .bind(Utc::now().timestamp()) + .execute(&mut connection) + .await?; + + Ok(()) + } + + /// retrieves the number of sessions currently stored, including + /// expired sessions + /// + /// ```rust + /// # use witch_watch::session_store::SqliteSessionStore; + /// # use async_session::{Result, SessionStore, Session}; + /// # use std::time::Duration; + /// # #[tokio::main] + /// # async fn main() -> Result { + /// let store = SqliteSessionStore::new("sqlite::memory:").await?; + /// store.migrate().await?; + /// assert_eq!(store.count().await?, 0); + /// store.store_session(Session::new()).await?; + /// assert_eq!(store.count().await?, 1); + /// # Ok(()) } + /// ``` + pub async fn count(&self) -> sqlx::Result { + let (count,) = + sqlx::query_as(&self.substitute_table_name("SELECT COUNT(*) FROM %%TABLE_NAME%%")) + .fetch_one(&mut self.connection().await?) + .await?; + + Ok(count) + } +} + +#[async_trait] +impl SessionStore for SqliteSessionStore { + async fn load_session(&self, cookie_value: String) -> Result> { + let id = Session::id_from_cookie_value(&cookie_value)?; + let mut connection = self.connection().await?; + + let result: Option<(String,)> = sqlx::query_as(&self.substitute_table_name( + r#" + SELECT session FROM %%TABLE_NAME%% + WHERE id = ? AND (expires IS NULL OR expires > ?) + "#, + )) + .bind(&id) + .bind(Utc::now().timestamp()) + .fetch_optional(&mut connection) + .await?; + + Ok(result + .map(|(session,)| serde_json::from_str(&session)) + .transpose()?) + } + + async fn store_session(&self, session: Session) -> Result> { + let id = session.id(); + let string = serde_json::to_string(&session)?; + let mut connection = self.connection().await?; + + sqlx::query(&self.substitute_table_name( + r#" + INSERT INTO %%TABLE_NAME%% + (id, session, expires) VALUES (?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + expires = excluded.expires, + session = excluded.session + "#, + )) + .bind(id) + .bind(&string) + .bind(session.expiry().map(|expiry| expiry.timestamp())) + .execute(&mut connection) + .await?; + + Ok(session.into_cookie_value()) + } + + async fn destroy_session(&self, session: Session) -> Result { + let id = session.id(); + let mut connection = self.connection().await?; + sqlx::query(&self.substitute_table_name( + r#" + DELETE FROM %%TABLE_NAME%% WHERE id = ? + "#, + )) + .bind(id) + .execute(&mut connection) + .await?; + + Ok(()) + } + + async fn clear_store(&self) -> Result { + let mut connection = self.connection().await?; + sqlx::query(&self.substitute_table_name( + r#" + DELETE FROM %%TABLE_NAME%% + "#, + )) + .execute(&mut connection) + .await?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::*; + + async fn test_store() -> SqliteSessionStore { + let store = SqliteSessionStore::new("sqlite::memory:") + .await + .expect("building a sqlite :memory: SqliteSessionStore"); + store + .migrate() + .await + .expect("migrating a brand new :memory: SqliteSessionStore"); + store + } + + #[tokio::test] + async fn creating_a_new_session_with_no_expiry() -> Result { + let store = test_store().await; + let mut session = Session::new(); + session.insert("key", "value")?; + let cloned = session.clone(); + let cookie_value = store.store_session(session).await?.unwrap(); + + let (id, expires, serialized, count): (String, Option, String, i64) = + sqlx::query_as("select id, expires, session, count(*) from async_sessions") + .fetch_one(&mut store.connection().await?) + .await?; + + assert_eq!(1, count); + assert_eq!(id, cloned.id()); + assert_eq!(expires, None); + + let deserialized_session: Session = serde_json::from_str(&serialized)?; + assert_eq!(cloned.id(), deserialized_session.id()); + assert_eq!("value", &deserialized_session.get::("key").unwrap()); + + let loaded_session = store.load_session(cookie_value).await?.unwrap(); + assert_eq!(cloned.id(), loaded_session.id()); + assert_eq!("value", &loaded_session.get::("key").unwrap()); + + assert!(!loaded_session.is_expired()); + Ok(()) + } + + #[tokio::test] + async fn updating_a_session() -> Result { + let store = test_store().await; + let mut session = Session::new(); + let original_id = session.id().to_owned(); + + session.insert("key", "value")?; + let cookie_value = store.store_session(session).await?.unwrap(); + + let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); + session.insert("key", "other value")?; + assert_eq!(None, store.store_session(session).await?); + + let session = store.load_session(cookie_value.clone()).await?.unwrap(); + assert_eq!(session.get::("key").unwrap(), "other value"); + + let (id, count): (String, i64) = sqlx::query_as("select id, count(*) from async_sessions") + .fetch_one(&mut store.connection().await?) + .await?; + + assert_eq!(1, count); + assert_eq!(original_id, id); + + Ok(()) + } + + #[tokio::test] + async fn updating_a_session_extending_expiry() -> Result { + let store = test_store().await; + let mut session = Session::new(); + session.expire_in(Duration::from_secs(10)); + let original_id = session.id().to_owned(); + let original_expires = session.expiry().unwrap().clone(); + let cookie_value = store.store_session(session).await?.unwrap(); + + let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); + assert_eq!(session.expiry().unwrap(), &original_expires); + session.expire_in(Duration::from_secs(20)); + let new_expires = session.expiry().unwrap().clone(); + store.store_session(session).await?; + + let session = store.load_session(cookie_value.clone()).await?.unwrap(); + assert_eq!(session.expiry().unwrap(), &new_expires); + + let (id, expires, count): (String, i64, i64) = + sqlx::query_as("select id, expires, count(*) from async_sessions") + .fetch_one(&mut store.connection().await?) + .await?; + + assert_eq!(1, count); + assert_eq!(expires, new_expires.timestamp()); + assert_eq!(original_id, id); + + Ok(()) + } + + #[tokio::test] + async fn creating_a_new_session_with_expiry() -> Result { + let store = test_store().await; + let mut session = Session::new(); + session.expire_in(Duration::from_secs(1)); + session.insert("key", "value")?; + let cloned = session.clone(); + + let cookie_value = store.store_session(session).await?.unwrap(); + + let (id, expires, serialized, count): (String, Option, String, i64) = + sqlx::query_as("select id, expires, session, count(*) from async_sessions") + .fetch_one(&mut store.connection().await?) + .await?; + + assert_eq!(1, count); + assert_eq!(id, cloned.id()); + assert!(expires.unwrap() > Utc::now().timestamp()); + + let deserialized_session: Session = serde_json::from_str(&serialized)?; + assert_eq!(cloned.id(), deserialized_session.id()); + assert_eq!("value", &deserialized_session.get::("key").unwrap()); + + let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap(); + assert_eq!(cloned.id(), loaded_session.id()); + assert_eq!("value", &loaded_session.get::("key").unwrap()); + + assert!(!loaded_session.is_expired()); + + tokio::time::sleep(Duration::from_secs(1)).await; + assert_eq!(None, store.load_session(cookie_value).await?); + + Ok(()) + } + + #[tokio::test] + async fn destroying_a_single_session() -> Result { + let store = test_store().await; + for _ in 0..3i8 { + store.store_session(Session::new()).await?; + } + + let cookie = store.store_session(Session::new()).await?.unwrap(); + assert_eq!(4, store.count().await?); + let session = store.load_session(cookie.clone()).await?.unwrap(); + store.destroy_session(session.clone()).await.unwrap(); + assert_eq!(None, store.load_session(cookie).await?); + assert_eq!(3, store.count().await?); + + // // attempting to destroy the session again is not an error + // assert!(store.destroy_session(session).await.is_ok()); + Ok(()) + } + + #[tokio::test] + async fn clearing_the_whole_store() -> Result { + let store = test_store().await; + for _ in 0..3i8 { + store.store_session(Session::new()).await?; + } + + assert_eq!(3, store.count().await?); + store.clear_store().await.unwrap(); + assert_eq!(0, store.count().await?); + + Ok(()) + } +} From 60862a5d6a574d19a3ed26a44d8fe2155077bdd8 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Wed, 24 May 2023 17:08:40 -0700 Subject: [PATCH 04/12] minor tidy --- Cargo.lock | 44 +++++++++++++------------------------------- Cargo.toml | 10 ++++++---- src/lib.rs | 1 + src/users.rs | 37 +++++++++++++++++++++++++++---------- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09fecbe..e25a242 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,7 +194,6 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -212,7 +211,6 @@ dependencies = [ "rustversion", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -482,30 +480,15 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" -dependencies = [ - "crc-catalog 1.1.1", -] - [[package]] name = "crc" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ - "crc-catalog 2.2.0", + "crc-catalog", ] -[[package]] -name = "crc-catalog" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" - [[package]] name = "crc-catalog" version = "2.2.0" @@ -1604,18 +1587,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", @@ -1816,7 +1799,6 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc 2.1.0", "crossbeam-queue", "either", "event-listener", @@ -1838,7 +1820,6 @@ dependencies = [ "paste", "percent-encoding", "rustls 0.19.1", - "sha2 0.10.6", "smallvec", "sqlformat 0.1.8", "sqlx-rt 0.5.13", @@ -1863,7 +1844,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", - "crc 3.0.1", + "crc", "crossbeam-queue", "dotenvy", "either", @@ -1910,7 +1891,6 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", "sqlx-core 0.5.13", "sqlx-rt 0.5.13", "syn 1.0.109", @@ -2181,9 +2161,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", @@ -2195,6 +2175,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] @@ -2461,9 +2442,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ "getrandom", "serde", @@ -2784,6 +2765,7 @@ dependencies = [ "argon2", "askama", "askama_axum", + "async-session", "axum", "axum-login", "axum-macros", @@ -2800,7 +2782,7 @@ dependencies = [ "tracing-subscriber", "unicode-segmentation", "urlencoding", - "uuid 1.3.1", + "uuid 1.3.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 49fb315..18ac566 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,18 +4,18 @@ version = "0.0.1" edition = "2021" [dependencies] -axum = { version = "0.6", features = ["macros", "tracing"] } +axum = { version = "0.6", features = ["macros", "headers"] } askama = { version = "0.12", features = ["with-axum"] } askama_axum = "0.3" axum-macros = "0.3" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["full", "tracing"], default-features = false } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tower = { version = "0.4", features = ["util", "timeout"] } +tower = { version = "0.4", features = ["util", "timeout"], default-features = false } tower-http = { version = "0.4", features = ["add-extension", "trace"] } uuid = { version = "1.3", features = ["serde", "v4"] } serde = { version = "1", features = ["derive"] } -sqlx = { version = "0.5.10", features = ["runtime-tokio-rustls", "any", "sqlite", "chrono", "time", "uuid"] } +sqlx = { version = "0.5.10", default-features = false, features = ["runtime-tokio-rustls", "any", "sqlite", "chrono", "time", "uuid"] } argon2 = "0.5" rand_core = { version = "0.6", features = ["getrandom"] } thiserror = "1.0.40" @@ -24,3 +24,5 @@ password-hash = { version = "0.5.0", features = ["std", "getrandom"] } axum-login = { version = "0.5.0", features = ["sqlite", "sqlx"] } unicode-segmentation = "1.10.1" urlencoding = "2.1.2" +async-session = "3.0.0" + diff --git a/src/lib.rs b/src/lib.rs index 1f45553..b0ff663 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ extern crate justerror; pub mod db; pub mod generic_handlers; +pub mod session_store; pub(crate) mod templates; pub mod users; diff --git a/src/users.rs b/src/users.rs index fa6fed1..34a2a05 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use argon2::{ - password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, Argon2, }; use askama::Template; @@ -10,7 +10,10 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use sqlx::{sqlite::SqliteRow, Row, SqlitePool}; +use axum_login::{ + secrecy::{SecretVec}, AuthUser, +}; +use sqlx::{query_as, sqlite::SqliteRow, Row, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; @@ -18,16 +21,17 @@ use crate::{templates::CreateUser, ToBlob}; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; - const ID_QUERY: &str = "select * from witches where id = $1"; +// const PW_QUERY: &str = "select pwhash from witches where id = $1"; #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct User { - id: Uuid, - username: String, - displayname: Option, - email: Option, - last_seen: Option, + pub id: Uuid, + pub username: String, + pub displayname: Option, + pub email: Option, + pub last_seen: Option, + pwhash: String, } impl Display for User { @@ -43,6 +47,16 @@ impl Display for User { } } +impl AuthUser for User { + fn get_id(&self) -> Uuid { + self.id + } + + fn get_password_hash(&self) -> SecretVec { + SecretVec::new(self.pwhash.as_bytes().to_vec()) + } +} + #[derive(Debug, Clone, Template)] #[template(path = "signup_success.html")] pub struct CreateUserSuccess(User); @@ -57,6 +71,7 @@ impl sqlx::FromRow<'_, SqliteRow> for User { let displayname: Option = row.get("displayname"); let last_seen: Option = row.get("last_seen"); let email: Option = row.get("email"); + let pwhash: String = row.get("pwhash"); Ok(Self { id, @@ -64,6 +79,7 @@ impl sqlx::FromRow<'_, SqliteRow> for User { displayname, email, last_seen, + pwhash, }) } } @@ -147,7 +163,7 @@ pub async fn handle_signup_success( let user: User = { let id = id.trim(); let id = Uuid::try_parse(id).unwrap_or_default(); - sqlx::query_as(ID_QUERY) + query_as(ID_QUERY) .bind(id.blob()) .fetch_one(&pool) .await @@ -185,7 +201,7 @@ async fn create_user( .bind(username) .bind(displayname) .bind(email) - .bind(pwhash) + .bind(&pwhash) .execute(pool) .await; @@ -197,6 +213,7 @@ async fn create_user( displayname: displayname.to_owned(), email: email.to_owned(), last_seen: None, + pwhash, }; Ok(user) } From 151719daf1555739f014c06ea4d79f5caababd5f Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sat, 27 May 2023 22:55:00 -0700 Subject: [PATCH 05/12] update sqlx to actual latest version --- Cargo.lock | 665 ++++++++++------------------------------------------- Cargo.toml | 2 +- 2 files changed, 122 insertions(+), 545 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e25a242..0f00871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -88,7 +99,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -135,16 +146,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", -] - -[[package]] -name = "atoi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" -dependencies = [ - "num-traits", + "syn 2.0.18", ] [[package]] @@ -164,9 +166,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b70caf9f1b0c045f7da350636435b775a9733adf2df56e8aa2a29210fbc335d4" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", @@ -253,7 +255,7 @@ dependencies = [ "secrecy", "serde", "serde_json", - "sqlx 0.6.3", + "sqlx", "tokio", "tower", "tower-http 0.3.5", @@ -269,7 +271,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -288,12 +290,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - [[package]] name = "base64" version = "0.13.1" @@ -302,9 +298,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -342,7 +338,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -380,9 +376,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" @@ -427,22 +423,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -455,13 +435,13 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "hmac 0.12.1", "percent-encoding", "rand", "sha2 0.10.6", "subtle", - "time 0.3.21", + "time", "version_check", ] @@ -544,50 +524,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.15", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - [[package]] name = "digest" version = "0.9.0" @@ -599,27 +535,15 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", "subtle", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "dotenvy" version = "0.15.7" @@ -748,7 +672,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -802,40 +726,28 @@ dependencies = [ "wasi", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.3", ] [[package]] name = "hashlink" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa" dependencies = [ - "hashbrown 0.11.2", -] - -[[package]] -name = "hashlink" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" -dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -851,7 +763,7 @@ dependencies = [ "http", "httpdate", "mime", - "sha1 0.10.5", + "sha1", ] [[package]] @@ -903,7 +815,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -994,12 +906,11 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -1054,9 +965,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1080,15 +991,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libsqlite3-sys" @@ -1101,15 +1012,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - [[package]] name = "lock_api" version = "0.4.9" @@ -1122,12 +1024,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "matchers" @@ -1324,22 +1223,22 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -1356,9 +1255,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" @@ -1366,26 +1265,20 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1431,11 +1324,11 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" dependencies = [ - "regex-syntax 0.7.1", + "regex-syntax 0.7.2", ] [[package]] @@ -1455,9 +1348,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "ring" @@ -1474,28 +1367,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64 0.13.1", - "log", - "ring", - "sct 0.6.1", - "webpki 0.21.4", -] - [[package]] name = "rustls" version = "0.20.8" @@ -1504,8 +1375,8 @@ checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", - "sct 0.7.0", - "webpki 0.22.0", + "sct", + "webpki", ] [[package]] @@ -1514,7 +1385,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] @@ -1535,22 +1406,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sct" version = "0.7.0" @@ -1570,21 +1425,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.163" @@ -1602,7 +1442,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -1637,15 +1477,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - [[package]] name = "sha1" version = "0.10.5" @@ -1654,15 +1485,9 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" version = "0.9.9" @@ -1684,7 +1509,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1745,17 +1570,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "sqlformat" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" -dependencies = [ - "itertools", - "nom", - "unicode_categories", -] - [[package]] name = "sqlformat" version = "0.2.1" @@ -1767,70 +1581,14 @@ dependencies = [ "unicode_categories", ] -[[package]] -name = "sqlx" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" -dependencies = [ - "sqlx-core 0.5.13", - "sqlx-macros 0.5.13", -] - [[package]] name = "sqlx" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" dependencies = [ - "sqlx-core 0.6.3", - "sqlx-macros 0.6.3", -] - -[[package]] -name = "sqlx-core" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" -dependencies = [ - "ahash", - "atoi 0.4.0", - "bitflags", - "byteorder", - "bytes", - "chrono", - "crossbeam-queue", - "either", - "event-listener", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "hashlink 0.7.0", - "hex", - "indexmap", - "itoa", - "libc", - "libsqlite3-sys", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "rustls 0.19.1", - "smallvec", - "sqlformat 0.1.8", - "sqlx-rt 0.5.13", - "stringprep", - "thiserror", - "time 0.2.27", - "tokio-stream", - "url", - "uuid 0.8.2", - "webpki 0.21.4", - "webpki-roots 0.21.1", + "sqlx-core", + "sqlx-macros", ] [[package]] @@ -1839,11 +1597,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ - "ahash", - "atoi 1.0.0", + "ahash 0.7.6", + "atoi", "bitflags", "byteorder", "bytes", + "chrono", "crc", "crossbeam-queue", "dotenvy", @@ -1855,7 +1614,7 @@ dependencies = [ "futures-executor", "futures-intrusive", "futures-util", - "hashlink 0.8.1", + "hashlink", "hex", "indexmap", "itoa", @@ -1866,35 +1625,19 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.20.8", + "rustls", "rustls-pemfile", "sha2 0.10.6", "smallvec", - "sqlformat 0.2.1", - "sqlx-rt 0.6.3", + "sqlformat", + "sqlx-rt", "stringprep", "thiserror", + "time", "tokio-stream", "url", - "webpki-roots 0.22.6", -] - -[[package]] -name = "sqlx-macros" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" -dependencies = [ - "dotenv", - "either", - "heck", - "once_cell", - "proc-macro2", - "quote", - "sqlx-core 0.5.13", - "sqlx-rt 0.5.13", - "syn 1.0.109", - "url", + "uuid", + "webpki-roots", ] [[package]] @@ -1910,23 +1653,12 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.6", - "sqlx-core 0.6.3", - "sqlx-rt 0.6.3", + "sqlx-core", + "sqlx-rt", "syn 1.0.109", "url", ] -[[package]] -name = "sqlx-rt" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" -dependencies = [ - "once_cell", - "tokio", - "tokio-rustls 0.22.0", -] - [[package]] name = "sqlx-rt" version = "0.6.3" @@ -1935,67 +1667,9 @@ checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ "once_cell", "tokio", - "tokio-rustls 0.23.4", + "tokio-rustls", ] -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1 0.6.1", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "stringprep" version = "0.1.2" @@ -2025,9 +1699,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -2040,15 +1714,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.40" @@ -2066,7 +1731,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -2079,21 +1744,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros 0.1.1", - "version_check", - "winapi", -] - [[package]] name = "time" version = "0.3.21" @@ -2103,7 +1753,7 @@ dependencies = [ "itoa", "serde", "time-core", - "time-macros 0.2.9", + "time-macros", ] [[package]] @@ -2112,16 +1762,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - [[package]] name = "time-macros" version = "0.2.9" @@ -2131,19 +1771,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn 1.0.109", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -2161,9 +1788,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -2187,18 +1814,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", -] - -[[package]] -name = "tokio-rustls" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" -dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.4", + "syn 2.0.18", ] [[package]] @@ -2207,9 +1823,9 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.8", + "rustls", "tokio", - "webpki 0.22.0", + "webpki", ] [[package]] @@ -2291,10 +1907,11 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.38" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if 1.0.0", "log", "pin-project-lite", "tracing-attributes", @@ -2309,14 +1926,14 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -2380,9 +1997,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -2399,12 +2016,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - [[package]] name = "unicode_categories" version = "0.1.1" @@ -2434,12 +2045,6 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "uuid" version = "1.3.3" @@ -2486,9 +2091,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2496,24 +2101,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2521,43 +2126,33 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki" version = "0.22.0" @@ -2568,22 +2163,13 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki 0.21.4", -] - [[package]] name = "webpki-roots" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ - "webpki 0.22.0", + "webpki", ] [[package]] @@ -2602,15 +2188,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2773,7 +2350,7 @@ dependencies = [ "password-hash", "rand_core", "serde", - "sqlx 0.5.13", + "sqlx", "thiserror", "tokio", "tower", @@ -2782,7 +2359,7 @@ dependencies = [ "tracing-subscriber", "unicode-segmentation", "urlencoding", - "uuid 1.3.3", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 18ac566..3e3215a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ tower = { version = "0.4", features = ["util", "timeout"], default-features = fa tower-http = { version = "0.4", features = ["add-extension", "trace"] } uuid = { version = "1.3", features = ["serde", "v4"] } serde = { version = "1", features = ["derive"] } -sqlx = { version = "0.5.10", default-features = false, features = ["runtime-tokio-rustls", "any", "sqlite", "chrono", "time", "uuid"] } +sqlx = { version = "0.6", default-features = false, features = ["runtime-tokio-rustls", "any", "sqlite", "chrono", "time", "uuid"] } argon2 = "0.5" rand_core = { version = "0.6", features = ["getrandom"] } thiserror = "1.0.40" From b1fd57c4862e9957d4f4362f99f11b7cf714e5ef Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sun, 28 May 2023 12:20:55 -0700 Subject: [PATCH 06/12] checkpoint working skeleton for login --- migrations/20230426221940_init.up.sql | 2 +- src/db.rs | 4 +- src/main.rs | 22 ++-- src/templates.rs | 18 ++- src/users.rs | 175 ++++++++++++++++---------- templates/login_get.html | 0 templates/login_post.html | 0 7 files changed, 140 insertions(+), 81 deletions(-) create mode 100644 templates/login_get.html create mode 100644 templates/login_post.html diff --git a/migrations/20230426221940_init.up.sql b/migrations/20230426221940_init.up.sql index 7d53573..02df49a 100644 --- a/migrations/20230426221940_init.up.sql +++ b/migrations/20230426221940_init.up.sql @@ -5,7 +5,7 @@ -- users create table if not exists witches ( - id blob not null primary key, + id int not null primary key, username text not null unique, displayname text, email text, diff --git a/src/db.rs b/src/db.rs index c9a55d9..e3ccc45 100644 --- a/src/db.rs +++ b/src/db.rs @@ -20,12 +20,12 @@ pub async fn get_pool() -> SqlitePool { let conn_opts = SqliteConnectOptions::new() .foreign_keys(true) .auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Incremental) - .filename(&db_filename); + .filename(&db_filename) + .busy_timeout(Duration::from_secs(TIMEOUT)); // setup connection pool SqlitePoolOptions::new() .max_connections(MAX_CONNS) - .connect_timeout(Duration::from_secs(TIMEOUT)) .connect_with(conn_opts) .await .expect("can't connect to database") diff --git a/src/main.rs b/src/main.rs index 5e83589..66c0783 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use witch_watch::{ db, generic_handlers::{handle_slash, handle_slash_redir}, session_store::SqliteSessionStore, - users::{get_create_user, handle_signup_success, post_create_user}, + users::{get_create_user, get_login, handle_signup_success, post_create_user, post_login}, }; #[tokio::main] @@ -22,17 +22,18 @@ async fn main() { .init(); let pool = db::get_pool().await; - let store = SqliteSessionStore::from_client(pool.clone()); - store.migrate().await.expect("Could not migrate session DB"); - let secret = { - let mut bytes = [0u8; 128]; - let mut rng = OsRng; - rng.fill_bytes(&mut bytes); - bytes + let session_layer = { + let store = SqliteSessionStore::from_client(pool.clone()); + store.migrate().await.expect("Could not migrate session DB"); + let secret = { + let mut bytes = [0u8; 128]; + let mut rng = OsRng; + rng.fill_bytes(&mut bytes); + bytes + }; + SessionLayer::new(store, &secret).with_secure(true) }; - let session_layer = SessionLayer::new(store, &secret).with_secure(true); - // build our application with some routes let app = Router::new() .route("/", get(handle_slash).post(handle_slash)) .route("/signup", get(get_create_user).post(post_create_user)) @@ -40,6 +41,7 @@ async fn main() { "/signup_success/:id", get(handle_signup_success).post(handle_signup_success), ) + .route("/login", get(get_login).post(post_login)) .fallback(handle_slash_redir) .layer(session_layer) .with_state(pool); diff --git a/src/templates.rs b/src/templates.rs index afbc50d..f8ad15f 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,7 +1,7 @@ use askama::Template; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Template, Deserialize)] +#[derive(Debug, Default, Template, Deserialize, Serialize)] #[template(path = "signup.html")] pub struct CreateUser { pub username: String, @@ -10,3 +10,17 @@ pub struct CreateUser { pub password: String, pub pw_verify: String, } + +#[derive(Debug, Default, Template, Deserialize, Serialize)] +#[template(path = "login_post.html")] +pub struct LoginPost { + pub username: String, + pub password: String, +} + +#[derive(Debug, Default, Template, Deserialize, Serialize)] +#[template(path = "login_get.html")] +pub struct LoginGet { + pub username: String, + pub password: String, +} diff --git a/src/users.rs b/src/users.rs index 34a2a05..c729ccc 100644 --- a/src/users.rs +++ b/src/users.rs @@ -10,23 +10,25 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use axum_login::{ - secrecy::{SecretVec}, AuthUser, -}; -use sqlx::{query_as, sqlite::SqliteRow, Row, SqlitePool}; +use axum_login::{secrecy::SecretVec, AuthUser, SqliteStore}; +use rand_core::CryptoRngCore; +use sqlx::{query_as, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; -use crate::{templates::CreateUser, ToBlob}; +use crate::{ + templates::{CreateUser, LoginGet}, + ToBlob, +}; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; const ID_QUERY: &str = "select * from witches where id = $1"; // const PW_QUERY: &str = "select pwhash from witches where id = $1"; -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, sqlx::FromRow)] pub struct User { - pub id: Uuid, + pub id: i64, pub username: String, pub displayname: Option, pub email: Option, @@ -47,8 +49,10 @@ impl Display for User { } } -impl AuthUser for User { - fn get_id(&self) -> Uuid { +pub type AuthContext = axum_login::extractors::AuthContext>; + +impl AuthUser for User { + fn get_id(&self) -> i64 { self.id } @@ -57,33 +61,49 @@ impl AuthUser for User { } } +//-------------------------------------------------------------------------- +// Result types for user creation +//-------------------------------------------------------------------------- + #[derive(Debug, Clone, Template)] #[template(path = "signup_success.html")] pub struct CreateUserSuccess(User); -impl sqlx::FromRow<'_, SqliteRow> for User { - fn from_row(row: &SqliteRow) -> Result { - let bytes: Vec = row.get("id"); - let bytes = bytes.as_slice(); - let bytes: [u8; 16] = bytes.try_into().unwrap(); - let id = Uuid::from_bytes_le(bytes); - let username: String = row.get("username"); - let displayname: Option = row.get("displayname"); - let last_seen: Option = row.get("last_seen"); - let email: Option = row.get("email"); - let pwhash: String = row.get("pwhash"); +#[Error(desc = "Could not create user.")] +#[non_exhaustive] +pub struct CreateUserError(#[from] CreateUserErrorKind); - Ok(Self { - id, - username, - displayname, - email, - last_seen, - pwhash, - }) +impl IntoResponse for CreateUserError { + fn into_response(self) -> askama_axum::Response { + match self.0 { + CreateUserErrorKind::UnknownDBError => { + (StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response() + } + _ => (StatusCode::BAD_REQUEST, format!("{self}")).into_response(), + } } } +#[Error] +#[non_exhaustive] +pub enum CreateUserErrorKind { + AlreadyExists, + #[error(desc = "Usernames must be between 1 and 20 non-whitespace characters long")] + BadUsername, + PasswordMismatch, + #[error(desc = "Password must have at least 4 and at most 50 characters")] + BadPassword, + #[error(desc = "Display name must be less than 100 characters long")] + BadDisplayname, + BadEmail, + MissingFields, + UnknownDBError, +} + +//-------------------------------------------------------------------------- +// User creation route handlers +//-------------------------------------------------------------------------- + /// Get Handler: displays the form to create a user pub async fn get_create_user() -> CreateUser { CreateUser::default() @@ -109,12 +129,6 @@ pub async fn post_create_user( return Err(CreateUserErrorKind::BadUsername.into()); } - if let Some(ref dn) = displayname { - if dn.len() > 50 { - return Err(CreateUserErrorKind::BadDisplayname.into()); - } - } - if password != verify { return Err(CreateUserErrorKind::PasswordMismatch.into()); } @@ -122,12 +136,21 @@ pub async fn post_create_user( let password = urlencoding::decode(password) .map_err(|_| CreateUserErrorKind::BadPassword)? .to_string(); + let password = password.trim(); let password = password.as_bytes(); + if !(4..=50).contains(&password.len()) { + return Err(CreateUserErrorKind::BadPassword.into()); + } let displayname = if let Some(dn) = displayname { let dn = urlencoding::decode(dn) .map_err(|_| CreateUserErrorKind::BadDisplayname)? + .to_string() + .trim() .to_string(); + if dn.graphemes(true).size_hint().1.unwrap() > 100 { + return Err(CreateUserErrorKind::BadDisplayname.into()); + } Some(dn) } else { None @@ -147,7 +170,7 @@ pub async fn post_create_user( let user = create_user(username, displayname, email, password, &pool).await?; tracing::debug!("created {user:?}"); - let id = user.id.simple().to_string(); + let id = user.id; let location = format!("/signup_success/{id}"); let resp = axum::response::Redirect::temporary(&location).into_response(); @@ -180,6 +203,54 @@ pub async fn handle_signup_success( resp } +//-------------------------------------------------------------------------- +// Login error and success types +//-------------------------------------------------------------------------- + +#[Error] +pub struct LoginError(#[from] LoginErrorKind); + +#[Error] +#[non_exhaustive] +pub enum LoginErrorKind { + BadPassword, + Unknown, +} + +impl IntoResponse for LoginError { + fn into_response(self) -> Response { + match self.0 { + LoginErrorKind::Unknown => ( + StatusCode::INTERNAL_SERVER_ERROR, + "An unknown error occurred; you cursed, brah?", + ) + .into_response(), + _ => (StatusCode::BAD_REQUEST, format!("{self}")).into_response(), + } + } +} + +//-------------------------------------------------------------------------- +// Login handlers +//-------------------------------------------------------------------------- + +/// Handle login queries +#[axum::debug_handler] +pub async fn post_login( + mut auth: AuthContext, + State(pool): State, +) -> Result<(), LoginError> { + Err(LoginErrorKind::Unknown.into()) +} + +pub async fn get_login() -> impl IntoResponse { + LoginGet::default() +} + +//------------------------------------------------------------------------- +// private fns +//------------------------------------------------------------------------- + async fn create_user( username: &str, displayname: &Option, @@ -195,9 +266,10 @@ async fn create_user( .unwrap() // safe to unwrap, we know the salt is valid .to_string(); - let id = Uuid::new_v4(); + let mut rng = OsRng; + let id: i64 = rng.as_rngcore().next_u64() as i64; let res = sqlx::query(CREATE_QUERY) - .bind(id.blob()) + .bind(id) .bind(username) .bind(displayname) .bind(email) @@ -233,32 +305,3 @@ async fn create_user( _ => Err(CreateUserErrorKind::UnknownDBError.into()), } } - -#[Error(desc = "Could not create user.")] -#[non_exhaustive] -pub struct CreateUserError(#[from] CreateUserErrorKind); - -impl IntoResponse for CreateUserError { - fn into_response(self) -> askama_axum::Response { - match self.0 { - CreateUserErrorKind::UnknownDBError => { - (StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response() - } - _ => (StatusCode::BAD_REQUEST, format!("{self}")).into_response(), - } - } -} - -#[Error] -#[non_exhaustive] -pub enum CreateUserErrorKind { - AlreadyExists, - #[error(desc = "Usernames must be between 1 and 20 non-whitespace characters long")] - BadUsername, - PasswordMismatch, - BadPassword, - BadDisplayname, - BadEmail, - MissingFields, - UnknownDBError, -} diff --git a/templates/login_get.html b/templates/login_get.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/login_post.html b/templates/login_post.html new file mode 100644 index 0000000..e69de29 From 41a7abbe13c1b8d35ae0dda85b09f5b17feae27a Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sun, 28 May 2023 12:58:26 -0700 Subject: [PATCH 07/12] go back to uuids for users --- migrations/20230426221940_init.up.sql | 2 +- src/lib.rs | 10 ---------- src/users.rs | 21 ++++++++------------- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/migrations/20230426221940_init.up.sql b/migrations/20230426221940_init.up.sql index 02df49a..7d53573 100644 --- a/migrations/20230426221940_init.up.sql +++ b/migrations/20230426221940_init.up.sql @@ -5,7 +5,7 @@ -- users create table if not exists witches ( - id int not null primary key, + id blob not null primary key, username text not null unique, displayname text, email text, diff --git a/src/lib.rs b/src/lib.rs index b0ff663..6c16c13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,13 +6,3 @@ pub mod generic_handlers; pub mod session_store; pub(crate) mod templates; pub mod users; - -pub trait ToBlob { - fn blob(&self) -> &[u8]; -} - -impl ToBlob for uuid::Uuid { - fn blob(&self) -> &[u8] { - self.as_bytes().as_slice() - } -} diff --git a/src/users.rs b/src/users.rs index c729ccc..63c46e7 100644 --- a/src/users.rs +++ b/src/users.rs @@ -11,15 +11,11 @@ use axum::{ response::{IntoResponse, Response}, }; use axum_login::{secrecy::SecretVec, AuthUser, SqliteStore}; -use rand_core::CryptoRngCore; use sqlx::{query_as, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; -use crate::{ - templates::{CreateUser, LoginGet}, - ToBlob, -}; +use crate::templates::{CreateUser, LoginGet}; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; @@ -28,7 +24,7 @@ const ID_QUERY: &str = "select * from witches where id = $1"; #[derive(Debug, Default, Clone, PartialEq, Eq, sqlx::FromRow)] pub struct User { - pub id: i64, + pub id: Uuid, pub username: String, pub displayname: Option, pub email: Option, @@ -49,10 +45,10 @@ impl Display for User { } } -pub type AuthContext = axum_login::extractors::AuthContext>; +pub type AuthContext = axum_login::extractors::AuthContext>; -impl AuthUser for User { - fn get_id(&self) -> i64 { +impl AuthUser for User { + fn get_id(&self) -> Uuid { self.id } @@ -170,7 +166,7 @@ pub async fn post_create_user( let user = create_user(username, displayname, email, password, &pool).await?; tracing::debug!("created {user:?}"); - let id = user.id; + let id = user.id.as_simple().to_string(); let location = format!("/signup_success/{id}"); let resp = axum::response::Redirect::temporary(&location).into_response(); @@ -187,7 +183,7 @@ pub async fn handle_signup_success( let id = id.trim(); let id = Uuid::try_parse(id).unwrap_or_default(); query_as(ID_QUERY) - .bind(id.blob()) + .bind(id) .fetch_one(&pool) .await .unwrap_or_default() @@ -266,8 +262,7 @@ async fn create_user( .unwrap() // safe to unwrap, we know the salt is valid .to_string(); - let mut rng = OsRng; - let id: i64 = rng.as_rngcore().next_u64() as i64; + let id = Uuid::new_v4(); let res = sqlx::query(CREATE_QUERY) .bind(id) .bind(username) From e5d66769dcdd05495ea6220ca723ed20b58541a4 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sun, 28 May 2023 13:31:45 -0700 Subject: [PATCH 08/12] rename modules for users --- src/lib.rs | 2 +- src/login.rs | 0 src/main.rs | 2 +- src/{users.rs => signup.rs} | 26 +++++++++++++------------- 4 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 src/login.rs rename src/{users.rs => signup.rs} (90%) diff --git a/src/lib.rs b/src/lib.rs index 6c16c13..c3ef877 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,5 @@ extern crate justerror; pub mod db; pub mod generic_handlers; pub mod session_store; +pub mod signup; pub(crate) mod templates; -pub mod users; diff --git a/src/login.rs b/src/login.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs index 66c0783..7cc38cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use witch_watch::{ db, generic_handlers::{handle_slash, handle_slash_redir}, session_store::SqliteSessionStore, - users::{get_create_user, get_login, handle_signup_success, post_create_user, post_login}, + signup::{get_create_user, get_login, handle_signup_success, post_create_user, post_login}, }; #[tokio::main] diff --git a/src/users.rs b/src/signup.rs similarity index 90% rename from src/users.rs rename to src/signup.rs index 63c46e7..d2d7513 100644 --- a/src/users.rs +++ b/src/signup.rs @@ -20,7 +20,6 @@ use crate::templates::{CreateUser, LoginGet}; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; const ID_QUERY: &str = "select * from witches where id = $1"; -// const PW_QUERY: &str = "select pwhash from witches where id = $1"; #[derive(Debug, Default, Clone, PartialEq, Eq, sqlx::FromRow)] pub struct User { @@ -57,9 +56,9 @@ impl AuthUser for User { } } -//-------------------------------------------------------------------------- +//-************************************************************************ // Result types for user creation -//-------------------------------------------------------------------------- +//-************************************************************************ #[derive(Debug, Clone, Template)] #[template(path = "signup_success.html")] @@ -96,9 +95,9 @@ pub enum CreateUserErrorKind { UnknownDBError, } -//-------------------------------------------------------------------------- +//-************************************************************************ // User creation route handlers -//-------------------------------------------------------------------------- +//-************************************************************************ /// Get Handler: displays the form to create a user pub async fn get_create_user() -> CreateUser { @@ -174,13 +173,13 @@ pub async fn post_create_user( Ok(resp) } -/// Get handler for successful signup +/// Generic handler for successful signup pub async fn handle_signup_success( Path(id): Path, State(pool): State, ) -> Response { + let id = id.trim(); let user: User = { - let id = id.trim(); let id = Uuid::try_parse(id).unwrap_or_default(); query_as(ID_QUERY) .bind(id) @@ -196,12 +195,13 @@ pub async fn handle_signup_success( *resp.status_mut() = StatusCode::TEMPORARY_REDIRECT; resp.headers_mut().insert("Location", "/".parse().unwrap()); } + resp } -//-------------------------------------------------------------------------- +//-************************************************************************ // Login error and success types -//-------------------------------------------------------------------------- +//-************************************************************************ #[Error] pub struct LoginError(#[from] LoginErrorKind); @@ -226,9 +226,9 @@ impl IntoResponse for LoginError { } } -//-------------------------------------------------------------------------- +//-************************************************************************ // Login handlers -//-------------------------------------------------------------------------- +//-************************************************************************ /// Handle login queries #[axum::debug_handler] @@ -243,9 +243,9 @@ pub async fn get_login() -> impl IntoResponse { LoginGet::default() } -//------------------------------------------------------------------------- +//-************************************************************************ // private fns -//------------------------------------------------------------------------- +//-************************************************************************ async fn create_user( username: &str, From 0f0a7583cde68a8c57db9d80107f26e8533ebd08 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sun, 28 May 2023 13:44:50 -0700 Subject: [PATCH 09/12] Move login stuff to own module. --- src/lib.rs | 3 +++ src/login.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 ++- src/signup.rs | 61 ++-------------------------------------------- 4 files changed, 74 insertions(+), 60 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3ef877..62b7b65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,9 @@ extern crate justerror; pub mod db; pub mod generic_handlers; +pub mod login; pub mod session_store; pub mod signup; pub(crate) mod templates; + +pub use signup::User; diff --git a/src/login.rs b/src/login.rs index e69de29..8010c86 100644 --- a/src/login.rs +++ b/src/login.rs @@ -0,0 +1,67 @@ +use argon2::PasswordVerifier; +use axum::{ + extract::State, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use axum_login::{secrecy::SecretVec, AuthUser, SqliteStore}; +use sqlx::SqlitePool; +use uuid::Uuid; + +use crate::{templates::LoginGet, User}; + +pub type AuthContext = axum_login::extractors::AuthContext>; + +impl AuthUser for User { + fn get_id(&self) -> Uuid { + self.id + } + + fn get_password_hash(&self) -> SecretVec { + SecretVec::new(self.pwhash.as_bytes().to_vec()) + } +} + +//-************************************************************************ +// Login error and success types +//-************************************************************************ + +#[Error] +pub struct LoginError(#[from] LoginErrorKind); + +#[Error] +#[non_exhaustive] +pub enum LoginErrorKind { + BadPassword, + Unknown, +} + +impl IntoResponse for LoginError { + fn into_response(self) -> Response { + match self.0 { + LoginErrorKind::Unknown => ( + StatusCode::INTERNAL_SERVER_ERROR, + "An unknown error occurred; you cursed, brah?", + ) + .into_response(), + _ => (StatusCode::BAD_REQUEST, format!("{self}")).into_response(), + } + } +} + +//-************************************************************************ +// Login handlers +//-************************************************************************ + +/// Handle login queries +#[axum::debug_handler] +pub async fn post_login( + mut auth: AuthContext, + State(pool): State, +) -> Result<(), LoginError> { + Err(LoginErrorKind::Unknown.into()) +} + +pub async fn get_login() -> impl IntoResponse { + LoginGet::default() +} diff --git a/src/main.rs b/src/main.rs index 7cc38cc..638a8bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,9 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use witch_watch::{ db, generic_handlers::{handle_slash, handle_slash_redir}, + login::{get_login, post_login}, session_store::SqliteSessionStore, - signup::{get_create_user, get_login, handle_signup_success, post_create_user, post_login}, + signup::{get_create_user, handle_signup_success, post_create_user}, }; #[tokio::main] diff --git a/src/signup.rs b/src/signup.rs index d2d7513..917e233 100644 --- a/src/signup.rs +++ b/src/signup.rs @@ -10,12 +10,11 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use axum_login::{secrecy::SecretVec, AuthUser, SqliteStore}; use sqlx::{query_as, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; -use crate::templates::{CreateUser, LoginGet}; +use crate::templates::CreateUser; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; @@ -28,7 +27,7 @@ pub struct User { pub displayname: Option, pub email: Option, pub last_seen: Option, - pwhash: String, + pub(crate) pwhash: String, } impl Display for User { @@ -44,18 +43,6 @@ impl Display for User { } } -pub type AuthContext = axum_login::extractors::AuthContext>; - -impl AuthUser for User { - fn get_id(&self) -> Uuid { - self.id - } - - fn get_password_hash(&self) -> SecretVec { - SecretVec::new(self.pwhash.as_bytes().to_vec()) - } -} - //-************************************************************************ // Result types for user creation //-************************************************************************ @@ -199,50 +186,6 @@ pub async fn handle_signup_success( resp } -//-************************************************************************ -// Login error and success types -//-************************************************************************ - -#[Error] -pub struct LoginError(#[from] LoginErrorKind); - -#[Error] -#[non_exhaustive] -pub enum LoginErrorKind { - BadPassword, - Unknown, -} - -impl IntoResponse for LoginError { - fn into_response(self) -> Response { - match self.0 { - LoginErrorKind::Unknown => ( - StatusCode::INTERNAL_SERVER_ERROR, - "An unknown error occurred; you cursed, brah?", - ) - .into_response(), - _ => (StatusCode::BAD_REQUEST, format!("{self}")).into_response(), - } - } -} - -//-************************************************************************ -// Login handlers -//-************************************************************************ - -/// Handle login queries -#[axum::debug_handler] -pub async fn post_login( - mut auth: AuthContext, - State(pool): State, -) -> Result<(), LoginError> { - Err(LoginErrorKind::Unknown.into()) -} - -pub async fn get_login() -> impl IntoResponse { - LoginGet::default() -} - //-************************************************************************ // private fns //-************************************************************************ From 559e277d9e479805bf46b1b5209b815c58236d8f Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sun, 28 May 2023 17:06:09 -0700 Subject: [PATCH 10/12] fix deps --- Cargo.lock | 7 +++---- Cargo.toml | 16 ++++++++-------- src/form.rs | 54 ----------------------------------------------------- 3 files changed, 11 insertions(+), 66 deletions(-) delete mode 100644 src/form.rs diff --git a/Cargo.lock b/Cargo.lock index 0f00871..507f70a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1073,14 +1073,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "eebffdb73fe72e917997fad08bdbf31ac50b0fa91cec93e69a0662e4264d454c" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3e3215a..6d27745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,16 +13,16 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tower = { version = "0.4", features = ["util", "timeout"], default-features = false } tower-http = { version = "0.4", features = ["add-extension", "trace"] } -uuid = { version = "1.3", features = ["serde", "v4"] } +uuid = { version = "1", features = ["serde", "v4"] } serde = { version = "1", features = ["derive"] } sqlx = { version = "0.6", default-features = false, features = ["runtime-tokio-rustls", "any", "sqlite", "chrono", "time", "uuid"] } argon2 = "0.5" rand_core = { version = "0.6", features = ["getrandom"] } -thiserror = "1.0.40" -justerror = "1.1.0" -password-hash = { version = "0.5.0", features = ["std", "getrandom"] } -axum-login = { version = "0.5.0", features = ["sqlite", "sqlx"] } -unicode-segmentation = "1.10.1" -urlencoding = "2.1.2" -async-session = "3.0.0" +thiserror = "1" +justerror = "1" +password-hash = { version = "0.5", features = ["std", "getrandom"] } +axum-login = { version = "0.5", features = ["sqlite", "sqlx"] } +unicode-segmentation = "1" +urlencoding = "2" +async-session = "3" diff --git a/src/form.rs b/src/form.rs deleted file mode 100644 index a8b7842..0000000 --- a/src/form.rs +++ /dev/null @@ -1,54 +0,0 @@ -use axum::{extract::Form, response::Html}; -use serde::Deserialize; - -pub(crate) async fn show_form() -> Html<&'static str> { - Html( - r#" - - - - -
- - - - - -
- - - "#, - ) -} - -#[derive(Deserialize, Debug)] -#[allow(dead_code)] -pub(crate) struct Input { - name: String, - email: String, -} - -pub(crate) async fn accept_form(Form(input): Form) -> Html { - let Input { name, email: _ } = input; - let html = format!( - r#" - - - - -

Hi, {}

- - - -"#, - name - ); - - Html(html) -} From dbff72330e97e5593000c450fa575788f586375d Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sun, 28 May 2023 17:55:16 -0700 Subject: [PATCH 11/12] Adds working login route. --- src/generic_handlers.rs | 10 +++++- src/lib.rs | 8 ++++- src/login.rs | 66 +++++++++++++++++++++++++++++----------- src/main.rs | 24 ++++++++++----- src/signup.rs | 31 ++----------------- src/users.rs | 49 +++++++++++++++++++++++++++++ src/util.rs | 3 ++ templates/login_get.html | 17 +++++++++++ 8 files changed, 154 insertions(+), 54 deletions(-) create mode 100644 src/users.rs create mode 100644 src/util.rs diff --git a/src/generic_handlers.rs b/src/generic_handlers.rs index 7ad4bcd..1e21dc2 100644 --- a/src/generic_handlers.rs +++ b/src/generic_handlers.rs @@ -1,7 +1,15 @@ use axum::response::{IntoResponse, Redirect}; +use crate::AuthContext; + pub async fn handle_slash_redir() -> impl IntoResponse { Redirect::temporary("/") } -pub async fn handle_slash() -> impl IntoResponse {} +pub async fn handle_slash(auth: AuthContext) -> impl IntoResponse { + if let Some(user) = auth.current_user { + tracing::debug!("Logged in as: {user}"); + } else { + tracing::debug!("Not logged in.") + } +} diff --git a/src/lib.rs b/src/lib.rs index 62b7b65..bc43291 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,17 @@ #[macro_use] extern crate justerror; +use axum_login::SqliteStore; +pub use users::User; +use uuid::Uuid; + pub mod db; pub mod generic_handlers; pub mod login; pub mod session_store; pub mod signup; pub(crate) mod templates; +pub mod users; +pub(crate) mod util; -pub use signup::User; +pub type AuthContext = axum_login::extractors::AuthContext>; diff --git a/src/login.rs b/src/login.rs index 8010c86..4923679 100644 --- a/src/login.rs +++ b/src/login.rs @@ -1,26 +1,26 @@ -use argon2::PasswordVerifier; +use argon2::{ + password_hash::{PasswordHash, PasswordVerifier}, + Argon2, +}; use axum::{ extract::State, http::StatusCode, - response::{IntoResponse, Response}, + response::{IntoResponse, Redirect, Response}, + Form, }; -use axum_login::{secrecy::SecretVec, AuthUser, SqliteStore}; use sqlx::SqlitePool; -use uuid::Uuid; -use crate::{templates::LoginGet, User}; +use crate::{ + templates::{LoginGet, LoginPost}, + util::form_decode, + AuthContext, User, +}; -pub type AuthContext = axum_login::extractors::AuthContext>; +//-************************************************************************ +// Constants +//-************************************************************************ -impl AuthUser for User { - fn get_id(&self) -> Uuid { - self.id - } - - fn get_password_hash(&self) -> SecretVec { - SecretVec::new(self.pwhash.as_bytes().to_vec()) - } -} +const LAST_SEEN_QUERY: &str = "update witches set last_seen = (select unixepoch()) where id = $1"; //-************************************************************************ // Login error and success types @@ -32,7 +32,9 @@ pub struct LoginError(#[from] LoginErrorKind); #[Error] #[non_exhaustive] pub enum LoginErrorKind { + Internal, BadPassword, + BadUsername, Unknown, } @@ -58,8 +60,38 @@ impl IntoResponse for LoginError { pub async fn post_login( mut auth: AuthContext, State(pool): State, -) -> Result<(), LoginError> { - Err(LoginErrorKind::Unknown.into()) + Form(login): Form, +) -> Result { + let username = form_decode(&login.username, LoginErrorKind::BadUsername)?; + let username = username.trim(); + + let pw = form_decode(&login.password, LoginErrorKind::BadPassword)?; + let pw = pw.trim(); + + let user = User::get(username, &pool) + .await + .map_err(|_| LoginErrorKind::Unknown)?; + + let verifier = Argon2::default(); + let hash = PasswordHash::new(&user.pwhash).map_err(|_| LoginErrorKind::Internal)?; + match verifier.verify_password(pw.as_bytes(), &hash) { + Ok(_) => { + // log them in and set a session cookie + auth.login(&user) + .await + .map_err(|_| LoginErrorKind::Internal)?; + + // update last_seen; maybe this is ok to fail? + sqlx::query(LAST_SEEN_QUERY) + .bind(user.id) + .execute(&pool) + .await + .map_err(|_| LoginErrorKind::Internal)?; + + Ok(Redirect::temporary("/")) + } + _ => Err(LoginErrorKind::BadPassword.into()), + } } pub async fn get_login() -> impl IntoResponse { diff --git a/src/main.rs b/src/main.rs index 638a8bb..57342f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::net::SocketAddr; use axum::{routing::get, Router}; -use axum_login::axum_sessions::SessionLayer; +use axum_login::{axum_sessions::SessionLayer, AuthLayer, SqliteStore}; use rand_core::{OsRng, RngCore}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use witch_watch::{ @@ -10,6 +10,7 @@ use witch_watch::{ login::{get_login, post_login}, session_store::SqliteSessionStore, signup::{get_create_user, handle_signup_success, post_create_user}, + User, }; #[tokio::main] @@ -23,18 +24,26 @@ async fn main() { .init(); let pool = db::get_pool().await; + + let secret = { + let mut bytes = [0u8; 128]; + let mut rng = OsRng; + rng.fill_bytes(&mut bytes); + bytes + }; + let session_layer = { let store = SqliteSessionStore::from_client(pool.clone()); store.migrate().await.expect("Could not migrate session DB"); - let secret = { - let mut bytes = [0u8; 128]; - let mut rng = OsRng; - rng.fill_bytes(&mut bytes); - bytes - }; SessionLayer::new(store, &secret).with_secure(true) }; + let auth_layer = { + const QUERY: &str = "select * from witches where id = $1"; + let store = SqliteStore::::new(pool.clone()).with_query(QUERY); + AuthLayer::new(store, &secret) + }; + let app = Router::new() .route("/", get(handle_slash).post(handle_slash)) .route("/signup", get(get_create_user).post(post_create_user)) @@ -44,6 +53,7 @@ async fn main() { ) .route("/login", get(get_login).post(post_login)) .fallback(handle_slash_redir) + .layer(auth_layer) .layer(session_layer) .with_state(pool); diff --git a/src/signup.rs b/src/signup.rs index 917e233..f32a47f 100644 --- a/src/signup.rs +++ b/src/signup.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, Argon2, @@ -14,35 +12,12 @@ use sqlx::{query_as, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; -use crate::templates::CreateUser; +use crate::{templates::CreateUser, User}; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; const ID_QUERY: &str = "select * from witches where id = $1"; -#[derive(Debug, Default, Clone, PartialEq, Eq, sqlx::FromRow)] -pub struct User { - pub id: Uuid, - pub username: String, - pub displayname: Option, - pub email: Option, - pub last_seen: Option, - pub(crate) pwhash: String, -} - -impl Display for User { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let uname = &self.username; - let dname = if let Some(ref n) = self.displayname { - n - } else { - "" - }; - let email = if let Some(ref e) = self.email { e } else { "" }; - write!(f, "Username: {uname}\nDisplayname: {dname}\nEmail: {email}") - } -} - //-************************************************************************ // Result types for user creation //-************************************************************************ @@ -97,7 +72,7 @@ pub async fn get_create_user() -> CreateUser { pub async fn post_create_user( State(pool): State, Form(signup): Form, -) -> Result { +) -> Result { let username = &signup.username; let displayname = &signup.displayname; let email = &signup.email; @@ -155,7 +130,7 @@ pub async fn post_create_user( let id = user.id.as_simple().to_string(); let location = format!("/signup_success/{id}"); - let resp = axum::response::Redirect::temporary(&location).into_response(); + let resp = axum::response::Redirect::temporary(&location); Ok(resp) } diff --git a/src/users.rs b/src/users.rs new file mode 100644 index 0000000..4ef50fd --- /dev/null +++ b/src/users.rs @@ -0,0 +1,49 @@ +use std::fmt::Display; + +use axum_login::{secrecy::SecretVec, AuthUser}; +use sqlx::SqlitePool; +use uuid::Uuid; + +const USERNAME_QUERY: &str = "select * from witches where username = $1"; + +#[derive(Debug, Default, Clone, PartialEq, Eq, sqlx::FromRow)] +pub struct User { + pub id: Uuid, + pub username: String, + pub displayname: Option, + pub email: Option, + pub last_seen: Option, + pub(crate) pwhash: String, +} + +impl Display for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let uname = &self.username; + let dname = if let Some(ref n) = self.displayname { + n + } else { + "" + }; + let email = if let Some(ref e) = self.email { e } else { "" }; + write!(f, "Username: {uname}\nDisplayname: {dname}\nEmail: {email}") + } +} + +impl AuthUser for User { + fn get_id(&self) -> Uuid { + self.id + } + + fn get_password_hash(&self) -> SecretVec { + SecretVec::new(self.pwhash.as_bytes().to_vec()) + } +} + +impl User { + pub async fn get(username: &str, db: &SqlitePool) -> Result { + sqlx::query_as(USERNAME_QUERY) + .bind(username) + .fetch_one(db) + .await + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..1b15b86 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,3 @@ +pub fn form_decode(input: &str, err: E) -> Result { + Ok(urlencoding::decode(input).map_err(|_| err)?.into_owned()) +} diff --git a/templates/login_get.html b/templates/login_get.html index e69de29..ea7a1a7 100644 --- a/templates/login_get.html +++ b/templates/login_get.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %}Login to Witch Watch, Bish{% endblock %} + +{% block content %} + +

+

+ +
+ +
+ +
+

+ +{% endblock %} From 5de4b9994c586fc508bbdb5edd5bba8de841ed42 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Mon, 29 May 2023 11:13:12 -0700 Subject: [PATCH 12/12] stub out logout --- src/login.rs | 8 ++++++++ src/main.rs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/login.rs b/src/login.rs index 4923679..d9d3603 100644 --- a/src/login.rs +++ b/src/login.rs @@ -97,3 +97,11 @@ pub async fn post_login( pub async fn get_login() -> impl IntoResponse { LoginGet::default() } + +pub async fn get_logout() -> impl IntoResponse { + todo!() +} + +pub async fn post_logout() -> impl IntoResponse { + todo!() +} diff --git a/src/main.rs b/src/main.rs index 57342f4..39903e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use witch_watch::{ db, generic_handlers::{handle_slash, handle_slash_redir}, - login::{get_login, post_login}, + login::{get_login, get_logout, post_login, post_logout}, session_store::SqliteSessionStore, signup::{get_create_user, handle_signup_success, post_create_user}, User, @@ -52,6 +52,7 @@ async fn main() { get(handle_signup_success).post(handle_signup_success), ) .route("/login", get(get_login).post(post_login)) + .route("/logout", get(get_logout).post(post_logout)) .fallback(handle_slash_redir) .layer(auth_layer) .layer(session_layer)