refactor(subscribe): integrate Privy authentication for checkout sessions#1724
refactor(subscribe): integrate Privy authentication for checkout sessions#1724ahmednahima0-beep wants to merge 2 commits intotestfrom
Conversation
…ions - Updated `useSubscribeClick` to retrieve an access token using `usePrivy` before creating a checkout session. - Modified `createClientCheckoutSession` to accept an access token and use it in the request headers for enhanced security. - Adjusted the success URL handling to ensure proper session creation and error handling. This change improves the authentication flow for subscription management.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe checkout flow is refactored to use Privy authentication tokens instead of account IDs. The hook now asynchronously retrieves an access token for checkout, while the client session function signature changes to accept Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Hook as useSubscribeClick
participant Privy
participant ClientFn as createClientCheckoutSession
participant API as /api/subscriptions/sessions
participant Browser
User->>Hook: Click subscribe
Hook->>Privy: getAccessToken()
Privy-->>Hook: accessToken
alt Token missing
Hook->>Hook: Exit early
else Token present
Hook->>ClientFn: createClientCheckoutSession(accessToken, currentURL)
ClientFn->>API: POST with Bearer auth<br/>{successUrl}
API-->>ClientFn: {id?, url?, error?}
alt url is valid
ClientFn->>Browser: window.open(url, noopener,noreferrer)
Browser-->>User: Checkout page
else url invalid
ClientFn->>ClientFn: console.error & return {error}
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
1 issue found across 3 files
Confidence score: 3/5
- There is a concrete security concern in
lib/stripe/createClientCheckoutSession.ts: usingwindow.open(..., '_blank')withoutnoopener,noreferrercan allow reverse-tabnabbing. - Given the 6/10 severity and high confidence (9/10), this introduces some real user-impacting risk, so this sits in a moderate-risk merge range until patched.
- The fix is typically straightforward (add
noopener,noreferrerto the new-tab behavior), which keeps this from looking like a high-risk architectural issue. - Pay close attention to
lib/stripe/createClientCheckoutSession.ts- ensure external checkout navigation cannot accesswindow.opener.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/stripe/createClientCheckoutSession.ts">
<violation number="1" location="lib/stripe/createClientCheckoutSession.ts:30">
P2: `window.open` with `_blank` should include `noopener,noreferrer` to prevent reverse-tabnabbing.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- Updated the `window.open` method in `createClientCheckoutSession` to include `noopener,noreferrer` for improved security against potential tab-nabbing attacks. - Adjusted the corresponding test to verify the new parameters are correctly passed when opening the checkout session URL. This change enhances the security of the checkout process.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hooks/useSubscribeClick.ts`:
- Around line 12-23: The handleClick function currently returns early if
userData?.account_id is missing, which incorrectly blocks the checkout path;
change the guard so that the account_id check is only used for the portal flow:
inside the isSubscribed branch, verify userData?.account_id and call
createClientPortalSession(userData.account_id) (or return if missing), while
leaving the non-subscribed path to proceed to await getAccessToken() and call
createClientCheckoutSession(accessToken, window.location.href); update
references to isSubscribed, userData.account_id, createClientPortalSession,
getAccessToken, and createClientCheckoutSession accordingly.
In `@lib/stripe/createClientCheckoutSession.ts`:
- Around line 8-27: The client is posting the wrong payload and expecting the
wrong response shape; update createClientCheckoutSession to POST both accountId
and successUrl (matching app/api/stripe/session/create/route.ts) to the correct
endpoint (`/api/stripe/session/create`) via getClientApiBaseUrl(), then parse
the returned JSON as { data: session } and read session.url (or throw using
data.data.error) and still check response.ok; locate the fetch call in
lib/stripe/createClientCheckoutSession.ts and adjust the request body, endpoint
path, and response handling accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 75ff84ac-1689-4ef5-80ba-fc43ac2b02b8
⛔ Files ignored due to path filters (1)
lib/stripe/__tests__/createClientCheckoutSession.test.tsis excluded by!**/*.test.*and included bylib/**
📒 Files selected for processing (2)
hooks/useSubscribeClick.tslib/stripe/createClientCheckoutSession.ts
| const handleClick = async () => { | ||
| if (!userData?.account_id) return; | ||
|
|
||
| if (isSubscribed) { | ||
| createClientPortalSession(userData.account_id); | ||
| void createClientPortalSession(userData.account_id); | ||
| return; | ||
| } | ||
| createClientCheckoutSession(userData.account_id); | ||
|
|
||
| const accessToken = await getAccessToken(); | ||
| if (!accessToken) return; | ||
|
|
||
| void createClientCheckoutSession(accessToken, window.location.href); |
There was a problem hiding this comment.
Don't gate checkout on account_id.
The new checkout flow uses a Privy access token, so the early return on userData?.account_id blocks the non-subscribed path entirely for users who no longer have an account ID. Move that guard inside the isSubscribed branch so only the portal flow depends on account_id.
Suggested fix
const handleClick = async () => {
- if (!userData?.account_id) return;
-
if (isSubscribed) {
+ if (!userData?.account_id) return;
void createClientPortalSession(userData.account_id);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleClick = async () => { | |
| if (!userData?.account_id) return; | |
| if (isSubscribed) { | |
| createClientPortalSession(userData.account_id); | |
| void createClientPortalSession(userData.account_id); | |
| return; | |
| } | |
| createClientCheckoutSession(userData.account_id); | |
| const accessToken = await getAccessToken(); | |
| if (!accessToken) return; | |
| void createClientCheckoutSession(accessToken, window.location.href); | |
| const handleClick = async () => { | |
| if (isSubscribed) { | |
| if (!userData?.account_id) return; | |
| void createClientPortalSession(userData.account_id); | |
| return; | |
| } | |
| const accessToken = await getAccessToken(); | |
| if (!accessToken) return; | |
| void createClientCheckoutSession(accessToken, window.location.href); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hooks/useSubscribeClick.ts` around lines 12 - 23, The handleClick function
currently returns early if userData?.account_id is missing, which incorrectly
blocks the checkout path; change the guard so that the account_id check is only
used for the portal flow: inside the isSubscribed branch, verify
userData?.account_id and call createClientPortalSession(userData.account_id) (or
return if missing), while leaving the non-subscribed path to proceed to await
getAccessToken() and call createClientCheckoutSession(accessToken,
window.location.href); update references to isSubscribed, userData.account_id,
createClientPortalSession, getAccessToken, and createClientCheckoutSession
accordingly.
| const response = await fetch( | ||
| `${getClientApiBaseUrl()}/api/subscriptions/sessions`, | ||
| { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${accessToken}`, | ||
| }, | ||
| body: JSON.stringify({ successUrl }), | ||
| }, | ||
| body: JSON.stringify({ | ||
| accountId, | ||
| successUrl: `${window.location.href}`, | ||
| }), | ||
| }); | ||
| ); | ||
|
|
||
| const data = (await response.json()) as { | ||
| id?: string; | ||
| url?: string; | ||
| error?: string; | ||
| }; | ||
|
|
||
| if (!response.ok || typeof data.url !== "string") { | ||
| throw new Error(data.error || "Failed to create checkout session"); |
There was a problem hiding this comment.
Checkout requests are pointed at the wrong API contract.
app/api/stripe/session/create/route.ts expects { accountId, successUrl } and returns { data: session }, but this client now posts only { successUrl } to /api/subscriptions/sessions and then looks for a top-level url. Unless a matching backend route lands in this PR, the checkout flow will fail at runtime.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/stripe/createClientCheckoutSession.ts` around lines 8 - 27, The client is
posting the wrong payload and expecting the wrong response shape; update
createClientCheckoutSession to POST both accountId and successUrl (matching
app/api/stripe/session/create/route.ts) to the correct endpoint
(`/api/stripe/session/create`) via getClientApiBaseUrl(), then parse the
returned JSON as { data: session } and read session.url (or throw using
data.data.error) and still check response.ok; locate the fetch call in
lib/stripe/createClientCheckoutSession.ts and adjust the request body, endpoint
path, and response handling accordingly.
useSubscribeClickto retrieve an access token usingusePrivybefore creating a checkout session.createClientCheckoutSessionto accept an access token and use it in the request headers for enhanced security.This change improves the authentication flow for subscription management.
Summary by CodeRabbit