diff --git a/Cargo.lock b/Cargo.lock index 3f2654166e..1d800d53bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6837,6 +6837,7 @@ dependencies = [ "bcrypt", "chrono", "container_common", + "cookies", "dotenv", "enums", "errors", diff --git a/crates/service/plugins/users/Cargo.toml b/crates/service/plugins/users/Cargo.toml index 98e3e88b6a..8e5d603ea3 100644 --- a/crates/service/plugins/users/Cargo.toml +++ b/crates/service/plugins/users/Cargo.toml @@ -16,6 +16,7 @@ path = "src/lib.rs" # Internal container_common = { path = "../../../lib/deprecated/container_common" } +cookies = { path = "../../../lib/cookies" } enums = { path = "../../../_types/enums" } errors = { path = "../../../lib/errors" } http_server_common = { path = "../../../lib/deprecated/http_server_common" } diff --git a/crates/service/plugins/users/src/cookies/mod.rs b/crates/service/plugins/users/src/cookies/mod.rs new file mode 100644 index 0000000000..0f135e9168 --- /dev/null +++ b/crates/service/plugins/users/src/cookies/mod.rs @@ -0,0 +1,2 @@ +pub mod session_cookie_manager; +pub mod session_cookie_payload; diff --git a/crates/service/plugins/users/src/utils/session_cookie_manager.rs b/crates/service/plugins/users/src/cookies/session_cookie_manager.rs similarity index 70% rename from crates/service/plugins/users/src/utils/session_cookie_manager.rs rename to crates/service/plugins/users/src/cookies/session_cookie_manager.rs index 5e6dd22aa2..d35c6d2a53 100644 --- a/crates/service/plugins/users/src/utils/session_cookie_manager.rs +++ b/crates/service/plugins/users/src/cookies/session_cookie_manager.rs @@ -8,15 +8,13 @@ use std::collections::BTreeMap; use actix_web::cookie::Cookie; use actix_web::HttpRequest; use anyhow::anyhow; -use hmac::Hmac; -use hmac::NewMac; -use jwt::SignWithKey; -use jwt::VerifyWithKey; use log::warn; -use sha2::Sha256; +use cookies::jwt_signer::JwtSigner; use errors::AnyhowResult; +use crate::cookies::session_cookie_payload::SessionCookiePayload; + /** * Cookie version history * @@ -34,15 +32,7 @@ const SESSION_COOKIE_NAME : &str = "session"; pub struct SessionCookieManager { cookie_domain: String, hmac_secret: String, -} - -#[derive(Clone)] -pub struct SessionCookiePayload { - /// The database primary key for the session instance. - pub session_token: String, - /// The primary key identifier of the user. - /// Version 1 cookies do not have a user token, hence it is optional. - pub maybe_user_token: Option, + jwt_signer: JwtSigner, } impl SessionCookieManager { @@ -50,13 +40,11 @@ impl SessionCookieManager { Self { cookie_domain: cookie_domain.to_string(), hmac_secret: hmac_secret.to_string(), + jwt_signer: JwtSigner::new(hmac_secret).unwrap(),//TODO FIXME } } pub fn create_cookie(&self, session_token: &str, user_token: &str) -> AnyhowResult { - let key: Hmac = Hmac::new_varkey(self.hmac_secret.as_bytes()) - .map_err(|e| anyhow!("invalid hmac: {:?}", e))?; - let cookie_version = COOKIE_VERSION.to_string(); let mut claims = BTreeMap::new(); @@ -64,7 +52,7 @@ impl SessionCookieManager { claims.insert("user_token", user_token); claims.insert("cookie_version", &cookie_version); - let jwt_string = claims.sign_with_key(&key)?; + let jwt_string = self.jwt_signer.claims_to_jwt(&claims)?; let make_secure = !self.cookie_domain.to_lowercase().contains("jungle.horse") && !self.cookie_domain.to_lowercase().contains("localhost"); @@ -91,12 +79,9 @@ impl SessionCookieManager { pub fn decode_session_cookie_payload(&self, session_cookie: &Cookie) -> AnyhowResult { - let key: Hmac = Hmac::new_varkey(self.hmac_secret.as_bytes()) - .map_err(|e| anyhow!("invalid hmac: {:?}", e))?; - let cookie_contents = session_cookie.value().to_string(); - let claims: BTreeMap = cookie_contents.verify_with_key(&key)?; + let claims = self.jwt_signer.jwt_to_claims(&cookie_contents)?; let session_token = claims["session_token"].clone(); let maybe_user_token = claims.get("user_token") @@ -149,3 +134,30 @@ impl SessionCookieManager { } } +#[cfg(test)] +mod tests { + use crate::cookies::session_cookie_manager::SessionCookieManager; + + #[test] + fn test_cookie_payload() { + // NB: Let's make extra sure this always works when migrating cookies, else we'll accidentally log out logged-in users. + // (These are version 2 cookies.) + let manager = SessionCookieManager::new("fakeyou.com", "secret"); + let cookie = manager.create_cookie("ex_session_token", "ex_user_token").unwrap(); + + assert_eq!(cookie.value(), "eyJhbGciOiJIUzI1NiJ9.eyJjb29raWVfdmVyc2lvbiI6IjIiLCJzZXNzaW9uX3Rva2VuIjoiZXhfc2Vzc2lvbl90b2tlbiIsInVzZXJfdG9rZW4iOiJleF91c2VyX3Rva2VuIn0.94ly2gHhlPVtnANsNy6cJozFVmId4imwW5v-mei7jD8"); + } + + #[test] + fn test_cookie_round_trip() { + // NB: Let's make extra sure this always works when migrating cookies, else we'll accidentally log out logged-in users. + // (These are version 2 cookies.) + let manager = SessionCookieManager::new("fakeyou.com", "secret"); + let cookie = manager.create_cookie("ex_session_token", "ex_user_token").unwrap(); + + let decoded = manager.decode_session_cookie_payload(&cookie).unwrap(); + + assert_eq!(decoded.session_token, "ex_session_token".to_string()); + assert_eq!(decoded.maybe_user_token, Some("ex_user_token".to_string())); + } +} diff --git a/crates/service/plugins/users/src/cookies/session_cookie_payload.rs b/crates/service/plugins/users/src/cookies/session_cookie_payload.rs new file mode 100644 index 0000000000..b60da99e0e --- /dev/null +++ b/crates/service/plugins/users/src/cookies/session_cookie_payload.rs @@ -0,0 +1,9 @@ + +#[derive(Clone)] +pub struct SessionCookiePayload { + /// The database primary key for the session instance. + pub session_token: String, + /// The primary key identifier of the user. + /// Version 1 cookies do not have a user token, hence it is optional. + pub maybe_user_token: Option, +} diff --git a/crates/service/plugins/users/src/endpoints/create_account_handler.rs b/crates/service/plugins/users/src/endpoints/create_account_handler.rs index 282f5521bc..7b0bdb6681 100644 --- a/crates/service/plugins/users/src/endpoints/create_account_handler.rs +++ b/crates/service/plugins/users/src/endpoints/create_account_handler.rs @@ -21,8 +21,8 @@ use mysql_queries::queries::users::user_sessions::create_user_session::create_us use mysql_queries::tokens::Tokens; use user_input_common::check_for_slurs::contains_slurs; +use crate::cookies::session_cookie_manager::SessionCookieManager; use crate::utils::email_to_gravatar::email_to_gravatar; -use crate::utils::session_cookie_manager::SessionCookieManager; use crate::validations::is_reserved_username::is_reserved_username; use crate::validations::validate_passwords::validate_passwords; use crate::validations::validate_username::validate_username; diff --git a/crates/service/plugins/users/src/endpoints/login_handler.rs b/crates/service/plugins/users/src/endpoints/login_handler.rs index 7dd840b4b9..e959e201ed 100644 --- a/crates/service/plugins/users/src/endpoints/login_handler.rs +++ b/crates/service/plugins/users/src/endpoints/login_handler.rs @@ -19,7 +19,7 @@ use mysql_queries::queries::users::user::lookup_user_for_login_by_email::lookup_ use mysql_queries::queries::users::user::lookup_user_for_login_by_username::lookup_user_for_login_by_username; use mysql_queries::queries::users::user_sessions::create_user_session::create_user_session; -use crate::utils::session_cookie_manager::SessionCookieManager; +use crate::cookies::session_cookie_manager::SessionCookieManager; #[derive(Deserialize)] pub struct LoginRequest { diff --git a/crates/service/plugins/users/src/endpoints/logout_handler.rs b/crates/service/plugins/users/src/endpoints/logout_handler.rs index 1f94bf9b7d..72dc0ebd5f 100644 --- a/crates/service/plugins/users/src/endpoints/logout_handler.rs +++ b/crates/service/plugins/users/src/endpoints/logout_handler.rs @@ -14,7 +14,7 @@ use sqlx::MySqlPool; use http_server_common::response::response_error_helpers::to_simple_json_error; use mysql_queries::queries::users::user_sessions::delete_user_session::delete_user_session; -use crate::utils::session_cookie_manager::SessionCookieManager; +use crate::cookies::session_cookie_manager::SessionCookieManager; #[derive(Serialize)] pub struct LogoutSuccessResponse { diff --git a/crates/service/plugins/users/src/lib.rs b/crates/service/plugins/users/src/lib.rs index 9570a7af35..308f563f5c 100644 --- a/crates/service/plugins/users/src/lib.rs +++ b/crates/service/plugins/users/src/lib.rs @@ -4,6 +4,7 @@ pub const RESERVED_USERNAMES : &str = include_str!("../../../../../includes/binary_includes/reserved_usernames.txt"); pub const RESERVED_SUBSTRINGS : &str = include_str!("../../../../../includes/binary_includes/reserved_usernames_including.txt"); +pub mod cookies; pub mod default_routes; pub mod endpoints; pub mod utils; diff --git a/crates/service/plugins/users/src/utils/mod.rs b/crates/service/plugins/users/src/utils/mod.rs index 0e6f07cc17..0110b80c7c 100644 --- a/crates/service/plugins/users/src/utils/mod.rs +++ b/crates/service/plugins/users/src/utils/mod.rs @@ -2,5 +2,4 @@ pub mod default_avatar_color_from_username; pub mod default_avatar_from_username; pub mod email_to_gravatar; pub mod session_checker; -pub mod session_cookie_manager; pub mod user_session_extended; \ No newline at end of file diff --git a/crates/service/plugins/users/src/utils/session_checker.rs b/crates/service/plugins/users/src/utils/session_checker.rs index 00d6647e6c..bc51f816a3 100644 --- a/crates/service/plugins/users/src/utils/session_checker.rs +++ b/crates/service/plugins/users/src/utils/session_checker.rs @@ -15,7 +15,7 @@ use mysql_queries::queries::users::user_subscriptions::list_active_user_subscrip use redis_caching::redis_ttl_cache::{RedisTtlCache, RedisTtlCacheConnection}; use redis_common::redis_cache_keys::RedisCacheKeys; -use crate::utils::session_cookie_manager::SessionCookieManager; +use crate::cookies::session_cookie_manager::SessionCookieManager; use crate::utils::user_session_extended::{UserSessionExtended, UserSessionPreferences, UserSessionPremiumPlanInfo, UserSessionRoleAndPermissions, UserSessionSubscriptionPlan, UserSessionUserDetails}; #[derive(Clone)] diff --git a/crates/service/web/storyteller_web/src/main.rs b/crates/service/web/storyteller_web/src/main.rs index e17a965fb2..4b9cad4a04 100644 --- a/crates/service/web/storyteller_web/src/main.rs +++ b/crates/service/web/storyteller_web/src/main.rs @@ -64,8 +64,8 @@ use redis_caching::redis_ttl_cache::RedisTtlCache; use reusable_types::server_environment::ServerEnvironment; use twitch_common::twitch_secrets::TwitchSecrets; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; +use users_component::cookies::session_cookie_manager::SessionCookieManager; use users_component::utils::session_checker::SessionChecker; -use users_component::utils::session_cookie_manager::SessionCookieManager; use crate::billing::internal_product_to_stripe_lookup_impl::InternalProductToStripeLookupImpl; use crate::billing::internal_session_cache_purge_impl::InternalSessionCachePurgeImpl; diff --git a/crates/service/web/storyteller_web/src/server_state.rs b/crates/service/web/storyteller_web/src/server_state.rs index 2fe1a156e9..68b59ab334 100644 --- a/crates/service/web/storyteller_web/src/server_state.rs +++ b/crates/service/web/storyteller_web/src/server_state.rs @@ -15,8 +15,8 @@ use mysql_queries::queries::w2l::w2l_templates::list_w2l_templates::W2lTemplateR use redis_caching::redis_ttl_cache::RedisTtlCache; use reusable_types::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; +use users_component::cookies::session_cookie_manager::SessionCookieManager; use users_component::utils::session_checker::SessionChecker; -use users_component::utils::session_cookie_manager::SessionCookieManager; use crate::http_server::endpoints::categories::tts::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories::ModelTokensByCategoryToken; use crate::http_server::endpoints::leaderboard::get_leaderboard::LeaderboardInfo;