Skip to content

Fix auth end-to-end and add Cloudflare verification handoff - #55

Merged
SaharPak merged 8 commits into
mainfrom
fix/auth-end-to-end-product
Jul 2, 2026
Merged

Fix auth end-to-end and add Cloudflare verification handoff#55
SaharPak merged 8 commits into
mainfrom
fix/auth-end-to-end-product

Conversation

@SaharPak

@SaharPak SaharPak commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Consolidates the auth work and adds a Cloudflare-focused deployment handoff. The auth code itself (cookie-preserving proxy redirects, onboarding upsert, callback error logging) was already on fix/auth and is verified to work; this PR brings that work forward as a focused branch and replaces Vercel assumptions in the docs.

Auth fixes (verified)

  • src/lib/supabase/middleware.ts — added redirectWithCookies helper. Both redirect sites (protected-route denial, onboarding gate) forward any refreshed auth cookies so the session survives proxy-driven redirects. Without this, an expired-session refresh inside getUser() was silently discarded on the next redirect, causing the classic "log in then loop back to /login" symptom.
  • src/app/onboarding/actions.ts — switched updateupsert({...}, { onConflict: "id" }). If the handle_new_user trigger never created a profile row in the live DB, the previous update matched zero rows silently and the user was stuck on /onboarding forever.
  • src/app/auth/callback/route.ts — consolidated error logging to a single console.error that records only error.message and error.status (no tokens, codes, cookies, or env values). Removed redundant fallback path.

Validation on this branch:

  • npm run lint — pass
  • npm run build — pass (14 routes built)
  • scripts/auth-smoke.sh — 16/16 pass

Cloudflare correction

Vercel assumptions were removed because deployment target is Cloudflare. Updated:

  • README.md — Deployment section now describes Cloudflare Pages + OpenNext, not Vercel.
  • OPERATIONS.md — Cron is now a Cloudflare Cron Trigger (wrangler.toml); vercel.json is documented as legacy.
  • DEVELOPER_HANDOFF_PRODUCT_BUILDERS.md — replaced Vercel references with Cloudflare.
  • docs/AUTH_DEBUGGING_HANDOFF.md — added sections G–K for Supabase URL Configuration, Google Cloud Console, Cloudflare Pages project, Cloudflare env vars (public vs secret), and local .dev.vars.

vercel.json is intentionally left in place — it is inert on Cloudflare (Cloudflare ignores it) and acts as a historical reference for the cron schedule. Remove it once the Cloudflare Cron Trigger is verified to fire.

A wrangler.toml template is included in OPERATIONS.md ("Cloudflare setup" section) but not yet committed to the repo. The Owner must add it with their Cloudflare account ID / project name — those values cannot be invented. The @opennextjs/cloudflare adapter was not installed on this branch because it is a deployment-environment addition, not an auth fix.

Manual owner checklist (must do, cannot be automated)

  1. Create Cloudflare Pages project, install @opennextjs/cloudflare, add wrangler.toml from OPERATIONS.md.
  2. Set the four env vars in Cloudflare (two public at build time, two secret). See docs/AUTH_DEBUGGING_HANDOFF.md § J.
  3. Supabase Auth → URL Configuration: add http://localhost:3000 (dev) and the Cloudflare production domain (with /auth/callback in Additional Redirect URLs).
  4. Supabase Auth → Providers → Google: enable, paste Client ID/Secret from Google Cloud Console.
  5. Google Cloud Console → OAuth client: add Cloudflare domain(s) to Authorized JavaScript origins, keep https://<ref>.supabase.co/auth/v1/callback as Authorized redirect URI.
  6. Google Cloud Console → Consent screen: add test account if status is "Testing".
  7. Run migrations 001–003 in Supabase SQL editor (idempotent).
  8. Create public Storage bucket product-images.

What was not done (and why)

  • db push / db reset against production — destructive, owner-only.
  • service-role key use beyond cron handler — already used in src/app/api/cron/demo-day/route.ts; no new uses were added.
  • real Google OAuth browser test — owner has the test Google account. Smoke script exercises every code path that doesn't require a real user session.
  • real magic-link inbox test — magic-link rate limit was hit in earlier sessions; only the owner can click a fresh link.
  • Playwright suite — out of scope for this branch; recommended as the next task.
  • wrangler.toml committed to repo — needs the owner's account ID; template is in OPERATIONS.md.

Safety notes

  • No .env / .env.local / secrets were committed. Branch diff excludes them.
  • No destructive database operations were run. No migrations were applied to the production project.
  • The supabase/migrations files were read but not modified.
  • The git push and this PR were performed under the owner's own GitHub credentials via the locally authenticated gh CLI.

Sahar Pakseresht and others added 5 commits June 29, 2026 22:23
…dation

Production sign-in skipped onboarding because the Cloudflare x-forwarded-host
rewrite overwrote the redirect path. Enforce handle setup in the proxy, block
open redirects, and verify sessions with getUser() on the server.

Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve proxy redirect cookie forwarding, onboarding profile upsert, and
callback error logging. Add DEVELOPER_HANDOFF_PRODUCT_BUILDERS.md for the
next developer debugging auth on fix/auth.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Exercises every public and protected route plus the auth-callback error
paths against a running dev server. Asserts status codes and Location
headers without triggering Supabase auth flows, touching the database,
or printing tokens / cookies / env values.

Usage:
  ./scripts/auth-smoke.sh
  BASE_URL=https://productbuilders.app ./scripts/auth-smoke.sh
@SaharPak

SaharPak commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

Follow-up hardening (this push)

Two changes added since the initial review of this PR:

1. scripts/auth-smoke.sh is now BASE_URL-safe

The previous version stripped localhost:3000 from the Location header, which silently broke against production and Cloudflare preview URLs.

  • New normalize_location() turns absolute or relative Location headers into just path+query.
  • Unit-tested against 10 inputs: relative, absolute localhost, absolute production (https://productbuilders.app), Cloudflare Pages preview (https://fix-auth-end-to-end-product.<project>.pages.dev), empty, bare host, trailing-slash host, query, fragment, uppercase scheme.
  • All assertions now check the normalized path+query, not the host-prefixed raw value.

Works against http://localhost:3000, https://productbuilders.app, and any <branch>.<project>.pages.dev Cloudflare preview URL.

2. src/app/auth/callback/route.ts no longer trusts x-forwarded-host

The previous production branch read x-forwarded-host to build the post-login redirect host. That header is spoofable: an attacker can set x-forwarded-host: evil.com on a forged request and have the callback redirect a freshly-authenticated user to https://evil.com/....

Added getSafeRedirectOrigin(request) with strict priority:

  1. NEXT_PUBLIC_SITE_URL — owner-configured, validated as http(s): URL.
  2. Request origin in development.
  3. Request origin in production (sane fallback; on Cloudflare Pages the request URL is the external URL the browser sent).

x-forwarded-host is never consulted. If NEXT_PUBLIC_SITE_URL is unset in production, the callback logs a one-shot warning and falls back to the request origin.

New env var: NEXT_PUBLIC_SITE_URL

Documented in .env.example, README.md, and docs/AUTH_DEBUGGING_HANDOFF.md. Required in production. Optional in dev (request origin is used).

Validation (latest commit ceb0bcb)

Command Result
npm run lint pass
npm run build pass — 14 routes
scripts/auth-smoke.sh (default) 16 passed, 0 failed
BASE_URL=http://localhost:3000 ./scripts/auth-smoke.sh 16 passed, 0 failed
normalize_location() unit tests 10 passed
Direct callback probes (/auth/callback with no code / empty code / provider error) All redirect to /login?error=... as expected

Remaining owner actions (unchanged from before)

All previous Supabase / Google / Cloudflare dashboard checks still apply. In addition:

  • Set NEXT_PUBLIC_SITE_URL per environment in Cloudflare Pages (e.g. https://productbuilders.app for Production, the pages.dev preview URL for Preview).

No destructive operations. No secrets committed. No force-push.

@SaharPak
SaharPak force-pushed the fix/auth-end-to-end-product branch from ceb0bcb to 5a0e834 Compare July 2, 2026 12:15
Sahar Pakseresht added 3 commits July 2, 2026 15:16
Consolidates the architectural map, root-cause analysis, fixes, dashboard
checklist, manual test steps, and remaining risks for the auth work on
this branch. Intended to be readable by the owner and any future
contributor without prior context.
…cklist

The deployment target is Cloudflare Pages via OpenNext, not Vercel.
Update README, OPERATIONS, the developer handoff, and this doc to:

- describe the Cloudflare Pages + OpenNext deployment model
- move the demo-day cron from vercel.json into a Cloudflare Cron Triggers
  template (wrangler.toml)
- document required Cloudflare env vars and their visibility
  (public at build time vs secret)
- list the wrangler.toml placeholders as 'Owner must confirm'
- keep vercel.json as legacy (Cloudflare ignores it) until the cron is
  verified to fire from Cloudflare

Vercel assumptions were removed because deployment target is Cloudflare.
Remaining mentions of vercel.json are intentional historical references
explaining the migration path.
Two follow-up hardenings on top of the auth work in this branch:

1. scripts/auth-smoke.sh is now BASE_URL-safe.
   - Added normalize_location() that turns an absolute Location header
     (http://host:port/path, https://host/path) into just /path+query.
     Handles Cloudflare preview URLs, localhost, production, relative
     redirects, empty input, bare host, fragments, query strings, and
     case-insensitive schemes.
   - All redirect assertions now check the normalized path+query against
     the expected prefix, instead of substring-stripping the host (which
     broke against non-localhost URLs).

2. src/app/auth/callback/route.ts no longer trusts x-forwarded-host.
   The previous production branch read x-forwarded-host to build the
   post-login redirect host. That value is spoofable: an attacker can
   set x-forwarded-host: evil.com on a forged request and have the
   callback redirect the freshly-authenticated user to evil.com.

   Added getSafeRedirectOrigin(request). Strict priority order:
     1. NEXT_PUBLIC_SITE_URL (owner-configured, validated as http(s):)
     2. request origin in development
     3. request origin in production (sane fallback; the request URL on
        Cloudflare Pages is the external URL the browser sent)
   x-forwarded-host is never consulted. If NEXT_PUBLIC_SITE_URL is
   unset in production, the callback logs a one-shot warn and falls
   back to the request origin.

Documentation:
- .env.example lists NEXT_PUBLIC_SITE_URL with a comment explaining
  that it is required in production.
- README.md adds NEXT_PUBLIC_SITE_URL to the env-var list.
- docs/AUTH_DEBUGGING_HANDOFF.md adds the new env var to the
  Cloudflare env-vars section, the architecture map, the root-cause
  table, the auth flow, and the local-setup steps.

Validation on this commit:
- npm run lint        pass
- npm run build       pass (14 routes built)
- scripts/auth-smoke.sh 16 passed, 0 failed (default and explicit
  BASE_URL=http://localhost:3000)
- normalize_location() unit-tested against 10 inputs (relative,
  absolute localhost, absolute production, Cloudflare preview, empty,
  bare host, trailing-slash host, query, fragment, uppercase scheme)
- All six checked.

No destructive operations. No secrets committed. No force-push.
@SaharPak
SaharPak force-pushed the fix/auth-end-to-end-product branch from 5a0e834 to 17b5b93 Compare July 2, 2026 12:16
@SaharPak
SaharPak merged commit 3fc9e88 into main Jul 2, 2026
@SaharPak
SaharPak deleted the fix/auth-end-to-end-product branch July 2, 2026 12:25
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