Add NEXT_PUBLIC_GARY_SKIP_AUTH dev/demo bypass

✅ merged · #11 · foolish-bandit/gary ← foolish-bandit/gary · opened 19d ago by foolish-bandit · merged 19d ago by foolish-bandit · self · +62-3 across 4 files · ↗ on GitHub

From the PR description

Summary

Adds an environment-gated dev/demo bypass so the app can be inspected without Supabase login. Frontend-only. No auth code is removed; login/signup screens and the protected-route gate stay intact.

How auth currently works

  • AuthProvider (root, via Providers) calls supabase.auth.getSession() on mount, sets User { id, email }, listens to onAuthStateChange, and POSTs to /user/profile to ensure the backend profile row.
  • Protected gate: (pages)/layout.tsx:62-65 redirects to /login when !authLoading && !isAuthenticated.
  • /login and /signup redirect to /assistant once isAuthenticated.
  • Backend-hitting components read useAuth().user for id/email and call supabase.auth.getSession() directly to attach the bearer token.

Where the bypass was added

  • frontend/src/contexts/AuthContext.tsx - module-level SKIP_AUTH = process.env.NEXT_PUBLIC_GARY_SKIP_AUTH === "true". Lazy useState initializers seed the demo user when on. useEffect bails out after a one-time console.warn. signOut() no-ops when on. The flag is exposed on the context as isAuthBypassed.
  • frontend/src/components/dev-auth-banner.tsx - new slim amber strip that says "Dev auth bypass enabled".
  • frontend/src/app/(pages)/layout.tsx - renders <DevAuthBanner /> at the top of the layout when isAuthBypassed is true.
  • frontend/.env.local.example - documents the variable (commented out by default).

Fake user shape

{
    id:    "00000000-0000-0000-0000-000000000000",
    email: "demo@gary.local",
}

UUID-shaped so anything that string-coerces or hashes the id behaves predictably. The email.split("@")[0] fallback in InitialView produces a sensible greeting ("Hi, demo") when no profile exists.

How to enable

Local dev

# in frontend/.env.local
NEXT_PUBLIC_GARY_SKIP_AUTH=true

Then npm run dev. The login screen redirects straight into /assistant, the amber banner shows, and the home renders without needing a real Supabase project.

Deployed Cloudflare Worker

NEXT_PUBLIC_* is baked at build time by OpenNext. Set the var in the build environment before npm run cf:build && npm run cf:deploy, or in the Cloudflare Worker's build configuration. Use a separate Worker (e.g. a preview/staging deploy) for this so production keeps real auth.

Off path verified unchanged

  • Lazy useState(() => SKIP_AUTH ? DEMO_USER : null) evaluates to null when the flag is unset, exactly like today.
  • Lazy useState(() => !SKIP_AUTH) evaluates to true, exactly like today.
  • The useEffect body's if (SKIP_AUTH) branch is dead; the existing checkUser() + onAuthStateChange() block runs as before.
  • signOut()'s early-return is dead; it still calls supabase.auth.signOut() and clears the user.
  • (pages)/layout.tsx only renders <DevAuthBanner /> when isAuthBypassed is true - false && ... collapses to nothing.
  • Login/signup pages are untouched.

Limitations

  • Backend calls 401 while the bypass is on. No real Supabase session exists, so supabase.auth.getSession() returns null and the bearer token isn't attached. The console warning calls this out. The bypass is for inspecting the UI, not for exercising the backend.
  • /user/profile POST is skipped while on. Wouldn't succeed without a token anyway.
  • signOut is intentionally a no-op while on. Without it, the next mount would re-create the demo user and the user would seem to bounce. Cleaner to just keep the demo session stable.
  • Build-time toggle on Cloudflare deploys. OpenNext bakes NEXT_PUBLIC_* at build time, so flipping the var without rebuilding has no effect on the deployed Worker.

Tests

There are no auth-gating tests in this repo, and no test scripts in frontend/package.json or backend/package.json. Nothing to update.

Build / lint

  • npx tsc --noEmit - clean.
  • npm run lint - 107 problems on main, 107 after (initial draft introduced one new react-hooks/set-state-in-effect; refactored to lazy useState initializers and bypass return in the effect, which restores the baseline).
  • npm run build - same fonts.googleapis.com sandbox network failure on main and on this branch; environmental. Workers Builds will run the real build on the PR.

Test plan

  • Workers Build for this PR succeeds.
  • On the preview Worker URL without the env var: log in normally, redirect to /assistant, no amber banner. Sign out works.
  • In a separate preview build with NEXT_PUBLIC_GARY_SKIP_AUTH=true: opening the root URL lands on /assistant without going through /login; the amber "Dev auth bypass enabled" banner is visible at the top; the four Gary-mode cards render.
  • In bypass mode, navigate to /login manually - it should redirect away to /assistant because isAuthenticated is true.
  • In bypass mode, click Sign Out in the sidebar dropdown - nothing happens (no-op), session stays stable.
  • Console warning fires once per session in bypass mode.

https://claude.ai/code/session_019pRkhcGDRKQWHjzAnV5yCL


Generated by Claude Code

Our analysis

Environment-gated dev auth bypass for Gary — read the full analysis →

Think the analysis missed something the PR description covers?

Commits in this PR (1)

SHA Subject Author Date
0b9ad4db Add NEXT_PUBLIC_GARY_SKIP_AUTH dev/demo bypass Claude 2026-05-07 ↗ GitHub
commit body
Lets us inspect the app without going through Supabase login during
development and on demo deploys. Frontend-only.

When NEXT_PUBLIC_GARY_SKIP_AUTH=true:
- AuthContext initializes with a fake demo user
  ({ id: "00000000-...-000", email: "demo@gary.local" }) via lazy
  useState, so isAuthenticated is true immediately.
- The Supabase session check + auth-state-change subscription are
  skipped (the useEffect bails out after a one-time console.warn).
- signOut becomes a no-op so the demo session stays stable.
- A slim amber "Dev auth bypass enabled" banner renders at the top
  of the (pages) layout via the new DevAuthBanner component.
- The flag is exposed as `isAuthBypassed` on the auth context so
  any consumer can branch on it later.

When the flag is unset or "false":
- useState initializers return null/true, identical to today.
- The effect's bypass branch is dead and the original Supabase
  logic runs unchanged. Login/signup, signOut, and the redirect
  gate in (pages)/layout.tsx are untouched.

Documents the variable in frontend/.env.local.example as
commented-out so normal setups stay on real auth.

No backend changes, no schema changes, no auth contract changes.
Backend calls made while the bypass is on will 401 because no
real Supabase session exists - that's intentional and noted in
the console warning.

https://claude.ai/code/session_019pRkhcGDRKQWHjzAnV5yCL

Capture this PR into my fork

Download a Markdown prompt that tells Claude how to port every commit in this PR into your working tree. Run it via claude -p < capture-pull-11.md from inside the repo you want the changes in.

⬇ Download capture-pull-11.md