diff --git a/Cargo.lock b/Cargo.lock index 183f6a355e..39276ba701 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1097,27 +1097,6 @@ dependencies = [ "toml", ] -[[package]] -name = "component_subsystem_users" -version = "0.0.1" -dependencies = [ - "actix-cors 0.6.0-beta.3", - "actix-http 3.0.0-beta.11", - "actix-web 4.0.0-beta.10", - "anyhow", - "base64 0.13.0", - "chrono", - "container_common", - "dotenv", - "log", - "once_cell", - "regex", - "serde", - "serde_derive", - "serde_json", - "toml", -] - [[package]] name = "config" version = "0.0.1" @@ -4062,6 +4041,7 @@ dependencies = [ "twitch_common", "twitch_oauth2", "user_input_common", + "users_component", "uuid", ] @@ -4955,6 +4935,29 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "users_component" +version = "0.0.1" +dependencies = [ + "actix-web 4.0.0-beta.10", + "anyhow", + "base64 0.13.0", + "chrono", + "container_common", + "dotenv", + "hmac 0.10.1", + "jwt", + "log", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "sha2", + "time 0.2.26", + "toml", +] + [[package]] name = "utf-8" version = "0.7.6" diff --git a/crates/component_subsystem/users/Cargo.toml b/crates/component_subsystem/users/Cargo.toml index 1f721c46cd..572aac6ebd 100644 --- a/crates/component_subsystem/users/Cargo.toml +++ b/crates/component_subsystem/users/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "component_subsystem_users" +name = "users_component" edition = "2018" version = "0.0.1" authors = [ @@ -9,7 +9,7 @@ authors = [ ] [lib] -name = "component_subsystem_users" +name = "users_component" path = "src/lib.rs" [dependencies] @@ -25,11 +25,15 @@ dotenv = "0.15.0" log = "0.4.14" once_cell = "1.9.0" regex = "1.5.4" +time = "0.2.26" # Web -actix-cors = "0.6.0-beta.3" -actix-http = "3.0.0-beta.11" actix-web = "4.0.0-beta.10" +jwt = "0.13.0" + +# Crypto +hmac = "0.10.0" # cookies +sha2 = "0.9.5" # file hashes, inference text hashes, etc. # Serialization serde = "1.0.125" diff --git a/crates/component_subsystem/users/src/lib.rs b/crates/component_subsystem/users/src/lib.rs index e69de29bb2..fab870e3ea 100644 --- a/crates/component_subsystem/users/src/lib.rs +++ b/crates/component_subsystem/users/src/lib.rs @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000000..11a4cc04ea --- /dev/null +++ b/crates/component_subsystem/users/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod session_cookie_manager; \ No newline at end of file diff --git a/crates/service/storyteller_web/src/http_server/web_utils/cookie_manager.rs b/crates/component_subsystem/users/src/utils/session_cookie_manager.rs similarity index 84% rename from crates/service/storyteller_web/src/http_server/web_utils/cookie_manager.rs rename to crates/component_subsystem/users/src/utils/session_cookie_manager.rs index c2de649aff..35e096e351 100644 --- a/crates/service/storyteller_web/src/http_server/web_utils/cookie_manager.rs +++ b/crates/component_subsystem/users/src/utils/session_cookie_manager.rs @@ -1,29 +1,35 @@ -use actix_web::cookie::{Cookie, SameSite}; -use actix_web::{HttpRequest, HttpMessage}; +// NB: Incrementally getting rid of build warnings... +#![forbid(unused_imports)] +#![forbid(unused_mut)] +#![forbid(unused_variables)] + +use actix_web::HttpRequest; +use actix_web::cookie::Cookie; use anyhow::anyhow; -use crate::AnyhowResult; +use container_common::anyhow_result::AnyhowResult; use hmac::Hmac; use hmac::NewMac; use jwt::SignWithKey; use jwt::VerifyWithKey; -use log::LevelFilter::Off; -use log::{info, warn}; +use log::warn; use sha2::Sha256; use std::collections::BTreeMap; -use std::ops::Sub; use time::OffsetDateTime; const COOKIE_VERSION : u32 = 1; const SESSION_COOKIE_NAME : &'static str = "session"; +// TODO(echelon,2022-08-29): Make a CryptedCookieManager that this uses. +// TODO(echelon,2022-08-29): Fix how domains and "secure" cookies are handled + #[derive(Clone)] -pub struct CookieManager { +pub struct SessionCookieManager { cookie_domain: String, hmac_secret: String, } -impl CookieManager { +impl SessionCookieManager { pub fn new(cookie_domain: &str, hmac_secret: &str) -> Self { Self { cookie_domain: cookie_domain.to_string(), diff --git a/crates/service/storyteller_web/Cargo.toml b/crates/service/storyteller_web/Cargo.toml index 3bb8eb3e6b..aa788c4150 100644 --- a/crates/service/storyteller_web/Cargo.toml +++ b/crates/service/storyteller_web/Cargo.toml @@ -30,6 +30,9 @@ tts_common = { path = "../../lib/tts_common" } twitch_common = { path = "../../lib/twitch_common" } user_input_common = { path = "../../lib/user_input_common" } +# Internal Components +users_component = { path = "../../component_subsystem/users" } + # Vendored code newrelic-telemetry = { path = "../../vendor/newrelic-telemetry-sdk-rust" } diff --git a/crates/service/storyteller_web/src/http_server/endpoints/users/logout.rs b/crates/service/storyteller_web/src/http_server/endpoints/users/logout.rs index f892574399..a22f446e10 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/users/logout.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/users/logout.rs @@ -1,20 +1,16 @@ -use actix_http::Error; use actix_http::http::header; -use actix_web::cookie::Cookie; use actix_web::HttpResponseBuilder; +use actix_web::cookie::Cookie; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::{Responder, web, HttpResponse, error, HttpRequest, HttpMessage}; -use crate::AnyhowResult; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; +use container_common::anyhow_result::AnyhowResult; use crate::server_state::ServerState; -use derive_more::{Display, Error}; +use http_server_common::response::response_error_helpers::to_simple_json_error; use log::{info, warn, log}; use regex::Regex; use sqlx::MySqlPool; -use sqlx::error::DatabaseError; -use sqlx::error::Error::Database; -use sqlx::mysql::MySqlDatabaseError; +use std::fmt; use std::sync::Arc; #[derive(Serialize)] @@ -22,7 +18,7 @@ pub struct LogoutSuccessResponse { pub success: bool, } -#[derive(Debug, Display)] +#[derive(Debug)] pub enum LogoutError { ServerError, } @@ -43,6 +39,13 @@ impl ResponseError for LogoutError { } } +// NB: Not using derive_more::Display since Clion doesn't understand it. +impl fmt::Display for LogoutError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + pub async fn logout_handler( http_request: HttpRequest, server_state: web::Data>) -> Result 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 8037e1b8c8..583b527ba7 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 @@ -1,4 +1,3 @@ -pub mod cookie_manager; pub mod get_host_header; pub mod ip_address; pub mod read_multipart_field_bytes; 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 index aa4e2aa247..c0d037d390 100644 --- 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 @@ -6,17 +6,17 @@ use actix_web::HttpRequest; use anyhow::anyhow; use crate::AnyhowResult; -use crate::http_server::web_utils::cookie_manager::CookieManager; 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: CookieManager, + cookie_manager: SessionCookieManager, } pub struct SessionRecord { @@ -121,7 +121,7 @@ pub struct SessionUserRawDbRecord { impl SessionChecker { - pub fn new(cookie_manager: &CookieManager) -> Self { + pub fn new(cookie_manager: &SessionCookieManager) -> Self { Self { cookie_manager: cookie_manager.clone(), } diff --git a/crates/service/storyteller_web/src/main.rs b/crates/service/storyteller_web/src/main.rs index be16344dad..bd0d6978fc 100644 --- a/crates/service/storyteller_web/src/main.rs +++ b/crates/service/storyteller_web/src/main.rs @@ -102,7 +102,6 @@ use crate::http_server::endpoints::w2l::get_w2l_upload_template_job_status::get_ 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 crate::http_server::middleware::ip_filter_middleware::IpFilter; -use crate::http_server::web_utils::cookie_manager::CookieManager; use crate::http_server::web_utils::redis_rate_limiter::RedisRateLimiter; use crate::http_server::web_utils::session_checker::SessionChecker; use crate::routes::add_routes; @@ -129,6 +128,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_cookie_manager::SessionCookieManager; // TODO TODO TODO TODO // TODO TODO TODO TODO @@ -275,7 +275,7 @@ async fn main() -> AnyhowResult<()> { let website_homepage_redirect = easyenv::get_env_string_or_default("WEBSITE_HOMEPAGE_REDIRECT", "https://vo.codes/"); - let cookie_manager = CookieManager::new(&cookie_domain, &hmac_secret); + let cookie_manager = SessionCookieManager::new(&cookie_domain, &hmac_secret); let session_checker = SessionChecker::new(&cookie_manager); let access_key = easyenv::get_env_string_required(ENV_ACCESS_KEY)?; diff --git a/crates/service/storyteller_web/src/server_state.rs b/crates/service/storyteller_web/src/server_state.rs index 30130f5afd..d34ed4ffdf 100644 --- a/crates/service/storyteller_web/src/server_state.rs +++ b/crates/service/storyteller_web/src/server_state.rs @@ -1,7 +1,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::cookie_manager::CookieManager; 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; @@ -9,12 +8,13 @@ use crate::threads::ip_banlist_set::IpBanlistSet; use crate::util::encrypted_sort_id::SortKeyCrypto; use database_queries::mediators::badge_granter::BadgeGranter; use database_queries::mediators::firehose_publisher::FirehosePublisher; +use database_queries::queries::tts::tts_inference_jobs::get_pending_tts_inference_job_count::TtsQueueLengthResult; +use database_queries::queries::w2l::w2l_templates::list_w2l_templates::W2lTemplateRecordForList; use memory_caching::single_item_ttl_cache::SingleItemTtlCache; use r2d2_redis::{r2d2, RedisConnectionManager}; use sqlx::MySqlPool; -use database_queries::queries::tts::tts_inference_jobs::get_pending_tts_inference_job_count::TtsQueueLengthResult; -use database_queries::queries::w2l::w2l_templates::list_w2l_templates::W2lTemplateRecordForList; use storage_buckets_common::bucket_client::BucketClient; +use users_component::utils::session_cookie_manager::SessionCookieManager; /// State that is injected into every endpoint. pub struct ServerState { @@ -31,7 +31,7 @@ pub struct ServerState { pub redis_rate_limiters: RedisRateLimiters, - pub cookie_manager: CookieManager, + pub cookie_manager: SessionCookieManager, pub session_checker: SessionChecker,