diff --git a/Cargo.lock b/Cargo.lock index b3392f61b0..50145adb42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2101,6 +2101,7 @@ dependencies = [ "serde_derive", "serde_json", "sqlx", + "sqlx_mysql_helpers", "tokens", "toml", ] @@ -6322,6 +6323,14 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "sqlx_mysql_helpers" +version = "0.0.1" +dependencies = [ + "errors", + "sqlx", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -7750,6 +7759,7 @@ dependencies = [ "database_queries", "dotenv", "enums", + "errors", "hmac 0.10.1", "http_server_common", "jwt", @@ -7767,6 +7777,7 @@ dependencies = [ "serde_json", "sha2 0.9.5", "sqlx", + "sqlx_mysql_helpers", "time 0.2.26", "tokens", "toml", diff --git a/Cargo.toml b/Cargo.toml index c52346b3cc..41dc77b656 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "crates/lib/media", "crates/lib/redis_common", "crates/lib/sqlite_queries", + "crates/lib/sqlx_mysql_helpers", "crates/lib/subprocess_common", "crates/lib/tts_common", "crates/lib/url_config", diff --git a/crates/lib/database_queries/Cargo.toml b/crates/lib/database_queries/Cargo.toml index 3218d97956..31035bbf4b 100644 --- a/crates/lib/database_queries/Cargo.toml +++ b/crates/lib/database_queries/Cargo.toml @@ -21,6 +21,7 @@ container_common = { path = "../container_common" } enums = { path = "../../_types/enums" } errors = { path = "../errors" } reusable_types = { path = "../../_types/reusable_types" } +sqlx_mysql_helpers = { path = "../sqlx_mysql_helpers" } tokens = { path = "../../_types/tokens" } # External diff --git a/crates/lib/database_queries/src/queries/users/user_sessions/get_user_session_by_token_light.rs b/crates/lib/database_queries/src/queries/users/user_sessions/get_user_session_by_token_light.rs index 61cb18c558..62a4c69bdd 100644 --- a/crates/lib/database_queries/src/queries/users/user_sessions/get_user_session_by_token_light.rs +++ b/crates/lib/database_queries/src/queries/users/user_sessions/get_user_session_by_token_light.rs @@ -5,8 +5,7 @@ use anyhow::anyhow; use errors::AnyhowResult; use log::warn; -use sqlx::MySql; -use sqlx::pool::PoolConnection; +use sqlx::{Executor, MySql}; #[derive(Clone, Serialize, Deserialize)] pub struct SessionRecord { @@ -14,12 +13,12 @@ pub struct SessionRecord { pub user_token: String, } -pub async fn get_user_session_by_token_light( - mysql_connection: &mut PoolConnection, +pub async fn get_user_session_by_token_light<'e, 'c : 'e, E>( + mysql_executor: E, session_token: &str, -) -> AnyhowResult> { - - // NB: Lookup failure is Err(RowNotFound). +) -> AnyhowResult> + where E: 'e + Executor<'c, Database = MySql> +{ let maybe_session_record = sqlx::query_as!( SessionRecord, r#" @@ -32,7 +31,7 @@ AND deleted_at IS NULL "#, session_token, ) - .fetch_one(mysql_connection) + .fetch_one(mysql_executor) .await; match maybe_session_record { diff --git a/crates/lib/sqlx_mysql_helpers/Cargo.toml b/crates/lib/sqlx_mysql_helpers/Cargo.toml new file mode 100644 index 0000000000..25bd486fd3 --- /dev/null +++ b/crates/lib/sqlx_mysql_helpers/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sqlx_mysql_helpers" +edition = "2018" +version = "0.0.1" +authors = [ + "Brandon Thomas ", + "Brandon Thomas ", +] +publish = false + +[lib] +name = "sqlx_mysql_helpers" +path = "src/lib.rs" + +[dependencies] +# Internal +errors = { path = "../errors" } + +# MySql Database +# "offline" supports typesafe builds without a live database +# "rustls" doesn't appear to work with IP addresses, per: +# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828 +sqlx = { version = "0.6.2", features = [ "mysql", "runtime-actix-native-tls", "offline", "chrono" ] } + +[dev-dependencies] +# None yet diff --git a/crates/lib/sqlx_mysql_helpers/src/any_connection.rs b/crates/lib/sqlx_mysql_helpers/src/any_connection.rs new file mode 100644 index 0000000000..cea51ad650 --- /dev/null +++ b/crates/lib/sqlx_mysql_helpers/src/any_connection.rs @@ -0,0 +1,78 @@ +use sqlx::{MySql, MySqlPool}; +use sqlx::pool::PoolConnection; +use errors::AnyhowResult; + +/// Pass a handle to MySql without caring about the type. +pub enum MySqlHandle { + ConnectionPool(MySqlPool), + Connection(PoolConnection), +} + +/// Pass a handle to MySql without caring about the type. +/// NB: The connection is typically owned, whereas the connection pool is not. +pub enum MySqlHandleMixedRef<'a> { + ConnectionPool(&'a MySqlPool), + Connection(PoolConnection), +} + +/// Pass a handle to MySql without caring about the type. +pub enum MySqlHandleRef<'a> { + ConnectionPool(&'a MySqlPool), + Connection(&'a mut PoolConnection), +} + +//impl MySqlHandle { +// pub fn as_ref(&mut self) -> MySqlHandleRef { +// match self { +// Self::ConnectionPool(inner) => MySqlHandleRef::ConnectionPool(inner), +// Self::Connection(inner) => MySqlHandleRef::Connection(inner), +// } +// } +//} + +impl <'a> MySqlHandleMixedRef<'a> { + /// Consume the handle and return a connection. + pub async fn to_discrete_connection(self) -> AnyhowResult> { + match self { + Self::Connection(connection) => Ok(connection), + Self::ConnectionPool(pool) => Ok(pool.acquire().await?), + } + } +} + +//impl <'a> MySqlHandleRef<'a> { +// /// Consume the handle and return a connection. +// pub async fn to_discrete_connection(self) -> AnyhowResult<()> { +// let _r = match self { +// Self::Connection(connection) => Ok(connection), +// Self::ConnectionPool(pool) => { +// let connection = pool.acquire().await?; +// Ok(connection) +// }, +// }; +// +// Ok(()) +// } +//} +// +///// Exists to hold ownership (if necessary) +//pub struct MySqlConnectionWrapper<'a> { +// maybe_owned: Option>, +// reference: &'a mut PoolConnection, +//} + + + +// TODO: +//impl <'a> MySqlConnectionRef<'a> { +// pub async fn get_pool_connection(&'a mut self) -> AnyhowResult<&'a PoolConnection> { +// match self { +// MySqlConnectionRef::ConnectionPool(pool) => { +// let connection = pool.acquire().await?; +// Ok(connection) +// } +// MySqlConnectionRef::Connection(inner) => Ok(inner), +// } +// +// } +//} diff --git a/crates/lib/sqlx_mysql_helpers/src/lib.rs b/crates/lib/sqlx_mysql_helpers/src/lib.rs new file mode 100644 index 0000000000..db53bf0c13 --- /dev/null +++ b/crates/lib/sqlx_mysql_helpers/src/lib.rs @@ -0,0 +1,20 @@ +//! sqlx_mysql_helpers +//! +//! Collect reusable Sqlx functions and patterns, particularly for MySQL databases. +//! + +// Never allow these +#![forbid(private_in_public)] +#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things + +// Okay to toggle +#![forbid(unreachable_patterns)] +#![forbid(unused_imports)] +#![forbid(unused_mut)] +#![forbid(unused_variables)] + +// Always allow +#![allow(dead_code)] +#![allow(non_snake_case)] + +pub mod any_connection; \ No newline at end of file diff --git a/crates/service/plugins/users/Cargo.toml b/crates/service/plugins/users/Cargo.toml index 4cdd0b3f78..0c7eb97c7d 100644 --- a/crates/service/plugins/users/Cargo.toml +++ b/crates/service/plugins/users/Cargo.toml @@ -18,10 +18,12 @@ path = "src/lib.rs" container_common = { path = "../../../lib/container_common" } database_queries = { path = "../../../lib/database_queries" } enums = { path = "../../../_types/enums" } +errors = { path = "../../../lib/errors" } http_server_common = { path = "../../../lib/http_server_common" } redis_caching = { path = "../../../lib/caching/redis_caching" } redis_common = { path = "../../../lib/redis_common" } reusable_types = { path = "../../../_types/reusable_types" } +sqlx_mysql_helpers = { path = "../../../lib/sqlx_mysql_helpers" } tokens = { path = "../../../_types/tokens" } user_input_common = { path = "../../../lib/user_input_common" } diff --git a/crates/service/plugins/users/src/utils/session_checker.rs b/crates/service/plugins/users/src/utils/session_checker.rs index 4f119c1b6e..26e195be06 100644 --- a/crates/service/plugins/users/src/utils/session_checker.rs +++ b/crates/service/plugins/users/src/utils/session_checker.rs @@ -1,20 +1,21 @@ // NB: Incrementally getting rid of build warnings... -#![forbid(unused_imports)] -#![forbid(unused_mut)] -#![forbid(unused_variables)] +//#![forbid(unused_imports)] +//#![forbid(unused_mut)] +//#![forbid(unused_variables)] use actix_web::HttpRequest; -use log::warn; use container_common::anyhow_result::AnyhowResult; use crate::utils::session_cookie_manager::SessionCookieManager; use crate::utils::user_session_extended::{UserSessionExtended, UserSessionPreferences, UserSessionPremiumPlanInfo, UserSessionRoleAndPermissions, UserSessionSubscriptionPlan, UserSessionUserDetails}; use database_queries::queries::users::user_sessions::get_user_session_by_token::{get_user_session_by_token, SessionUserRecord}; use database_queries::queries::users::user_sessions::get_user_session_by_token_light::{get_user_session_by_token_light, SessionRecord}; use database_queries::queries::users::user_subscriptions::list_active_user_subscriptions::list_active_user_subscriptions; +use log::warn; use redis_caching::redis_ttl_cache::{RedisTtlCache, RedisTtlCacheConnection}; -use sqlx::pool::PoolConnection; -use sqlx::{MySqlPool, MySql}; use redis_common::redis_cache_keys::RedisCacheKeys; +use sqlx::pool::PoolConnection; +use sqlx::{MySqlPool, MySql, Acquire, Executor}; +use sqlx_mysql_helpers::any_connection::MySqlHandleMixedRef; #[derive(Clone)] pub struct SessionChecker { @@ -48,8 +49,8 @@ impl SessionChecker { pool: &MySqlPool ) -> AnyhowResult> { - let mut connection = pool.acquire().await?; - self.maybe_get_session_light_from_connection(request, &mut connection).await + //let mysql_handle = MySqlHandleMixedRef::ConnectionPool(pool); + self.do_session_light_lookup_and_cookie_decode(request, pool).await } pub async fn maybe_get_session_light_from_connection( @@ -58,42 +59,42 @@ impl SessionChecker { mysql_connection: &mut PoolConnection, ) -> AnyhowResult> { - let session_token = match self.cookie_manager.decode_session_token_from_request(request)? { - None => return Ok(None), - Some(session_token) => session_token, - }; - - get_user_session_by_token_light(mysql_connection, &session_token).await + //let mysql_handle = MySqlHandleMixedRef::Connection(mysql_connection); + self.do_session_light_lookup_and_cookie_decode(request, mysql_connection).await } - async fn maybe_get_session_light_from_connection_cached( + async fn do_session_light_lookup_and_cookie_decode<'e, 'c : 'e, E>( &self, request: &HttpRequest, - mysql_connection: &mut PoolConnection, + //mysql_handle: MySqlHandleMixedRef<'_>, + mysql_executor: E, ) -> AnyhowResult> + where E: 'e + Executor<'c, Database = MySql> { let session_token = match self.cookie_manager.decode_session_token_from_request(request)? { None => return Ok(None), Some(session_token) => session_token, }; - self.do_session_light_lookup(mysql_connection, &session_token).await + self.do_session_light_lookup(mysql_executor, &session_token).await } - async fn do_session_light_lookup( + async fn do_session_light_lookup<'e, 'c : 'e, E>( &self, - mysql_connection: &mut PoolConnection, + //mysql_handle: MySqlHandleMixedRef<'_>, + mysql_executor: E, session_token: &str, ) -> AnyhowResult> + where E: 'e + Executor<'c, Database = MySql> { match self.maybe_get_redis_cache_connection() { None => { - get_user_session_by_token_light(mysql_connection, session_token).await + get_user_session_by_token_light(mysql_executor, session_token).await } Some(mut redis_ttl_cache) => { let cache_key = RedisCacheKeys::session_record_light(session_token); redis_ttl_cache.lazy_load_if_not_cached(&cache_key, move || { - get_user_session_by_token_light(mysql_connection, session_token) + get_user_session_by_token_light(mysql_executor, session_token) }).await } }