Summary
Maple caches the billing service third-party JWT in sessionStorage as maple_billing_token. That token is Maple/billing-server state, not OpenSecret SDK state, so the OpenSecret SDK should not know about it or clear it. Maple needs to clear or scope this token whenever the current OpenSecret user identity changes.
Right now the token is stored under a single unscoped key and reused until the billing API rejects it. If the OpenSecret auth identity changes while a still-valid billing token remains in the tab, billing calls can be authorized as the previous user while the UI reads email/profile state from the current user.
Suspected ordering
A plausible ordering that can produce incorrect checkout ownership:
- A browser tab has a valid cached
maple_billing_token for User A.
- The OpenSecret auth tokens in
localStorage change to User B through login/OAuth/deep-link/native auth or another auth transition.
- The cached billing token remains in
sessionStorage because it is not tied to the OpenSecret user id.
- The pricing page builds checkout request data from current UI auth state for User B.
BillingService reuses the stale billing JWT for User A.
- Billing server keys checkout/customer creation by the JWT subject, so checkout is created under User A while request body fields came from User B.
This is especially risky around selected-plan auto-checkout flows because checkout can be triggered immediately after auth/navigation.
Code paths to audit
frontend/src/billing/billingService.ts stores maple_billing_token as one global sessionStorage key and reuses it until rejected.
frontend/src/components/DeepLinkHandler.tsx writes new OpenSecret auth tokens and reloads without clearing Maple billing state.
- Some signout paths call
os.signOut() directly. Since the OpenSecret SDK should not know about Maple billing state, those Maple UI paths should clear billing state themselves before/after signout.
/login and /signup already-logged-in redirect effects can navigate to /pricing?selected_plan=... without clearing or validating the cached billing token for the current user.
- Billing React Query keys such as
["billingStatus"] are not scoped by current user id.
Expected behavior
Maple should guarantee that billing requests use a billing JWT for the current OpenSecret user, or generate a fresh one before making the request.
Suggested fix
- Make billing token ownership explicit in Maple, not the OpenSecret SDK.
- Clear
maple_billing_token whenever os.auth.user?.user.id changes or auth becomes logged out.
- Consider storing the billing token under a user-scoped key, or decode/check the token
sub before reuse.
- Clear billing token in Maple-owned auth transition paths, including deep-link/native auth and signout UI paths.
- Scope billing query keys by user id, for example
["billingStatus", userId].
- Prevent selected-plan auto-checkout from running until billing token state has been validated/refreshed for the current user.
No customer identifiers or production-specific data are included here because this repo is public.
Summary
Maple caches the billing service third-party JWT in
sessionStorageasmaple_billing_token. That token is Maple/billing-server state, not OpenSecret SDK state, so the OpenSecret SDK should not know about it or clear it. Maple needs to clear or scope this token whenever the current OpenSecret user identity changes.Right now the token is stored under a single unscoped key and reused until the billing API rejects it. If the OpenSecret auth identity changes while a still-valid billing token remains in the tab, billing calls can be authorized as the previous user while the UI reads email/profile state from the current user.
Suspected ordering
A plausible ordering that can produce incorrect checkout ownership:
maple_billing_tokenfor User A.localStoragechange to User B through login/OAuth/deep-link/native auth or another auth transition.sessionStoragebecause it is not tied to the OpenSecret user id.BillingServicereuses the stale billing JWT for User A.This is especially risky around selected-plan auto-checkout flows because checkout can be triggered immediately after auth/navigation.
Code paths to audit
frontend/src/billing/billingService.tsstoresmaple_billing_tokenas one globalsessionStoragekey and reuses it until rejected.frontend/src/components/DeepLinkHandler.tsxwrites new OpenSecret auth tokens and reloads without clearing Maple billing state.os.signOut()directly. Since the OpenSecret SDK should not know about Maple billing state, those Maple UI paths should clear billing state themselves before/after signout./loginand/signupalready-logged-in redirect effects can navigate to/pricing?selected_plan=...without clearing or validating the cached billing token for the current user.["billingStatus"]are not scoped by current user id.Expected behavior
Maple should guarantee that billing requests use a billing JWT for the current OpenSecret user, or generate a fresh one before making the request.
Suggested fix
maple_billing_tokenwheneveros.auth.user?.user.idchanges or auth becomes logged out.subbefore reuse.["billingStatus", userId].No customer identifiers or production-specific data are included here because this repo is public.