Skip to main content

max / makenotwork

server: gate suspended users out of profile and synckit billing mutations AuthUser refreshes user.suspended on every request but most handlers that read it are mutation paths; these five were missed: - users/profile.rs update_profile — display name / bio edits - users/profile.rs disconnect_stripe — payout settings - synckit/billing.rs setup — Stripe customer creation - synckit/billing.rs activate — subscription creation - synckit/billing.rs patch — subscription re-pricing Each now calls user.check_not_suspended()? after extraction. Deliberately NOT gating synckit/billing.rs cancel — letting a suspended developer wind down their app's subscription so they stop being charged is humane and doesn't expose new risk.
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-25 23:44 UTC
Commit: 78dda3d54f1b9b77aa03d70f4043f1ff48342e19
Parent: e488b0e
2 files changed, +5 insertions, -0 deletions
@@ -46,6 +46,7 @@ pub(in crate::routes::api) async fn update_profile(
46 46 AuthUser(user): AuthUser,
47 47 Form(req): Form<UpdateProfileRequest>,
48 48 ) -> Result<Response> {
49 + user.check_not_suspended()?;
49 50 // Validate input
50 51 if let Some(ref name) = req.display_name {
51 52 validation::validate_display_name(name)?;
@@ -314,6 +315,7 @@ pub(in crate::routes::api) async fn disconnect_stripe(
314 315 State(state): State<AppState>,
315 316 AuthUser(user): AuthUser,
316 317 ) -> Result<impl IntoResponse> {
318 + user.check_not_suspended()?;
317 319 db::users::disconnect_user_stripe(&state.db, user.id).await?;
318 320 Ok(StatusCode::NO_CONTENT)
319 321 }
@@ -40,6 +40,7 @@ pub(super) async fn setup(
40 40 Path(app_id): Path<SyncAppId>,
41 41 ) -> Result<impl IntoResponse> {
42 42 user.check_not_sandbox()?;
43 + user.check_not_suspended()?;
43 44
44 45 let app = db::synckit_billing::get_app_with_billing(&state.db, app_id)
45 46 .await?
@@ -97,6 +98,7 @@ pub(super) async fn activate(
97 98 Json(req): Json<BillingActivateRequest>,
98 99 ) -> Result<impl IntoResponse> {
99 100 user.check_not_sandbox()?;
101 + user.check_not_suspended()?;
100 102 validate_knobs(&req.enforcement_mode, req.storage_gb_cap, req.key_cap, req.gb_per_key)?;
101 103
102 104 let app = db::synckit_billing::get_app_with_billing(&state.db, app_id)
@@ -168,6 +170,7 @@ pub(super) async fn patch(
168 170 Json(req): Json<BillingPatchRequest>,
169 171 ) -> Result<impl IntoResponse> {
170 172 user.check_not_sandbox()?;
173 + user.check_not_suspended()?;
171 174 validate_knobs(&req.enforcement_mode, req.storage_gb_cap, req.key_cap, req.gb_per_key)?;
172 175
173 176 let app = db::synckit_billing::get_app_with_billing(&state.db, app_id)