Skip to content

fix(auth): register Google/GitHub OAuth via fetch + redirect#240

Merged
seanhanca merged 5 commits into
mainfrom
fix/register-using-googleandgithub
Apr 8, 2026
Merged

fix(auth): register Google/GitHub OAuth via fetch + redirect#240
seanhanca merged 5 commits into
mainfrom
fix/register-using-googleandgithub

Conversation

@seanhanca
Copy link
Copy Markdown
Contributor

@seanhanca seanhanca commented Apr 8, 2026

Summary

Register page Google/GitHub buttons used window.location to /api/v1/auth/oauth/:provider, which returns JSON. The browser showed raw JSON instead of redirecting to the provider.

This change uses useAuth().loginWithOAuth (same as login): fetch with credentials, then window.location.href to data.url.

Tests

  • apps/web-next/tests/oauth.spec.ts — optional Playwright checks for login + register OAuth entrypoints; run with RUN_OAUTH_E2E=1 (skipped by default in CI).

Verification

  • npm run typecheck in apps/web-next

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Improved OAuth buttons with better loading/accessibility semantics (ARIA attributes) and clear in-flight state for the active provider.
  • Bug Fixes

    • Prevents duplicate OAuth submissions by keeping provider buttons disabled while an OAuth request is in progress.
  • Tests

    • Added opt-in end-to-end tests verifying Google and GitHub OAuth redirect flows.

The OAuth API returns JSON with the provider URL; document navigation
showed raw JSON. Align register with login using loginWithOAuth.

Add optional Playwright OAuth entrypoint tests (RUN_OAUTH_E2E=1), including register page.

Made-with: Cursor
@seanhanca seanhanca requested a review from eliteprox April 8, 2026 01:40
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
naap-platform Ready Ready Preview, Comment Apr 8, 2026 5:40pm

Request Review

@github-actions github-actions Bot added size/M Medium PR (51-200 lines) scope/shell Shell app changes labels Apr 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 827b9639-80cf-44a8-a89f-ee7a9019b9d1

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac13d7 and c5fd904.

📒 Files selected for processing (1)
  • apps/web-next/src/app/(auth)/register/register-form.tsx

📝 Walkthrough

Walkthrough

Add ARIA/loading semantics to OAuth buttons in the registration form and introduce Playwright E2E tests that verify Google/GitHub OAuth redirect behavior for login and register pages.

Changes

Cohort / File(s) Summary
Register form (ARIA/loading attrs)
apps/web-next/src/app/(auth)/register/register-form.tsx
Added aria-label and aria-busy attributes tied to oauthLoadingProvider on Google and GitHub OAuth buttons; preserved existing disabled and click behavior. No public API/signature changes.
Playwright OAuth E2E tests
apps/web-next/tests/oauth.spec.ts
New test suite OAuth entrypoints @oauth`` with four tests (Google/GitHub × login/register). Tests run with empty storage state and are skipped unless RUN_OAUTH_E2E === '1'; they click provider buttons and assert navigation toward provider domains and, for login tests, that navigating back restores button visibility.

Sequence Diagram(s)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

size/XS

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: fixing the OAuth registration flow by replacing direct window.location navigation with a fetch-based approach that properly redirects to OAuth providers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/register-using-googleandgithub

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@seanhanca seanhanca changed the title fix/register-using-googleandgithub fix(auth): register Google/GitHub OAuth via fetch + redirect Apr 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 `@apps/web-next/src/app/`(auth)/register/register-form.tsx:
- Around line 70-82: The oauth loading flag isn't reset on success in
handleOAuthRegister; ensure setOauthLoading(false) is called on all control
paths by moving it into a finally-equivalent place (e.g., use a try { await
loginWithOAuth(provider); } finally { setOauthLoading(false); }) or by adding a
subsequent setOauthLoading(false) after the awaited loginWithOAuth call; update
the handleOAuthRegister callback (which references loginWithOAuth and setError)
so oauthLoading is cleared whether loginWithOAuth succeeds or throws.

In `@apps/web-next/tests/oauth.spec.ts`:
- Around line 12-16: The test gating treats any non-empty RUN_OAUTH_E2E value as
enabled; change the condition in the test.beforeEach to explicitly check for the
string '1' (e.g., process.env.RUN_OAUTH_E2E !== '1') so only RUN_OAUTH_E2E='1'
runs the OAuth E2E tests; update the test.skip invocation in oauth.spec.ts
accordingly to use this strict equality check.
🪄 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: e4b6aca1-6265-4c05-b3df-bb9a67beb1d4

📥 Commits

Reviewing files that changed from the base of the PR and between 4803404 and 2b9c1fb.

📒 Files selected for processing (2)
  • apps/web-next/src/app/(auth)/register/register-form.tsx
  • apps/web-next/tests/oauth.spec.ts

Comment thread apps/web-next/src/app/(auth)/register/register-form.tsx
Comment thread apps/web-next/tests/oauth.spec.ts Outdated
@seanhanca seanhanca enabled auto-merge (squash) April 8, 2026 01:48
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
apps/web-next/src/app/(auth)/register/register-form.tsx (1)

211-230: Minor UX note: both OAuth buttons show identical spinners during loading.

When oauthLoading is true, both buttons display only a spinner without the provider label (e.g., "Google" or "GitHub"). This makes it unclear which provider the user clicked. Since the redirect happens quickly, this may be acceptable, but consider tracking which provider is loading to show a spinner only on the clicked button.

Optional: Track which provider is loading
- const [oauthLoading, setOauthLoading] = useState(false);
+ const [oauthLoading, setOauthLoading] = useState<'google' | 'github' | null>(null);

  const handleOAuthRegister = useCallback(
    async (provider: 'google' | 'github') => {
      setError(null);
-     setOauthLoading(true);
+     setOauthLoading(provider);
      try {
        await loginWithOAuth(provider);
      } catch (err) {
        setError(err instanceof Error ? err.message : 'OAuth sign up failed');
      } finally {
-       setOauthLoading(false);
+       setOauthLoading(null);
      }
    },
    [loginWithOAuth],
  );

Then update button disabled/spinner logic:

- disabled={isLoading || oauthLoading}
+ disabled={isLoading || oauthLoading !== null}
...
- {oauthLoading ? (
+ {oauthLoading === 'google' ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/app/`(auth)/register/register-form.tsx around lines 211 -
230, The two OAuth buttons use the shared oauthLoading boolean so both show a
spinner; introduce a piece of state (e.g., oauthLoadingProvider or
loadingProvider) and set it inside handleOAuthRegister(provider) before starting
the flow, then change each button to show the spinner and disabled state only
when oauthLoading && loadingProvider === 'google' (or 'github' respectively)
while keeping the global oauthLoading behavior for overall flow; update
handleOAuthRegister and the button render logic (referencing handleOAuthRegister
and oauthLoading in the register form) to use this provider-specific loading
flag.
apps/web-next/tests/oauth.spec.ts (2)

41-57: Register tests lack goBack assertions present in login tests.

The login OAuth tests include goBack and button visibility assertions (lines 26-27, 37-38), but the register tests omit them. Consider adding for consistency, especially since the register form's loading state handling was the focus of this fix.

Optional: Add goBack assertions for register tests
   test('Register: Google button navigates toward Google OAuth', async ({ page }) => {
     await page.goto('/register');
     const btn = page.getByRole('button', { name: 'Google' });
     await expect(btn).toBeEnabled();
     const nav = page.waitForURL(/google\.com|accounts\.google\./i, { timeout: 20_000 });
     await Promise.all([nav, btn.click()]);
     expect(page.url()).toMatch(/google/i);
+    await page.goBack({ waitUntil: 'domcontentloaded' });
+    await expect(btn).toBeVisible({ timeout: 15_000 });
   });

   test('Register: GitHub button navigates toward GitHub OAuth', async ({ page }) => {
     await page.goto('/register');
     const btn = page.getByRole('button', { name: 'GitHub' });
     await expect(btn).toBeEnabled();
     const nav = page.waitForURL(/github\.com/i, { timeout: 20_000 });
     await Promise.all([nav, btn.click()]);
     expect(page.url()).toMatch(/github/i);
+    await page.goBack({ waitUntil: 'domcontentloaded' });
+    await expect(btn).toBeVisible({ timeout: 15_000 });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/tests/oauth.spec.ts` around lines 41 - 57, Add the same
post-navigation assertions used in the login tests to the register OAuth tests:
after initiating navigation in the tests "Register: Google button navigates
toward Google OAuth" and "Register: GitHub button navigates toward GitHub
OAuth", call page.goBack() and then assert the OAuth button is visible/enabled
again (mirror the existing goBack and visibility checks from the login tests) so
the register flow validates returning from OAuth and the button state is
correct.

12-17: Remove unused testInfo parameter.

The testInfo parameter is declared but never used. The empty destructuring ({ }, testInfo) can be simplified.

Suggested fix
-  test.beforeEach(({ }, testInfo) => {
+  test.beforeEach(() => {
     test.skip(
       process.env.RUN_OAUTH_E2E !== '1',
       'Set RUN_OAUTH_E2E=1 to run OAuth redirect checks (see tests/E2E-OAUTH-MANUAL.md)',
     );
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/tests/oauth.spec.ts` around lines 12 - 17, The before-each hook
declares an unused parameter list; update the test.beforeEach declaration to
remove the empty destructuring and unused testInfo parameter so the callback
takes no parameters (i.e., replace "({ }, testInfo) =>" with "() =>"). This
keeps the test.skip logic intact while eliminating the unused parameter warning
in the test.beforeEach hook.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web-next/src/app/`(auth)/register/register-form.tsx:
- Around line 211-230: The two OAuth buttons use the shared oauthLoading boolean
so both show a spinner; introduce a piece of state (e.g., oauthLoadingProvider
or loadingProvider) and set it inside handleOAuthRegister(provider) before
starting the flow, then change each button to show the spinner and disabled
state only when oauthLoading && loadingProvider === 'google' (or 'github'
respectively) while keeping the global oauthLoading behavior for overall flow;
update handleOAuthRegister and the button render logic (referencing
handleOAuthRegister and oauthLoading in the register form) to use this
provider-specific loading flag.

In `@apps/web-next/tests/oauth.spec.ts`:
- Around line 41-57: Add the same post-navigation assertions used in the login
tests to the register OAuth tests: after initiating navigation in the tests
"Register: Google button navigates toward Google OAuth" and "Register: GitHub
button navigates toward GitHub OAuth", call page.goBack() and then assert the
OAuth button is visible/enabled again (mirror the existing goBack and visibility
checks from the login tests) so the register flow validates returning from OAuth
and the button state is correct.
- Around line 12-17: The before-each hook declares an unused parameter list;
update the test.beforeEach declaration to remove the empty destructuring and
unused testInfo parameter so the callback takes no parameters (i.e., replace "({
}, testInfo) =>" with "() =>"). This keeps the test.skip logic intact while
eliminating the unused parameter warning in the test.beforeEach hook.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 90fc47b4-9aff-4f95-8277-1b933369fa33

📥 Commits

Reviewing files that changed from the base of the PR and between 2b9c1fb and 888be6c.

📒 Files selected for processing (2)
  • apps/web-next/src/app/(auth)/register/register-form.tsx
  • apps/web-next/tests/oauth.spec.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 8, 2026
Copy link
Copy Markdown
Contributor

@eliteprox eliteprox left a comment

Choose a reason for hiding this comment

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

Core fix is sound and matches the API’s JSON contract. Tighten test comments and the manual-doc reference (or add the doc), and optionally improve loading-state accessibility on the OAuth buttons.

Comment thread apps/web-next/tests/oauth.spec.ts Outdated
* OAuth provider redirects are flaky in CI (Google/GitHub bot detection).
* Run explicitly: `RUN_OAUTH_E2E=1 npx playwright test oauth.spec.ts`
* Default `npx playwright test` skips these unless RUN_OAUTH_E2E is exactly `1`.
* In CI, tests matching @oauth are also excluded via playwright.config.ts grepInvert.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Inaccurate comment in oauth.spec.ts

The file says tests matching oauth are excluded in CI via playwright.config.ts grepInvert. PR #240 only changes two files, and playwright.config.ts on main has no such grepInvert. Skipping still happens in beforeEach, but the grepInvert sentence should be removed or the config updated so the comment stays true.

Comment thread apps/web-next/tests/oauth.spec.ts Outdated
test.beforeEach(() => {
test.skip(
process.env.RUN_OAUTH_E2E !== '1',
'Set RUN_OAUTH_E2E=1 to run OAuth redirect checks (see tests/E2E-OAUTH-MANUAL.md)',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The skip message points at tests/E2E-OAUTH-MANUAL.md. repo search shows no such file (at least on current main).

Either add a short doc or change the message to something that exists (e.g. .env.local.example already mentions RUN_OAUTH_E2E).

Comment on lines +217 to +219
{oauthLoadingProvider === 'google' ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Accessibility while loading

When oauthLoadingProvider === 'google'|'github', the button content is only <Loader2 />, so the visible “Google” / “GitHub” label disappears. For consistency and a11y, consider aria-label (e.g. Continue with Google) and/or aria-busy="true" on the button while loading.

Comment on lines +70 to +83
const handleOAuthRegister = useCallback(
async (provider: 'google' | 'github') => {
setError(null);
setOauthLoadingProvider(provider);
try {
await loginWithOAuth(provider);
} catch (err) {
setError(err instanceof Error ? err.message : 'OAuth sign up failed');
} finally {
setOauthLoadingProvider(null);
}
},
[loginWithOAuth],
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

finally after successful redirect

On success, loginWithOAuth assigns window.location.href, then your finally clears oauthLoadingProvider. That usually runs before unload; if it ever races with unmount you could see a benign “setState on unmounted component” in dev.

Low priority; login doesn’t use finally for this path.

I would recommend avoiding JS to catch the call-back for the authorization_code. I would instead capture the code in the redirect_url and set a Location header to redirect the browser on successful authorization.

See below recommendation:

Redirect with Location (302/303)
If the user hits your OAuth start URL with a full navigation (, window.location, or a form GET), the server can respond with 302/303 + Set-Cookie (oauth state) + Location: https://accounts.google.com/.... The browser follows the redirect; users never see a JSON body.

Pros

No client-side “parse JSON then assign location.href” for the happy path.
Works without JavaScript (plain links).
Matches how many OAuth guides describe “redirect to authorize URL.”

Cons / constraints

SPAs that only fetch() this URL won’t get a useful automatic redirect to Google: fetch won’t replace the page with the provider the way a top-level navigation does (and following cross-origin redirects inside fetch is the wrong tool). So you still either use top-level navigation to your start URL or keep the current fetch + window.location.href pattern.
You must still set state in a cookie on that first response; doing it on the redirect response is standard and works.
Errors (provider not configured, etc.) are awkward as redirects — you usually 302 to an error page or keep a JSON endpoint for programmatic clients and a separate “start OAuth” route that only redirects.
Compared to finally / loading state
Using a top-level navigation to a redirecting start URL means the page is about to unload, so client finally / spinners matter less on success (the browser leaves your app). You’d still use try/catch only when you use fetch for initiation or for reporting errors without navigating.

Practical recommendation

Two surfaces are often clean: a GET route that 302s to the IdP for buttons/links, and optionally the existing JSON route for API-style callers.
For your app, either keep fetch + window.location.href (what loginWithOAuth does now) or change buttons to window.location.href = '/api/.../oauth/google' (no fetch) if that route returns 302 + Location. Both avoid showing JSON; the second drops the extra client step and makes finally mostly irrelevant on success.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 8, 2026
@github-actions github-actions Bot added size/XS Extra small PR (0-10 lines) and removed size/M Medium PR (51-200 lines) labels Apr 8, 2026
@seanhanca seanhanca merged commit fda5e65 into main Apr 8, 2026
9 of 11 checks passed
@seanhanca seanhanca deleted the fix/register-using-googleandgithub branch April 8, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope/shell Shell app changes size/XS Extra small PR (0-10 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants