From ff22319c778cb9f5f5c360155abc2ff81e849b7b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 12 Mar 2023 05:17:37 -0400 Subject: [PATCH] purge session cache with billing endpoints --- ...pe_create_checkout_session_json_handler.rs | 7 +++- ...reate_checkout_session_redirect_handler.rs | 7 +++- ...te_customer_portal_session_json_handler.rs | 5 +++ ...ustomer_portal_session_redirect_handler.rs | 7 +++- .../traits/internal_session_cache_purge.rs | 9 +++++ .../plugins/billing/src/stripe/traits/mod.rs | 1 + .../src/endpoints/edit_profile_handler.rs | 4 +- .../internal_session_cache_purge_impl.rs | 39 +++++++++++++++++++ .../web/storyteller_web/src/billing/mod.rs | 1 + .../service/web/storyteller_web/src/main.rs | 7 ++++ 10 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 crates/service/plugins/billing/src/stripe/traits/internal_session_cache_purge.rs create mode 100644 crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs diff --git a/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs b/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs index 18dbfdb51e..da018e0ca6 100644 --- a/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs +++ b/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs @@ -8,15 +8,16 @@ use crate::stripe::http_endpoints::checkout::create::stripe_create_checkout_sess use crate::stripe::http_endpoints::checkout::create::stripe_create_checkout_session_shared::stripe_create_checkout_session_shared; use crate::stripe::stripe_config::StripeConfig; use crate::stripe::traits::internal_product_to_stripe_lookup::InternalProductToStripeLookup; +use crate::stripe::traits::internal_session_cache_purge::InternalSessionCachePurge; use crate::stripe::traits::internal_user_lookup::InternalUserLookup; use http_server_common::request::get_request_header_optional::get_request_header_optional; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use log::{error, warn}; +use reusable_types::server_environment::ServerEnvironment; use sqlx::MySqlPool; use std::collections::HashMap; use std::fmt; use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionLineItems}; -use reusable_types::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; // =============== Request =============== @@ -45,6 +46,7 @@ pub async fn stripe_create_checkout_session_json_handler( url_redirector: web::Data, internal_product_to_stripe_lookup: web::Data, internal_user_lookup: web::Data, + internal_session_cache_purge: web::Data, ) -> Result { let maybe_internal_product_key = request.internal_plan_key.as_deref(); @@ -60,6 +62,9 @@ pub async fn stripe_create_checkout_session_json_handler( internal_user_lookup.get_ref(), ).await?; + // Best effort to delete Redis session cache + internal_session_cache_purge.best_effort_purge_session_cache(&http_request); + let response = CreateCheckoutSessionSuccessResponse { success: true, stripe_checkout_redirect_url: url, diff --git a/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs b/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs index 878c226f3b..460bb18319 100644 --- a/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs +++ b/crates/service/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs @@ -8,14 +8,15 @@ use crate::stripe::http_endpoints::checkout::create::stripe_create_checkout_sess use crate::stripe::http_endpoints::checkout::create::stripe_create_checkout_session_shared::stripe_create_checkout_session_shared; use crate::stripe::stripe_config::StripeConfig; use crate::stripe::traits::internal_product_to_stripe_lookup::InternalProductToStripeLookup; +use crate::stripe::traits::internal_session_cache_purge::InternalSessionCachePurge; use crate::stripe::traits::internal_user_lookup::InternalUserLookup; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use log::{error, warn}; +use reusable_types::server_environment::ServerEnvironment; use sqlx::MySqlPool; use std::collections::HashMap; use std::fmt; use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionLineItems}; -use reusable_types::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; // =============== Request =============== @@ -34,6 +35,7 @@ pub async fn stripe_create_checkout_session_redirect_handler( url_redirector: web::Data, internal_product_to_stripe_lookup: web::Data, internal_user_lookup: web::Data, + internal_session_cache_purge: web::Data, ) -> Result { let maybe_internal_product_key = request.product.as_deref(); @@ -49,6 +51,9 @@ pub async fn stripe_create_checkout_session_redirect_handler( internal_user_lookup.get_ref(), ).await?; + // Best effort to delete Redis session cache + internal_session_cache_purge.best_effort_purge_session_cache(&http_request); + Ok(HttpResponse::Found() .append_header((header::LOCATION, url)) .finish()) diff --git a/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_json_handler.rs b/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_json_handler.rs index dabb1be12b..2ed6dfe8e8 100644 --- a/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_json_handler.rs +++ b/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_json_handler.rs @@ -4,6 +4,7 @@ use crate::stripe::http_endpoints::customer_portal::stripe_create_customer_porta use crate::stripe::http_endpoints::customer_portal::stripe_create_customer_portal_session_shared::stripe_create_customer_portal_session_shared; use crate::stripe::stripe_config::StripeConfig; use crate::stripe::traits::internal_product_to_stripe_lookup::InternalProductToStripeLookup; +use crate::stripe::traits::internal_session_cache_purge::InternalSessionCachePurge; use crate::stripe::traits::internal_user_lookup::InternalUserLookup; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; @@ -33,6 +34,7 @@ pub async fn stripe_create_customer_portal_session_json_handler( url_redirector: web::Data, internal_product_to_stripe_lookup: web::Data, internal_user_lookup: web::Data, + internal_session_cache_purge: web::Data, ) -> Result { let portal_config_id = request.portal_config_id @@ -40,6 +42,9 @@ pub async fn stripe_create_customer_portal_session_json_handler( .unwrap_or(&stripe_config.portal.default_portal_config_id) .to_string(); + // Best effort to delete Redis session cache + internal_session_cache_purge.best_effort_purge_session_cache(&http_request); + let redirect_url = stripe_create_customer_portal_session_shared( http_request, stripe_config, diff --git a/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_redirect_handler.rs b/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_redirect_handler.rs index 66a856ecf3..e7141f1585 100644 --- a/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_redirect_handler.rs +++ b/crates/service/plugins/billing/src/stripe/http_endpoints/customer_portal/stripe_create_customer_portal_session_redirect_handler.rs @@ -1,10 +1,11 @@ use actix_http::header; -use actix_web::{HttpRequest, HttpResponse, web}; use actix_web::web::Query; +use actix_web::{HttpRequest, HttpResponse, web}; use crate::stripe::http_endpoints::customer_portal::stripe_create_customer_portal_session_error::CreateCustomerPortalSessionError; use crate::stripe::http_endpoints::customer_portal::stripe_create_customer_portal_session_shared::stripe_create_customer_portal_session_shared; use crate::stripe::stripe_config::StripeConfig; use crate::stripe::traits::internal_product_to_stripe_lookup::InternalProductToStripeLookup; +use crate::stripe::traits::internal_session_cache_purge::InternalSessionCachePurge; use crate::stripe::traits::internal_user_lookup::InternalUserLookup; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; @@ -26,6 +27,7 @@ pub async fn stripe_create_customer_portal_session_redirect_handler( url_redirector: web::Data, internal_product_to_stripe_lookup: web::Data, internal_user_lookup: web::Data, + internal_session_cache_purge: web::Data, ) -> Result { let portal_config_id = query.portal_config_id @@ -33,6 +35,9 @@ pub async fn stripe_create_customer_portal_session_redirect_handler( .unwrap_or(&stripe_config.portal.default_portal_config_id) .to_string(); + // Best effort to delete Redis session cache + internal_session_cache_purge.best_effort_purge_session_cache(&http_request); + let redirect_url = stripe_create_customer_portal_session_shared( http_request, stripe_config, diff --git a/crates/service/plugins/billing/src/stripe/traits/internal_session_cache_purge.rs b/crates/service/plugins/billing/src/stripe/traits/internal_session_cache_purge.rs new file mode 100644 index 0000000000..d125c53d7f --- /dev/null +++ b/crates/service/plugins/billing/src/stripe/traits/internal_session_cache_purge.rs @@ -0,0 +1,9 @@ +use actix_web::HttpRequest; + +/// Allows us to purge session caches that might keep stale premium plan/billing data around +#[cfg_attr(test, automock)] +pub trait InternalSessionCachePurge { + /// Best effort attempt to delete + /// Fails silently. + fn best_effort_purge_session_cache(&self, http_request: &HttpRequest); +} diff --git a/crates/service/plugins/billing/src/stripe/traits/mod.rs b/crates/service/plugins/billing/src/stripe/traits/mod.rs index 44d38b0eac..2cf3b026fa 100644 --- a/crates/service/plugins/billing/src/stripe/traits/mod.rs +++ b/crates/service/plugins/billing/src/stripe/traits/mod.rs @@ -1,3 +1,4 @@ pub mod internal_product_to_stripe_lookup; +pub mod internal_session_cache_purge; pub mod internal_subscription_product_lookup; pub mod internal_user_lookup; \ No newline at end of file diff --git a/crates/service/plugins/users/src/endpoints/edit_profile_handler.rs b/crates/service/plugins/users/src/endpoints/edit_profile_handler.rs index e1a6d164b5..7fb2994e35 100644 --- a/crates/service/plugins/users/src/endpoints/edit_profile_handler.rs +++ b/crates/service/plugins/users/src/endpoints/edit_profile_handler.rs @@ -22,10 +22,10 @@ use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use log::warn; -use sqlx::MySqlPool; -use std::fmt; use redis_caching::redis_ttl_cache::RedisTtlCache; use redis_common::redis_cache_keys::RedisCacheKeys; +use sqlx::MySqlPool; +use std::fmt; use user_input_common::check_for_slurs::contains_slurs; use user_input_common::markdown_to_html::markdown_to_html; diff --git a/crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs b/crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs new file mode 100644 index 0000000000..3563a22919 --- /dev/null +++ b/crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs @@ -0,0 +1,39 @@ +use actix_web::HttpRequest; +use billing_component::stripe::traits::internal_session_cache_purge::InternalSessionCachePurge; +use redis_caching::redis_ttl_cache::RedisTtlCache; +use redis_common::redis_cache_keys::RedisCacheKeys; +use users_component::utils::session_checker::SessionChecker; + +pub struct InternalSessionCachePurgeImpl { + session_checker: SessionChecker, + redis_ttl_cache: RedisTtlCache, +} + +impl InternalSessionCachePurgeImpl { + pub fn new(session_checker: SessionChecker, redis_ttl_cache: RedisTtlCache) -> Self { + Self { + session_checker, + redis_ttl_cache, + } + } +} + +impl InternalSessionCachePurge for InternalSessionCachePurgeImpl { + fn best_effort_purge_session_cache(&self, http_request: &HttpRequest) { + // TODO: Clear Redis cache of sessions + // Unfortunately we don't yet have an index of user_token => session_tokens[] outside the DB. + // For now, a hacky solution is just to delete the cache under the current user. + // This makes sense for non-mods and should solve 95% of cases. + if let Some(session_token) = self.session_checker.forgiving_get_session_token(&http_request) { + if let Ok(mut redis_ttl_cache) = self.redis_ttl_cache.get_connection() { + let keys = vec![ + RedisCacheKeys::session_record_user(&session_token), + RedisCacheKeys::session_record_light(&session_token), + ]; + for key in keys.iter() { + let _r = redis_ttl_cache.delete_from_cache(key).ok(); + } + } + } + } +} diff --git a/crates/service/web/storyteller_web/src/billing/mod.rs b/crates/service/web/storyteller_web/src/billing/mod.rs index c037329014..bf3689f826 100644 --- a/crates/service/web/storyteller_web/src/billing/mod.rs +++ b/crates/service/web/storyteller_web/src/billing/mod.rs @@ -1,3 +1,4 @@ pub mod internal_product_to_stripe_lookup_impl; +pub mod internal_session_cache_purge_impl; pub mod stripe_internal_subscription_product_lookup_impl; pub mod stripe_internal_user_lookup_impl; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/main.rs b/crates/service/web/storyteller_web/src/main.rs index 295734b39b..401019d5a5 100644 --- a/crates/service/web/storyteller_web/src/main.rs +++ b/crates/service/web/storyteller_web/src/main.rs @@ -89,11 +89,13 @@ use actix_helpers::middleware::endpoint_disablement::disabled_endpoints::disable use actix_helpers::middleware::endpoint_disablement::disabled_endpoints::exact_match_endpoint_disablements::ExactMatchEndpointDisablements; use actix_helpers::middleware::endpoint_disablement::disabled_endpoints::prefix_endpoint_disablements::PrefixEndpointDisablements; use actix_helpers::middleware::endpoint_disablement::endpoint_disablement_middleware::EndpointDisablementFilter; +use billing_component::stripe::traits::internal_session_cache_purge::InternalSessionCachePurge; use redis_caching::redis_ttl_cache::RedisTtlCache; use twitch_common::twitch_secrets::TwitchSecrets; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; use users_component::utils::session_checker::SessionChecker; use users_component::utils::session_cookie_manager::SessionCookieManager; +use crate::billing::internal_session_cache_purge_impl::InternalSessionCachePurgeImpl; const DEFAULT_BIND_ADDRESS : &'static str = "0.0.0.0:12345"; @@ -511,6 +513,10 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()> server_state_arc.session_checker.clone(), server_state_arc.mysql_pool.clone(), )); + let session_cache_purge : Arc = Arc::new(InternalSessionCachePurgeImpl::new( + server_state_arc.session_checker.clone(), + server_state_arc.redis_ttl_cache.clone(), + )); // NB: app_data being clone()'d below should all be safe (dependencies included) let app = App::new() @@ -527,6 +533,7 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()> .app_data(web::Data::from(product_lookup)) // NB: Data::from(Arc) for dynamic dispatch .app_data(web::Data::from(stripe_lookup)) // NB: Data::from(Arc) for dynamic dispatch .app_data(web::Data::from(user_lookup)) // NB: Data::from(Arc) for dynamic dispatch + .app_data(web::Data::from(session_cache_purge)) // NB: Data::from(Arc) for dynamic dispatch .app_data(server_state_arc.clone()) .wrap(build_cors_config(server_state_arc.server_environment.clone())) .wrap(DefaultHeaders::new()