purge session cache with billing endpoints

This commit is contained in:
Brandon Thomas
2023-03-12 05:17:37 -04:00
parent 16f2d5557b
commit ff22319c77
10 changed files with 82 additions and 5 deletions
@@ -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<ThirdPartyUrlRedirector>,
internal_product_to_stripe_lookup: web::Data<dyn InternalProductToStripeLookup>,
internal_user_lookup: web::Data<dyn InternalUserLookup>,
internal_session_cache_purge: web::Data<dyn InternalSessionCachePurge>,
) -> Result<HttpResponse, CreateCheckoutSessionError>
{
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,
@@ -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<ThirdPartyUrlRedirector>,
internal_product_to_stripe_lookup: web::Data<dyn InternalProductToStripeLookup>,
internal_user_lookup: web::Data<dyn InternalUserLookup>,
internal_session_cache_purge: web::Data<dyn InternalSessionCachePurge>,
) -> Result<HttpResponse, CreateCheckoutSessionError>
{
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())
@@ -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<ThirdPartyUrlRedirector>,
internal_product_to_stripe_lookup: web::Data<dyn InternalProductToStripeLookup>,
internal_user_lookup: web::Data<dyn InternalUserLookup>,
internal_session_cache_purge: web::Data<dyn InternalSessionCachePurge>,
) -> Result<HttpResponse, CreateCustomerPortalSessionError>
{
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,
@@ -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<ThirdPartyUrlRedirector>,
internal_product_to_stripe_lookup: web::Data<dyn InternalProductToStripeLookup>,
internal_user_lookup: web::Data<dyn InternalUserLookup>,
internal_session_cache_purge: web::Data<dyn InternalSessionCachePurge>,
) -> Result<HttpResponse, CreateCustomerPortalSessionError>
{
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,
@@ -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);
}
@@ -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;
@@ -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;
@@ -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();
}
}
}
}
}
@@ -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;
@@ -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<dyn InternalSessionCachePurge> = 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<T>) for dynamic dispatch
.app_data(web::Data::from(stripe_lookup)) // NB: Data::from(Arc<T>) for dynamic dispatch
.app_data(web::Data::from(user_lookup)) // NB: Data::from(Arc<T>) for dynamic dispatch
.app_data(web::Data::from(session_cache_purge)) // NB: Data::from(Arc<T>) for dynamic dispatch
.app_data(server_state_arc.clone())
.wrap(build_cors_config(server_state_arc.server_environment.clone()))
.wrap(DefaultHeaders::new()