mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
purge session cache with billing endpoints
This commit is contained in:
+6
-1
@@ -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,
|
||||
|
||||
+6
-1
@@ -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())
|
||||
|
||||
+5
@@ -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,
|
||||
|
||||
+6
-1
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user