Skip to content

fix(security): RQ-3893 remove the AppSumo redemption flow - #116

Open
rohitneharabrowserstack wants to merge 1 commit into
masterfrom
rq-3893-remove-appsumo-redemption
Open

fix(security): RQ-3893 remove the AppSumo redemption flow#116
rohitneharabrowserstack wants to merge 1 commit into
masterfrom
rq-3893-remove-appsumo-redemption

Conversation

@rohitneharabrowserstack

Copy link
Copy Markdown

Client half of removing AppSumo redemption for RQ-3893. Backend: requestly/requestly-cloud#882.

Supersedes #115. AppSumo is being retired, so rather than routing the redemption screen through new server-side callables, the screen is deleted. That closes the finding outright and removes the cross-repo deploy ordering #115 needed.

Why this file was the security problem

The redemption screen was the only client reader and writer of the appSumoCodes Firestore collection:

// Appsumo.tsx:99   validate
const docSnap = await getDoc(doc(db, "appSumoCodes", enteredCode));
// Appsumo.tsx:122  consume
batch.update(docRef, { redeemed: true });

Those two calls are the sole reason the rules had to grant every authenticated user read, update on the collection — which let any account enumerate licence codes and clear redeemed on a paying customer's. With the screen gone, requestly-cloud#882 can delete the rule outright.

Removed

Everything below was reachable only from the redemption screen:

  • components/landing/Appsumo/ — the modal and its workspace dropdown (4 files)
  • the /appsumo route in miscRoutes.tsx and PATHS.APPSUMO
  • its entry in onboarding's EXCLUDED_PATHS
  • the "Signup to redeem your AppSumo code" variant of the signup header, plus the PATHS / useLocation imports the header was the last consumer of
  • trackAppsumoCodeRedeemed and its APPSUMO_CODE_REDEEMED event name

Deliberately kept — existing customers are unaffected

An AppSumo plan is a persisted value on the team document (plan: 'basic_appsumo_v0'), not something derived from appsumo.codes at read time. So everything that recognises and displays the deal stays:

  • PremiumPlanBadge (suppresses the upgrade nudge for AppSumo teams)
  • PricingUtils' "AppSumo" label
  • UserPlanDetails, ActiveLicenseInfo / SubscriptionInfo (the "N codes redeemed" display)
  • PlanType.APPSUMO, and the workspace/user types carrying the field

Deleting those would revoke live lifetime plans. That is a separate product decision with a migration attached, and this PR does not make it.

Verification

tsc --noEmit over the whole app/ project, branch vs master:

errors
master 1398
this branch 1397

The single reduction is the deleted AppSumoWorkspaceDropdown.tsx diagnostic. Normalising line numbers (this PR shifts lines in files it edits), the set difference of new errors is empty.

eslint --ext .js,.jsx on every .js file this PR touches: clean — which is why the orphaned PATHS / useLocation imports were removed rather than left behind.

No residual references remain: PATHS.APPSUMO, APPSUMO_CODE_REDEEMED, trackAppsumoCodeRedeemed, appSumoCodes and landing/Appsumo all return zero matches under app/src.

AppSumo is being retired, so the escalation path this ticket describes is removed
rather than guarded. The redemption screen was the only client-side reader and
writer of the `appSumoCodes` collection — it validated a typed code with `getDoc`
and marked it `redeemed` in a batch write — which is the sole reason the Firestore
rules had to grant every authenticated user read+update on that collection.

Removed, all of it reachable only from the redemption screen:

- components/landing/Appsumo/ (the modal and its workspace dropdown)
- the /appsumo route and PATHS.APPSUMO
- the entry in onboarding's EXCLUDED_PATHS
- the "Signup to redeem your AppSumo code" variant of the signup header, plus the
  now-orphaned PATHS / useLocation imports it was the last consumer of
- trackAppsumoCodeRedeemed and its APPSUMO_CODE_REDEEMED event name

Deliberately kept: everything that serves customers who already hold the deal. An
AppSumo plan is a persisted value on the team document (`plan: 'basic_appsumo_v0'`),
not something derived from `appsumo.codes` at read time, so entitlement display and
gating — PremiumPlanBadge, PricingUtils' "AppSumo" label, UserPlanDetails,
ActiveLicenseInfo, PlanType.APPSUMO — are untouched. Removing those would revoke
live lifetime plans, which is a separate decision with a migration attached.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rohitneharabrowserstack

Copy link
Copy Markdown
Author

Production bundle verified — nothing else breaks

Built the full production bundle on this branch and inspected the output, which is the check tsc cannot do for a deletion: a dangling module, an orphaned SCSS import, or a route that no longer resolves would all fail here.

✓ 10473 modules transformed
✓ built in 1m 5s        →  build/index.html, 47 JS chunks

Redemption code is gone from the shipped bundle

Searched in build/ Result
appSumoCodes absent
"Please enter your AppSumo code" absent
appsumo_code_redeemed absent
"Signup to redeem your AppSumo code" absent
redeemSubmittedCodes absent

Entitlement code is still shipped, as intended

AppSumo and appsumo both remain in the bundle — the plan label and PlanType.APPSUMO that existing lifetime customers depend on. That asymmetry is the point of this PR: the redemption path is removed, the entitlement path is not.

(basic_appsumo is absent from the client bundle, correctly — it is a backend constant in APPSUMO_DEALS, never client-side.)

Also verified

  • tsc --noEmit, whole project: 1398 → 1397 errors, and normalising line numbers the set of new errors is empty. The single reduction is the deleted AppSumoWorkspaceDropdown.tsx diagnostic.
  • eslint --ext .js,.jsx on every .js this PR touches: clean.
  • Zero residual source references to PATHS.APPSUMO, APPSUMO_CODE_REDEEMED, trackAppsumoCodeRedeemed, appSumoCodes, landing/Appsumo.

Note for anyone reproducing this

The app build needs a three-level chain that no single command wires up: common/rule-processor (webpack) → root @requestly/requestly-core (rollup) → app (vite). Skipping it fails with Failed to resolve entry for package "@requestly/requestly-core" — on master identically, so that failure is environmental and not a signal about any branch. Also note the root prepare hook (husky install) breaks a clean npm install with exit 127, which rolls the whole install back.

Not covered here

This was verified by build and static analysis, not by a running app: both PR-preview workflows are disabled (Deploy Preview here is disabled_manually, and requestly-cloud's counterpart likewise), and a local run needs app/.env, which I do not have access to. The paths I would exercise given a live environment are the sign-in/sign-up form (this PR removes PATHS and useLocation from AuthForm, which every user hits), and the plan-display surfaces for a team that already holds an AppSumo deal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant