Skip to content

feat(examples/hosted-checkout-demo): zero-supabase local dev mode - #186

Open
dhruv-sanan wants to merge 3 commits into
solvapay:mainfrom
dhruv-sanan:feat/hosted-checkout-demo-zero-supabase
Open

feat(examples/hosted-checkout-demo): zero-supabase local dev mode#186
dhruv-sanan wants to merge 3 commits into
solvapay:mainfrom
dhruv-sanan:feat/hosted-checkout-demo-zero-supabase

Conversation

@dhruv-sanan

@dhruv-sanan dhruv-sanan commented May 10, 2026

Copy link
Copy Markdown
Contributor

Description

Running hosted-checkout-demo currently requires a Supabase account, ~20 minutes of project setup, and you still get a silent 401 on every API call with no hint why. This PR adds a zero-config dev mode: set placeholder Supabase env vars and the example works end-to-end with just a SolvaPay test key.
Developers with a real Supabase project are unaffected — every production code path is identical to upstream. The changes branch on a single isSupabaseConfigured / isDevMode flag derived from env vars.

Type of Change

  • New feature (non-breaking change which adds functionality)

Changes Made

  • proxy.ts: added /api/create-checkout-session and /api/create-customer-session to publicRoutes — middleware was blocking both routes entirely when no Supabase session existed; authenticated requests still have x-user-id set as before
  • Providers.tsx: extended the Supabase adapter guard to also catch placeholder string values (e.g. https://placeholder.supabase.co), not just missing env vars — previously a dev copying env.example verbatim would silently initialise a broken adapter
  • ClientLayout.tsx: added isSupabaseConfigured flag; when false (no real Supabase project), skip the auth gate and render directly; when true, the original useEffect + login screen behavior is preserved exactly
  • route.ts: added isDevMode path that uses a stub customer when no x-user-id header is present; production path delegates to createCheckoutSession from @solvapay/next unchanged

Changeset

  • Or — this PR touches no published packages (changelog N/A)

Testing

  • pnpm test — unit tests pass
  • pnpm build — full monorepo build passes
  • Manual testing completed

To reproduce the dev-mode flow:

SOLVAPAY_SECRET_KEY=sp_sandbox_your_key
NEXT_PUBLIC_PRODUCT_REF=prd_your_ref
NEXT_PUBLIC_SUPABASE_URL=https://placeholder.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=placeholder
pnpm --filter hosted-checkout-demo dev → open localhost:3000 → dashboard renders without login → click Upgrade → SolvaPay hosted checkout loads.

Checklist
- My code follows the project's style guidelines (pnpm lint / pnpm format)
- I have performed a self-review of my code
- I have commented my code in hard-to-understand areas (not narration comments)
- My changes generate no new warnings

Additional Notes
ClientLayout.tsx in dev mode renders the dashboard unconditionally — there is no login screen. In production (Supabase configured), unauthenticated users still see the Supabase login screen exactly as before. API calls from unauthenticated users in production still 401 at the middleware layer.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: changes are confined to the `hosted-checkout-demo` example and primarily add env-guarded dev-only fallbacks; production/Supabase-configured paths remain the same but misconfiguration could unintentionally bypass the auth gate in the demo.
> 
> **Overview**
> Adds a *zero-Supabase* local dev mode for `hosted-checkout-demo` by treating missing/placeholder Supabase env vars as “not configured” and bypassing the login/auth gate on the client.
> 
> In dev mode, `/api/create-checkout-session` no longer depends on `x-user-id`; it creates/uses a stub customer via `@solvapay/server` and returns a checkout URL directly, while the existing `createCheckoutSession` helper remains the production path.
> 
> The Supabase provider adapter is now disabled when env vars are placeholder values, and the auth proxy middleware explicitly allows unauthenticated access to the session-creation endpoints via `publicRoutes`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit bcefa95881a7b0f8ea1357a0691a7538f6541d38. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit bcefa95. Configure here.

*/
export const proxy = createSupabaseAuthMiddleware({
publicRoutes: [],
publicRoutes: ['/api/create-checkout-session', '/api/create-customer-session'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public routes allow x-user-id header forgery in production

High Severity

Making /api/create-checkout-session and /api/create-customer-session unconditionally public (even in production) allows unauthenticated attackers to forge the x-user-id header. The middleware for public routes copies all incoming headers via new Headers(req.headers) but only sets x-user-id if a valid token is present — it never clears a client-supplied one. Downstream, getAuthenticatedUserCore trusts the x-user-id header unconditionally. An attacker can create checkout or customer sessions impersonating any user by simply including x-user-id: victim-id in the request.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bcefa95. Configure here.

// meaning no real Supabase project is wired up.
const isDevMode =
!process.env.NEXT_PUBLIC_SUPABASE_URL ||
process.env.NEXT_PUBLIC_SUPABASE_URL.includes('placeholder')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent dev-mode detection between client and server

Medium Severity

The isDevMode flag in route.ts only checks NEXT_PUBLIC_SUPABASE_URL, while isSupabaseConfigured in ClientLayout.tsx additionally checks NEXT_PUBLIC_SUPABASE_ANON_KEY. When the URL is real but the anon key is 'placeholder', the client renders in dev mode (no auth gate, no token sent) but the server takes the production path expecting authentication. This mismatch causes the checkout flow to silently fail with a 401.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bcefa95. Configure here.

@tomber

tomber commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for this — the zero-supabase dev mode is a nice quality-of-life improvement for trying the demo. The branch is updated and CI is green, but I'm holding the merge because Bugbot flagged two issues that need addressing first (and since this example is something merchants copy-paste, the auth one matters):

1. High — x-user-id header forgery in production (proxy.ts)

Making /api/create-checkout-session and /api/create-customer-session unconditionally public means the auth middleware lets these routes through in production too. The middleware copies incoming headers via new Headers(req.headers) and only sets x-user-id when a valid token is present — it never clears a client-supplied one. So an unauthenticated caller could forge x-user-id and have it trusted downstream.

Please gate the public-route exemption to dev mode only (e.g. compute publicRoutes from the same dev-mode check), so production keeps these routes authenticated.

2. Medium — inconsistent dev-mode detection (client vs server)

route.ts derives isDevMode from NEXT_PUBLIC_SUPABASE_URL only, while ClientLayout.tsx's isSupabaseConfigured also checks NEXT_PUBLIC_SUPABASE_ANON_KEY. If the URL is real but the anon key is 'placeholder', the client renders in dev mode (no auth gate, no token sent) while the server takes the production path expecting auth — a broken state.

Please extract a single shared isDevMode / isSupabaseConfigured helper and use it on both sides so they can't drift.

Once those two are addressed I'll re-run CI and merge. Appreciate the contribution!

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.

2 participants