From d047fa50a9a87a5503563c68fa2d0807098050e6 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 1 Sep 2022 17:25:13 -0400 Subject: [PATCH] Successfully move one "user" endpoint into new package --- Cargo.lock | 3 + crates/component_subsystem/users/Cargo.toml | 8 + .../users/src/endpoints/mod.rs | 1 + .../users/src/endpoints}/session_info.rs | 21 +- crates/component_subsystem/users/src/lib.rs | 3 + .../users/src/utils/mod.rs | 1 + .../users/src/utils/session_checker.rs | 78 +++++ .../src/helpers/boolean_converters.rs | 26 +- .../user_sessions/get_session_by_token.rs | 231 +++++++++++++ .../get_session_by_token_light.rs | 51 +++ .../src/queries/users/user_sessions/mod.rs | 4 +- .../endpoints/tts/get_tts_result.rs | 1 - .../src/http_server/endpoints/users/mod.rs | 1 - .../src/http_server/web_utils/mod.rs | 3 +- .../http_server/web_utils/session_checker.rs | 324 ------------------ crates/service/storyteller_web/src/main.rs | 5 +- crates/service/storyteller_web/src/routes.rs | 2 +- .../storyteller_web/src/server_state.rs | 2 +- 18 files changed, 417 insertions(+), 348 deletions(-) create mode 100644 crates/component_subsystem/users/src/endpoints/mod.rs rename crates/{service/storyteller_web/src/http_server/endpoints/users => component_subsystem/users/src/endpoints}/session_info.rs (92%) create mode 100644 crates/component_subsystem/users/src/utils/session_checker.rs create mode 100644 crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token.rs create mode 100644 crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token_light.rs delete mode 100644 crates/service/storyteller_web/src/http_server/web_utils/session_checker.rs diff --git a/Cargo.lock b/Cargo.lock index 39276ba701..fdc416ede4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4944,8 +4944,10 @@ dependencies = [ "base64 0.13.0", "chrono", "container_common", + "database_queries", "dotenv", "hmac 0.10.1", + "http_server_common", "jwt", "log", "once_cell", @@ -4954,6 +4956,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2", + "sqlx", "time 0.2.26", "toml", ] diff --git a/crates/component_subsystem/users/Cargo.toml b/crates/component_subsystem/users/Cargo.toml index 572aac6ebd..5e04c00a0e 100644 --- a/crates/component_subsystem/users/Cargo.toml +++ b/crates/component_subsystem/users/Cargo.toml @@ -16,6 +16,8 @@ path = "src/lib.rs" # Internal container_common = { path = "../../lib/container_common" } +database_queries = { path = "../../lib/database_queries" } +http_server_common = { path = "../../lib/http_server_common" } # External anyhow = "1.0.40" @@ -40,3 +42,9 @@ serde = "1.0.125" serde_derive = "1.0.125" serde_json = "1.0.64" toml = "0.5.8" + +# 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.5", features = [ "mysql", "runtime-actix-native-tls", "offline", "chrono" ] } diff --git a/crates/component_subsystem/users/src/endpoints/mod.rs b/crates/component_subsystem/users/src/endpoints/mod.rs new file mode 100644 index 0000000000..bc4a5e7b54 --- /dev/null +++ b/crates/component_subsystem/users/src/endpoints/mod.rs @@ -0,0 +1 @@ +pub mod session_info; \ No newline at end of file diff --git a/crates/service/storyteller_web/src/http_server/endpoints/users/session_info.rs b/crates/component_subsystem/users/src/endpoints/session_info.rs similarity index 92% rename from crates/service/storyteller_web/src/http_server/endpoints/users/session_info.rs rename to crates/component_subsystem/users/src/endpoints/session_info.rs index 9fb1e4792d..9daecbd98c 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/users/session_info.rs +++ b/crates/component_subsystem/users/src/endpoints/session_info.rs @@ -1,16 +1,16 @@ // 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::error::ResponseError; use actix_web::http::StatusCode; use actix_web::{web, HttpResponse, HttpRequest}; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::server_state::ServerState; +use crate::utils::session_checker::SessionChecker; +use http_server_common::response::response_error_helpers::to_simple_json_error; use log::warn; +use sqlx::MySqlPool; use std::fmt; -use std::sync::Arc; #[derive(Serialize, Copy, Clone)] #[serde(rename_all = "snake_case")] @@ -104,17 +104,18 @@ impl fmt::Display for SessionInfoError { pub async fn session_info_handler( http_request: HttpRequest, - server_state: web::Data>) -> Result + mysql_pool: web::Data, + session_checker: web::Data, +) -> Result { - let mut mysql_connection = server_state.mysql_pool.acquire() + let mut mysql_connection = mysql_pool.acquire() .await .map_err(|e| { warn!("Could not acquire DB pool: {:?}", e); SessionInfoError::ServerError })?; - let maybe_user_session = server_state - .session_checker + let maybe_user_session = session_checker .maybe_get_user_session_from_connection(&http_request, &mut mysql_connection) .await .map_err(|e| { diff --git a/crates/component_subsystem/users/src/lib.rs b/crates/component_subsystem/users/src/lib.rs index fab870e3ea..d6a60d0e4e 100644 --- a/crates/component_subsystem/users/src/lib.rs +++ b/crates/component_subsystem/users/src/lib.rs @@ -1 +1,4 @@ +#[macro_use] extern crate serde_derive; + +pub mod endpoints; pub mod utils; \ No newline at end of file diff --git a/crates/component_subsystem/users/src/utils/mod.rs b/crates/component_subsystem/users/src/utils/mod.rs index 11a4cc04ea..e9c3613457 100644 --- a/crates/component_subsystem/users/src/utils/mod.rs +++ b/crates/component_subsystem/users/src/utils/mod.rs @@ -1 +1,2 @@ +pub mod session_checker; pub mod session_cookie_manager; \ No newline at end of file diff --git a/crates/component_subsystem/users/src/utils/session_checker.rs b/crates/component_subsystem/users/src/utils/session_checker.rs new file mode 100644 index 0000000000..d7d0f1c47e --- /dev/null +++ b/crates/component_subsystem/users/src/utils/session_checker.rs @@ -0,0 +1,78 @@ +// NB: Incrementally getting rid of build warnings... +#![forbid(unused_imports)] +#![forbid(unused_mut)] +#![forbid(unused_variables)] + +use actix_web::HttpRequest; +use container_common::anyhow_result::AnyhowResult; +use crate::utils::session_cookie_manager::SessionCookieManager; +use database_queries::queries::users::user_sessions::get_session_by_token::{get_session_by_token, SessionUserRecord}; +use database_queries::queries::users::user_sessions::get_session_by_token_light::{get_session_by_token_light, SessionRecord}; +use sqlx::pool::PoolConnection; +use sqlx::{MySqlPool, MySql}; + +#[derive(Clone)] +pub struct SessionChecker { + cookie_manager: SessionCookieManager, +} + +impl SessionChecker { + + pub fn new(cookie_manager: &SessionCookieManager) -> Self { + Self { + cookie_manager: cookie_manager.clone(), + } + } + + #[deprecated = "Use the PoolConnection method instead of the MySqlPool one."] + pub async fn maybe_get_session( + &self, + request: &HttpRequest, + pool: &MySqlPool + ) -> AnyhowResult> + { + let mut connection = pool.acquire().await?; + self.maybe_get_session_from_connection(request, &mut connection).await + } + + pub async fn maybe_get_session_from_connection( + &self, + request: &HttpRequest, + 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_session_by_token_light(mysql_connection, &session_token).await + } + + + #[deprecated = "Use the PoolConnection method instead of the MySqlPool one."] + pub async fn maybe_get_user_session( + &self, + request: &HttpRequest, + pool: &MySqlPool, + ) -> AnyhowResult> + { + let mut connection = pool.acquire().await?; + self.maybe_get_user_session_from_connection(request, &mut connection).await + } + + pub async fn maybe_get_user_session_from_connection( + &self, + request: &HttpRequest, + 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_session_by_token(mysql_connection, &session_token).await + } +} + diff --git a/crates/lib/database_queries/src/helpers/boolean_converters.rs b/crates/lib/database_queries/src/helpers/boolean_converters.rs index 45d122f3c7..8e24071789 100644 --- a/crates/lib/database_queries/src/helpers/boolean_converters.rs +++ b/crates/lib/database_queries/src/helpers/boolean_converters.rs @@ -15,6 +15,16 @@ pub fn nullable_i8_to_bool(value: Option, default_value: bool) -> bool { .unwrap_or(default_value) } +pub fn nullable_i8_to_bool_default_false(value: Option) -> bool { + match value { + None => false, + Some(v) => match v { + 0 => false, + _ => true, + } + } +} + /// Bool conversion, but retain nulls. pub fn nullable_i8_to_optional_bool(value: Option) -> Option { value.map(|v| i8_to_bool(v)) @@ -22,11 +32,7 @@ pub fn nullable_i8_to_optional_bool(value: Option) -> Option { #[cfg(test)] mod tests { - use crate::helpers::boolean_converters::{ - i8_to_bool, - nullable_i8_to_bool, - nullable_i8_to_optional_bool - }; + use crate::helpers::boolean_converters::{i8_to_bool, nullable_i8_to_bool, nullable_i8_to_bool_default_false, nullable_i8_to_optional_bool}; #[test] fn test_i8_to_bool() { @@ -52,6 +58,16 @@ mod tests { assert_eq!(nullable_i8_to_bool(Some(-100), true), true); } + #[test] + fn test_nullable_i8_to_bool_default_false() { + assert_eq!(nullable_i8_to_bool_default_false(None), false); + assert_eq!(nullable_i8_to_bool_default_false(Some(0)), false); + + assert_eq!(nullable_i8_to_bool_default_false(Some(1)), true); + assert_eq!(nullable_i8_to_bool_default_false(Some(100)), true); + assert_eq!(nullable_i8_to_bool_default_false(Some(-100)), true); + } + #[test] fn test_nullable_i8_to_optional_bool() { assert_eq!(nullable_i8_to_optional_bool(None), None); diff --git a/crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token.rs b/crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token.rs new file mode 100644 index 0000000000..6fe4e3c8c9 --- /dev/null +++ b/crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token.rs @@ -0,0 +1,231 @@ +// NB: Incrementally getting rid of build warnings... +#![forbid(unused_imports)] +#![forbid(unused_mut)] + +use anyhow::anyhow; +use container_common::anyhow_result::AnyhowResult; +use crate::column_types::record_visibility::RecordVisibility; +use crate::helpers::boolean_converters::{nullable_i8_to_optional_bool, i8_to_bool, nullable_i8_to_bool_default_false}; +use log::warn; +use sqlx::MySql; +use sqlx::pool::PoolConnection; + +pub struct SessionUserRecord { + pub user_token: String, + pub username: String, + pub display_name: String, + + pub email_address: String, + pub email_confirmed: bool, + pub email_gravatar_hash: String, + + // ===== PREFERENCES ===== // + + pub disable_gravatar: bool, + pub auto_play_audio_preference: Option, + pub preferred_tts_result_visibility: RecordVisibility, + pub preferred_w2l_result_visibility: RecordVisibility, + pub auto_play_video_preference: Option, + + // ===== ROLE ===== // + + pub user_role_slug: String, + pub is_banned: bool, + + // ===== PERMISSIONS FLAGS ===== // + + // Usage + pub can_use_tts: bool, + pub can_use_w2l: bool, + pub can_delete_own_tts_results: bool, + pub can_delete_own_w2l_results: bool, + pub can_delete_own_account: bool, + + // Contribution + pub can_upload_tts_models: bool, + pub can_upload_w2l_templates: bool, + pub can_delete_own_tts_models: bool, + pub can_delete_own_w2l_templates: bool, + + // Moderation + pub can_approve_w2l_templates: bool, + pub can_edit_other_users_profiles: bool, + pub can_edit_other_users_tts_models: bool, + pub can_edit_other_users_w2l_templates: bool, + pub can_delete_other_users_tts_models: bool, + pub can_delete_other_users_tts_results: bool, + pub can_delete_other_users_w2l_templates: bool, + pub can_delete_other_users_w2l_results: bool, + pub can_ban_users: bool, + pub can_delete_users: bool, +} + +pub async fn get_session_by_token( + mysql_connection: &mut PoolConnection, + session_token: &str, +) -> AnyhowResult> { + + // NB: Lookup failure is Err(RowNotFound). + let maybe_user_record = sqlx::query_as!( + SessionUserRawDbRecord, + r#" +SELECT + users.token as user_token, + users.username, + users.display_name, + + users.email_address, + users.email_confirmed, + users.email_gravatar_hash, + + users.disable_gravatar, + users.auto_play_audio_preference, + users.auto_play_video_preference, + users.preferred_tts_result_visibility as `preferred_tts_result_visibility: crate::column_types::record_visibility::RecordVisibility`, + users.preferred_w2l_result_visibility as `preferred_w2l_result_visibility: crate::column_types::record_visibility::RecordVisibility`, + + users.user_role_slug, + users.is_banned, + + user_roles.can_use_tts, + user_roles.can_use_w2l, + user_roles.can_delete_own_tts_results, + user_roles.can_delete_own_w2l_results, + user_roles.can_delete_own_account, + + user_roles.can_upload_tts_models, + user_roles.can_upload_w2l_templates, + user_roles.can_delete_own_tts_models, + user_roles.can_delete_own_w2l_templates, + + user_roles.can_approve_w2l_templates, + user_roles.can_edit_other_users_profiles, + user_roles.can_edit_other_users_tts_models, + user_roles.can_edit_other_users_w2l_templates, + user_roles.can_delete_other_users_tts_models, + user_roles.can_delete_other_users_tts_results, + user_roles.can_delete_other_users_w2l_templates, + user_roles.can_delete_other_users_w2l_results, + user_roles.can_ban_users, + user_roles.can_delete_users + +FROM users +LEFT OUTER JOIN user_sessions + ON users.token = user_sessions.user_token +LEFT OUTER JOIN user_roles + ON users.user_role_slug = user_roles.slug +WHERE user_sessions.token = ? + AND user_sessions.deleted_at IS NULL + AND users.user_deleted_at IS NULL + AND users.mod_deleted_at IS NULL + "#, + session_token, + ) + .fetch_one(mysql_connection) + .await; // TODO: This will return error if it doesn't exist + + match maybe_user_record { + Ok(raw_user_record) => { + let result_user_record = SessionUserRecord { + user_token: raw_user_record.user_token, + username: raw_user_record.username, + display_name: raw_user_record.display_name, + email_address: raw_user_record.email_address, + email_confirmed: i8_to_bool(raw_user_record.email_confirmed), + email_gravatar_hash: raw_user_record.email_gravatar_hash, + // Preference + disable_gravatar: i8_to_bool(raw_user_record.disable_gravatar), + auto_play_audio_preference: nullable_i8_to_optional_bool(raw_user_record.auto_play_audio_preference), + auto_play_video_preference: nullable_i8_to_optional_bool(raw_user_record.auto_play_video_preference), + user_role_slug: raw_user_record.user_role_slug, + preferred_tts_result_visibility: raw_user_record.preferred_tts_result_visibility, + preferred_w2l_result_visibility: raw_user_record.preferred_w2l_result_visibility, + + is_banned: i8_to_bool(raw_user_record.is_banned), + + // Usage + can_use_tts: nullable_i8_to_bool_default_false(raw_user_record.can_use_tts), + can_use_w2l: nullable_i8_to_bool_default_false(raw_user_record.can_use_w2l), + can_delete_own_tts_results: nullable_i8_to_bool_default_false(raw_user_record.can_delete_own_tts_results), + can_delete_own_w2l_results: nullable_i8_to_bool_default_false(raw_user_record.can_delete_own_w2l_results), + can_delete_own_account: nullable_i8_to_bool_default_false(raw_user_record.can_delete_own_account), + // Contribution + can_upload_tts_models: nullable_i8_to_bool_default_false(raw_user_record.can_upload_tts_models), + can_upload_w2l_templates: nullable_i8_to_bool_default_false(raw_user_record.can_upload_w2l_templates), + can_delete_own_tts_models: nullable_i8_to_bool_default_false(raw_user_record.can_delete_own_tts_models), + can_delete_own_w2l_templates: nullable_i8_to_bool_default_false(raw_user_record.can_delete_own_w2l_templates), + // Moderation + can_approve_w2l_templates: nullable_i8_to_bool_default_false(raw_user_record.can_approve_w2l_templates), + can_edit_other_users_profiles: nullable_i8_to_bool_default_false(raw_user_record.can_edit_other_users_profiles), + can_edit_other_users_tts_models: nullable_i8_to_bool_default_false(raw_user_record.can_edit_other_users_tts_models), + can_edit_other_users_w2l_templates: nullable_i8_to_bool_default_false(raw_user_record.can_edit_other_users_w2l_templates), + can_delete_other_users_tts_models: nullable_i8_to_bool_default_false(raw_user_record.can_delete_other_users_tts_models), + can_delete_other_users_tts_results: nullable_i8_to_bool_default_false(raw_user_record.can_delete_other_users_tts_results), + can_delete_other_users_w2l_templates: nullable_i8_to_bool_default_false(raw_user_record.can_delete_other_users_w2l_templates), + can_delete_other_users_w2l_results: nullable_i8_to_bool_default_false(raw_user_record.can_delete_other_users_w2l_results), + can_ban_users: nullable_i8_to_bool_default_false(raw_user_record.can_ban_users), + can_delete_users: nullable_i8_to_bool_default_false(raw_user_record.can_delete_users) , + }; + + Ok(Some(result_user_record)) + }, + Err(err) => { + match err { + RowNotFound => { + warn!("Valid cookie; invalid session: {}", session_token); + Ok(None) + }, + _ => { + warn!("Session query error: {:?}", err); + Err(anyhow!("session query error: {:?}", err)) + } + } + } + } + +} + +struct SessionUserRawDbRecord { + user_token: String, + username: String, + display_name: String, + + email_address: String, + email_confirmed: i8, + email_gravatar_hash: String, + + disable_gravatar: i8, + auto_play_audio_preference: Option, + auto_play_video_preference: Option, + preferred_tts_result_visibility: RecordVisibility, + preferred_w2l_result_visibility: RecordVisibility, + + user_role_slug: String, + is_banned: i8, + + // NB: These are `Option` due to the JOIN not being compile-time assured. + // Usage + can_use_tts: Option, + can_use_w2l: Option, + can_delete_own_tts_results: Option, + can_delete_own_w2l_results: Option, + can_delete_own_account: Option, + + // Contribution + can_upload_tts_models: Option, + can_upload_w2l_templates: Option, + can_delete_own_tts_models: Option, + can_delete_own_w2l_templates: Option, + + // Moderation + can_approve_w2l_templates: Option, + can_edit_other_users_profiles: Option, + can_edit_other_users_tts_models: Option, + can_edit_other_users_w2l_templates: Option, + can_delete_other_users_tts_models: Option, + can_delete_other_users_tts_results: Option, + can_delete_other_users_w2l_templates: Option, + can_delete_other_users_w2l_results: Option, + can_ban_users: Option, + can_delete_users: Option, +} diff --git a/crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token_light.rs b/crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token_light.rs new file mode 100644 index 0000000000..eaa947757c --- /dev/null +++ b/crates/lib/database_queries/src/queries/users/user_sessions/get_session_by_token_light.rs @@ -0,0 +1,51 @@ +// NB: Incrementally getting rid of build warnings... +#![forbid(unused_imports)] +#![forbid(unused_mut)] + +use anyhow::anyhow; +use container_common::anyhow_result::AnyhowResult; +use log::warn; +use sqlx::MySql; +use sqlx::pool::PoolConnection; + +pub struct SessionRecord { + pub session_token: String, + pub user_token: String, +} +pub async fn get_session_by_token_light( + mysql_connection: &mut PoolConnection, + session_token: &str, +) -> AnyhowResult> { + + // NB: Lookup failure is Err(RowNotFound). + let maybe_session_record = sqlx::query_as!( + SessionRecord, + r#" +SELECT + token as session_token, + user_token +FROM user_sessions +WHERE token = ? +AND deleted_at IS NULL + "#, + session_token, + ) + .fetch_one(mysql_connection) + .await; + + match maybe_session_record { + Ok(session_record) => Ok(Some(session_record)), + Err(err) => { + match err { + RowNotFound => { + warn!("Valid cookie; invalid session: {}", session_token); + Ok(None) + }, + _ => { + warn!("Session query error: {:?}", err); + Err(anyhow!("session query error: {:?}", err)) + } + } + } + } +} diff --git a/crates/lib/database_queries/src/queries/users/user_sessions/mod.rs b/crates/lib/database_queries/src/queries/users/user_sessions/mod.rs index 6a3f6dcb74..342d3ce40c 100644 --- a/crates/lib/database_queries/src/queries/users/user_sessions/mod.rs +++ b/crates/lib/database_queries/src/queries/users/user_sessions/mod.rs @@ -1 +1,3 @@ -pub mod create_session; \ No newline at end of file +pub mod create_session; +pub mod get_session_by_token; +pub mod get_session_by_token_light; \ No newline at end of file diff --git a/crates/service/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs b/crates/service/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs index 3da2574f26..543f44f484 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs @@ -10,7 +10,6 @@ use chrono::{DateTime, Utc}; use crate::AnyhowResult; use crate::http_server::web_utils::ip_address::get_request_ip; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::session_checker::SessionRecord; use crate::server_state::ServerState; use crate::validations::passwords::validate_passwords; use crate::validations::username::validate_username; diff --git a/crates/service/storyteller_web/src/http_server/endpoints/users/mod.rs b/crates/service/storyteller_web/src/http_server/endpoints/users/mod.rs index 39f8c475ae..dd922ed6d8 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/users/mod.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/users/mod.rs @@ -8,4 +8,3 @@ pub mod list_user_w2l_inference_results; pub mod list_user_w2l_templates; pub mod login; pub mod logout; -pub mod session_info; diff --git a/crates/service/storyteller_web/src/http_server/web_utils/mod.rs b/crates/service/storyteller_web/src/http_server/web_utils/mod.rs index 583b527ba7..c5d99398e3 100644 --- a/crates/service/storyteller_web/src/http_server/web_utils/mod.rs +++ b/crates/service/storyteller_web/src/http_server/web_utils/mod.rs @@ -4,5 +4,4 @@ pub mod read_multipart_field_bytes; pub mod redis_rate_limiter; pub mod response_error_helpers; pub mod response_success_helpers; -pub mod serialize_as_json_error; -pub mod session_checker; \ No newline at end of file +pub mod serialize_as_json_error; \ No newline at end of file diff --git a/crates/service/storyteller_web/src/http_server/web_utils/session_checker.rs b/crates/service/storyteller_web/src/http_server/web_utils/session_checker.rs deleted file mode 100644 index c0d037d390..0000000000 --- a/crates/service/storyteller_web/src/http_server/web_utils/session_checker.rs +++ /dev/null @@ -1,324 +0,0 @@ -// NB: Incrementally getting rid of build warnings... -#![forbid(unused_imports)] -#![forbid(unused_mut)] -#![forbid(unused_variables)] - -use actix_web::HttpRequest; -use anyhow::anyhow; -use crate::AnyhowResult; -use database_queries::column_types::record_visibility::RecordVisibility; -use database_queries::helpers::boolean_converters::{nullable_i8_to_optional_bool, i8_to_bool}; -use log::warn; -use sqlx::error::Error::RowNotFound; -use sqlx::pool::PoolConnection; -use sqlx::{MySqlPool, MySql}; -use users_component::utils::session_cookie_manager::SessionCookieManager; - -#[derive(Clone)] -pub struct SessionChecker { - cookie_manager: SessionCookieManager, -} - -pub struct SessionRecord { - pub session_token: String, - pub user_token: String, -} - -pub struct SessionUserRecord { - pub user_token: String, - pub username: String, - pub display_name: String, - - pub email_address: String, - pub email_confirmed: bool, - pub email_gravatar_hash: String, - - // ===== PREFERENCES ===== // - - pub disable_gravatar: bool, - pub auto_play_audio_preference: Option, - pub preferred_tts_result_visibility: RecordVisibility, - pub preferred_w2l_result_visibility: RecordVisibility, - pub auto_play_video_preference: Option, - - // ===== ROLE ===== // - - pub user_role_slug: String, - pub is_banned: bool, - - // ===== PERMISSIONS FLAGS ===== // - - // Usage - pub can_use_tts: bool, - pub can_use_w2l: bool, - pub can_delete_own_tts_results: bool, - pub can_delete_own_w2l_results: bool, - pub can_delete_own_account: bool, - - // Contribution - pub can_upload_tts_models: bool, - pub can_upload_w2l_templates: bool, - pub can_delete_own_tts_models: bool, - pub can_delete_own_w2l_templates: bool, - - // Moderation - pub can_approve_w2l_templates: bool, - pub can_edit_other_users_profiles: bool, - pub can_edit_other_users_tts_models: bool, - pub can_edit_other_users_w2l_templates: bool, - pub can_delete_other_users_tts_models: bool, - pub can_delete_other_users_tts_results: bool, - pub can_delete_other_users_w2l_templates: bool, - pub can_delete_other_users_w2l_results: bool, - pub can_ban_users: bool, - pub can_delete_users: bool, -} - -pub struct SessionUserRawDbRecord { - pub user_token: String, - pub username: String, - pub display_name: String, - - pub email_address: String, - pub email_confirmed: i8, - pub email_gravatar_hash: String, - - pub disable_gravatar: i8, - pub auto_play_audio_preference: Option, - pub auto_play_video_preference: Option, - pub preferred_tts_result_visibility: RecordVisibility, - pub preferred_w2l_result_visibility: RecordVisibility, - - pub user_role_slug: String, - pub is_banned: i8, - - // NB: These are `Option` due to the JOIN not being compile-time assured. - // Usage - pub can_use_tts: Option, - pub can_use_w2l: Option, - pub can_delete_own_tts_results: Option, - pub can_delete_own_w2l_results: Option, - pub can_delete_own_account: Option, - - // Contribution - pub can_upload_tts_models: Option, - pub can_upload_w2l_templates: Option, - pub can_delete_own_tts_models: Option, - pub can_delete_own_w2l_templates: Option, - - // Moderation - pub can_approve_w2l_templates: Option, - pub can_edit_other_users_profiles: Option, - pub can_edit_other_users_tts_models: Option, - pub can_edit_other_users_w2l_templates: Option, - pub can_delete_other_users_tts_models: Option, - pub can_delete_other_users_tts_results: Option, - pub can_delete_other_users_w2l_templates: Option, - pub can_delete_other_users_w2l_results: Option, - pub can_ban_users: Option, - pub can_delete_users: Option, -} - -impl SessionChecker { - - pub fn new(cookie_manager: &SessionCookieManager) -> Self { - Self { - cookie_manager: cookie_manager.clone(), - } - } - - pub async fn maybe_get_session(&self, request: &HttpRequest, pool: &MySqlPool) - -> AnyhowResult> - { - let session_token = match self.cookie_manager.decode_session_token_from_request(request)? { - None => return Ok(None), - Some(session_token) => session_token, - }; - - // NB: Lookup failure is Err(RowNotFound). - let maybe_session_record = sqlx::query_as!( - SessionRecord, - r#" -SELECT - token as session_token, - user_token -FROM user_sessions -WHERE token = ? -AND deleted_at IS NULL - "#, - session_token.to_string(), - ) - .fetch_one(pool) - .await; // TODO: This will return error if it doesn't exist - - match maybe_session_record { - Ok(session_record) => { - Ok(Some(session_record)) - }, - Err(err) => { - match err { - RowNotFound => { - warn!("Valid cookie; invalid session: {}", session_token); - Ok(None) - }, - _ => { - warn!("Session query error: {:?}", err); - Err(anyhow!("session query error: {:?}", err)) - } - } - } - } - } - - #[deprecated = "Use the PoolConnection method instead of the MySqlPool one."] - pub async fn maybe_get_user_session( - &self, - request: &HttpRequest, - pool: &MySqlPool, - ) -> AnyhowResult> - { - let mut connection = pool.acquire().await?; - self.maybe_get_user_session_from_connection(request, &mut connection).await - } - - pub async fn maybe_get_user_session_from_connection( - &self, - request: &HttpRequest, - 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, - }; - - // NB: Lookup failure is Err(RowNotFound). - let maybe_user_record = sqlx::query_as!( - SessionUserRawDbRecord, - r#" -SELECT - users.token as user_token, - users.username, - users.display_name, - - users.email_address, - users.email_confirmed, - users.email_gravatar_hash, - - users.disable_gravatar, - users.auto_play_audio_preference, - users.auto_play_video_preference, - users.preferred_tts_result_visibility as `preferred_tts_result_visibility: database_queries::column_types::record_visibility::RecordVisibility`, - users.preferred_w2l_result_visibility as `preferred_w2l_result_visibility: database_queries::column_types::record_visibility::RecordVisibility`, - - users.user_role_slug, - users.is_banned, - - user_roles.can_use_tts, - user_roles.can_use_w2l, - user_roles.can_delete_own_tts_results, - user_roles.can_delete_own_w2l_results, - user_roles.can_delete_own_account, - - user_roles.can_upload_tts_models, - user_roles.can_upload_w2l_templates, - user_roles.can_delete_own_tts_models, - user_roles.can_delete_own_w2l_templates, - - user_roles.can_approve_w2l_templates, - user_roles.can_edit_other_users_profiles, - user_roles.can_edit_other_users_tts_models, - user_roles.can_edit_other_users_w2l_templates, - user_roles.can_delete_other_users_tts_models, - user_roles.can_delete_other_users_tts_results, - user_roles.can_delete_other_users_w2l_templates, - user_roles.can_delete_other_users_w2l_results, - user_roles.can_ban_users, - user_roles.can_delete_users - -FROM users -LEFT OUTER JOIN user_sessions - ON users.token = user_sessions.user_token -LEFT OUTER JOIN user_roles - ON users.user_role_slug = user_roles.slug -WHERE user_sessions.token = ? - AND user_sessions.deleted_at IS NULL - AND users.user_deleted_at IS NULL - AND users.mod_deleted_at IS NULL - "#, - session_token.to_string(), - ) - .fetch_one(mysql_connection) - .await; // TODO: This will return error if it doesn't exist - - match maybe_user_record { - Ok(raw_user_record) => { - let result_user_record = SessionUserRecord { - user_token: raw_user_record.user_token, - username: raw_user_record.username, - display_name: raw_user_record.display_name, - email_address: raw_user_record.email_address, - email_confirmed: i8_to_bool(raw_user_record.email_confirmed), - email_gravatar_hash: raw_user_record.email_gravatar_hash, - // Preference - disable_gravatar: i8_to_bool(raw_user_record.disable_gravatar), - auto_play_audio_preference: nullable_i8_to_optional_bool(raw_user_record.auto_play_audio_preference), - auto_play_video_preference: nullable_i8_to_optional_bool(raw_user_record.auto_play_video_preference), - user_role_slug: raw_user_record.user_role_slug, - preferred_tts_result_visibility: raw_user_record.preferred_tts_result_visibility, - preferred_w2l_result_visibility: raw_user_record.preferred_w2l_result_visibility, - - is_banned: i8_to_bool(raw_user_record.is_banned), - - // Usage - can_use_tts: convert_optional_db_bool_default_false(raw_user_record.can_use_tts), - can_use_w2l: convert_optional_db_bool_default_false(raw_user_record.can_use_w2l), - can_delete_own_tts_results: convert_optional_db_bool_default_false(raw_user_record.can_delete_own_tts_results), - can_delete_own_w2l_results: convert_optional_db_bool_default_false(raw_user_record.can_delete_own_w2l_results), - can_delete_own_account: convert_optional_db_bool_default_false(raw_user_record.can_delete_own_account), - // Contribution - can_upload_tts_models: convert_optional_db_bool_default_false(raw_user_record.can_upload_tts_models), - can_upload_w2l_templates: convert_optional_db_bool_default_false(raw_user_record.can_upload_w2l_templates), - can_delete_own_tts_models: convert_optional_db_bool_default_false(raw_user_record.can_delete_own_tts_models), - can_delete_own_w2l_templates: convert_optional_db_bool_default_false(raw_user_record.can_delete_own_w2l_templates), - // Moderation - can_approve_w2l_templates: convert_optional_db_bool_default_false(raw_user_record.can_approve_w2l_templates), - can_edit_other_users_profiles: convert_optional_db_bool_default_false(raw_user_record.can_edit_other_users_profiles), - can_edit_other_users_tts_models: convert_optional_db_bool_default_false(raw_user_record.can_edit_other_users_tts_models), - can_edit_other_users_w2l_templates: convert_optional_db_bool_default_false(raw_user_record.can_edit_other_users_w2l_templates), - can_delete_other_users_tts_models: convert_optional_db_bool_default_false(raw_user_record.can_delete_other_users_tts_models), - can_delete_other_users_tts_results: convert_optional_db_bool_default_false(raw_user_record.can_delete_other_users_tts_results), - can_delete_other_users_w2l_templates: convert_optional_db_bool_default_false(raw_user_record.can_delete_other_users_w2l_templates), - can_delete_other_users_w2l_results: convert_optional_db_bool_default_false(raw_user_record.can_delete_other_users_w2l_results), - can_ban_users: convert_optional_db_bool_default_false(raw_user_record.can_ban_users), - can_delete_users: convert_optional_db_bool_default_false(raw_user_record.can_delete_users) , - }; - - Ok(Some(result_user_record)) - }, - Err(err) => { - match err { - RowNotFound => { - warn!("Valid cookie; invalid session: {}", session_token); - Ok(None) - }, - _ => { - warn!("Session query error: {:?}", err); - Err(anyhow!("session query error: {:?}", err)) - } - } - } - } - } -} - -fn convert_optional_db_bool_default_false(value: Option) -> bool { - match value { - None => false, - Some(v) => match v { - 0 => false, - _ => true, - } - } -} diff --git a/crates/service/storyteller_web/src/main.rs b/crates/service/storyteller_web/src/main.rs index bd0d6978fc..b5c45de17f 100644 --- a/crates/service/storyteller_web/src/main.rs +++ b/crates/service/storyteller_web/src/main.rs @@ -86,7 +86,6 @@ use crate::http_server::endpoints::users::list_user_w2l_inference_results::list_ use crate::http_server::endpoints::users::list_user_w2l_templates::list_user_w2l_templates_handler; use crate::http_server::endpoints::users::login::login_handler; use crate::http_server::endpoints::users::logout::logout_handler; -use crate::http_server::endpoints::users::session_info::session_info_handler; use crate::http_server::endpoints::w2l::delete_w2l_result::delete_w2l_inference_result_handler; use crate::http_server::endpoints::w2l::delete_w2l_template::delete_w2l_template_handler; use crate::http_server::endpoints::w2l::edit_w2l_result::edit_w2l_inference_result_handler; @@ -103,7 +102,6 @@ use crate::http_server::endpoints::w2l::list_w2l_templates::list_w2l_templates_h use crate::http_server::endpoints::w2l::set_w2l_template_mod_approval::set_w2l_template_mod_approval_handler; use crate::http_server::middleware::ip_filter_middleware::IpFilter; use crate::http_server::web_utils::redis_rate_limiter::RedisRateLimiter; -use crate::http_server::web_utils::session_checker::SessionChecker; use crate::routes::add_routes; use crate::server_state::{ServerState, EnvConfig, TwitchOauthSecrets, TwitchOauth, RedisRateLimiters, InMemoryCaches}; use crate::threads::db_health_checker_thread::db_health_check_status::HealthCheckStatus; @@ -128,6 +126,7 @@ use std::time::Duration; use storage_buckets_common::bucket_client::BucketClient; use tokio::runtime::Runtime; use twitch_common::twitch_secrets::TwitchSecrets; +use users_component::utils::session_checker::SessionChecker; use users_component::utils::session_cookie_manager::SessionCookieManager; // TODO TODO TODO TODO @@ -436,6 +435,8 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()> let build_sha = std::fs::read_to_string("/GIT_SHA").unwrap_or(String::from("unknown")); let app = App::new() + .app_data(web::Data::new(server_state_arc.mysql_pool.clone())) + .app_data(web::Data::new(server_state_arc.session_checker.clone())) .app_data(server_state_arc.clone()) .wrap(build_common_cors_config()) .wrap(DefaultHeaders::new() diff --git a/crates/service/storyteller_web/src/routes.rs b/crates/service/storyteller_web/src/routes.rs index b3f12e7e6b..221103d15f 100644 --- a/crates/service/storyteller_web/src/routes.rs +++ b/crates/service/storyteller_web/src/routes.rs @@ -76,7 +76,6 @@ use crate::http_server::endpoints::users::list_user_w2l_inference_results::list_ use crate::http_server::endpoints::users::list_user_w2l_templates::list_user_w2l_templates_handler; use crate::http_server::endpoints::users::login::login_handler; use crate::http_server::endpoints::users::logout::logout_handler; -use crate::http_server::endpoints::users::session_info::session_info_handler; use crate::http_server::endpoints::vocoders::get_vocoder::get_vocoder_handler; use crate::http_server::endpoints::vocoders::list_vocoders::list_vocoders_handler; use crate::http_server::endpoints::voice_clone_requests::check_if_voice_clone_request_submitted::check_if_voice_clone_request_submitted_handler; @@ -95,6 +94,7 @@ use crate::http_server::endpoints::w2l::get_w2l_template_use_count::get_w2l_temp use crate::http_server::endpoints::w2l::get_w2l_upload_template_job_status::get_w2l_upload_template_job_status_handler; use crate::http_server::endpoints::w2l::list_w2l_templates::list_w2l_templates_handler; use crate::http_server::endpoints::w2l::set_w2l_template_mod_approval::set_w2l_template_mod_approval_handler; +use users_component::endpoints::session_info::session_info_handler; pub fn add_routes (app: App) -> App where diff --git a/crates/service/storyteller_web/src/server_state.rs b/crates/service/storyteller_web/src/server_state.rs index d34ed4ffdf..f529870c6a 100644 --- a/crates/service/storyteller_web/src/server_state.rs +++ b/crates/service/storyteller_web/src/server_state.rs @@ -2,7 +2,6 @@ use crate::StaticApiTokenSet; use crate::http_server::endpoints::categories::list_tts_categories::DisplayCategory; use crate::http_server::endpoints::tts::list_tts_models::TtsModelRecordForResponse; use crate::http_server::web_utils::redis_rate_limiter::RedisRateLimiter; -use crate::http_server::web_utils::session_checker::SessionChecker; use crate::threads::db_health_checker_thread::db_health_check_status::HealthCheckStatus; use crate::threads::ip_banlist_set::IpBanlistSet; use crate::util::encrypted_sort_id::SortKeyCrypto; @@ -14,6 +13,7 @@ use memory_caching::single_item_ttl_cache::SingleItemTtlCache; use r2d2_redis::{r2d2, RedisConnectionManager}; use sqlx::MySqlPool; use storage_buckets_common::bucket_client::BucketClient; +use users_component::utils::session_checker::SessionChecker; use users_component::utils::session_cookie_manager::SessionCookieManager; /// State that is injected into every endpoint.