Skip to content

fix(web): preserve browser OAuth callbacks - #4953

Merged
iscekic merged 2 commits into
mainfrom
oauth-callback-intercept-f613
Aug 3, 2026
Merged

fix(web): preserve browser OAuth callbacks#4953
iscekic merged 2 commits into
mainfrom
oauth-callback-intercept-f613

Conversation

@iscekic

@iscekic iscekic commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

What

Route browser OAuth and signup completion redirects that land on app-claimed paths through a new /users/continue interstitial.

Why

iOS Universal Links and Android App Links can hand a browser document navigation to the installed app. A mobile-browser OAuth return ending at /profile (or another claimed callback path) therefore prevents web login from completing in that browser.

How

  • Add a table-derived browserLandingPath guard backed by UNIVERSAL_LINK_ROUTES.
  • Add /users/continue, which validates the target then uses client-side router.replace for the final hop.
  • Apply the guard only to the five authentication redirect sites and add table-driven, open-redirect, login, and signup coverage.

Verification

  • pnpm typecheck — passed
  • pnpm lint — passed
  • pnpm run format:changed — passed
  • cd apps/web && pnpm test — 694 suites passed; one pre-existing unrelated timeout failed in src/lib/kiloclaw/subscription-schema.test.ts during database cleanup
  • Focused redirect tests — 79 tests passed
  • Cumulative implementation review — no findings
  • iOS E2E verifier — passed. It confirmed default and /claw auth server redirects target the handoff, hostile to values stay local, kiloapp://profile preserves profile deep-link resolution, and a fresh single-org user redirects directly to its organization without a handoff.
  • Latest-head E2E verifier — passed. It captured the neutral Continuing loading state after the copy repair.

E2E limitation

Native OS-level universal-link interception (Safari handing a claimed URL to the installed app) cannot be reproduced in a simulator: the local dev build does not have an App-Store-signed production AASA association or a real cross-domain OAuth return. The E2E verifier did not claim to verify or refute that OS behavior. The unit tests are the authoritative proof that auth server redirects route claimed targets through the interstitial.

Universal-link scope

The shared universal-link table and both .well-known files are deliberately unchanged. Mobile files are unchanged as well, preserving existing deep-link and Smart App Banner behavior.

Deferred Stripe/payment returns

These audited same-class defects are deliberately out of scope for this authentication-only PR:

Site Lands on
apps/web/src/lib/stripe/index.ts:1418 /profile?auto_topup_setup=cancelled
apps/web/src/routers/kilo-pass-router.ts:1755 /profile (billing-portal return_url)
apps/web/src/routers/kilo-pass-router.ts:2375 /profile?kilo_pass_checkout=cancelled
apps/web/src/routers/kiloclaw-router.ts:5900 /claw?checkout=cancelled
apps/web/src/routers/kiloclaw-router.ts:6344 /claw (billing-portal return_url)
apps/web/src/lib/stripe/index.ts:1489,1613; kiloclaw-router.ts:5504 via cancelUrl variables — trace before fixing
apps/web/src/app/payments/auto-topup/success/route.ts:8 /profile?auto_topup_setup=success

Visual Changes

users-continue-loading.png

@iscekic iscekic self-assigned this Aug 1, 2026
Comment thread apps/web/src/lib/app-link-safe-redirect.ts
Comment thread apps/web/src/app/users/continue/page.tsx
Comment thread apps/web/src/app/users/continue/ContinueClient.tsx Outdated
Comment thread apps/web/src/app/auth/signin/route.ts
@kilo-code-bot

kilo-code-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change since 242483f7 is a single copy string in the /users/continue interstitial loader, which resolves the only previously raised finding on a changed line and introduces no new defect.

Incremental scope

Diff reviewed: 242483f7a0b56dbbaf962451063db2bf4f798ece..fea0786a3413e5777df0e42b1a8193f484b3ab74 — 1 file, 1 line.

  • apps/web/src/app/users/continue/ContinueClient.tsx:16BigLoader title changed from "Signing you in" to "Continuing". This is accurate for all five call sites, including the / and /auth/signin hops that already-authenticated users can take, so the prior copy finding is resolved. The component still holds no timer, listener, or subscription, so there is no memory-leak or cleanup concern; the effect only calls router.replace(to) and the <noscript> fallback remains a same-domain link navigation.

No other file changed in this increment, so no other file was re-reviewed for new findings.

Status of previously raised findings
  • ContinueClient.tsx copy — fixed in fea0786a3.
  • The three remaining findings (app-link-safe-redirect.ts:30 wildcard-breadth guard, users/continue/page.tsx:5 crawler/cache headers, auth/signin/route.ts:9 out-of-repo consumers) sit on files unchanged in this increment. Their existing inline threads carry the author's rationale for declining them; they are not re-posted here and no duplicate comments were created.
Files Reviewed (1 file)
  • apps/web/src/app/users/continue/ContinueClient.tsx
Previous Review Summary (commit 242483f)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 242483f)

Status: 4 Issues Found | Recommendation: Address before merge

Executive Summary

The new isAppLinkClaimed guard models * as a single path segment while the AASA/Android artifacts compiled from the same table use globs that cross /, so a subset of OS-claimed destinations still bypasses the /users/continue interstitial.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/app-link-safe-redirect.ts 30 Claim check under-approximates the OS claim set: webPathToAppPath requires exact segment counts, but AASA * and Android .* cross /, so paths such as /organizations/o1/code-reviews/r2/extra (accepted by isValidCallbackPath) skip the interstitial while both compiled artifacts claim them

SUGGESTION

File Line Issue
apps/web/src/app/users/continue/page.tsx 5 New mid-auth interstitial does not get the Cache-Control: no-store / X-Robots-Tag: noindex treatment that proxy.ts:23-26 gives the analogous /auth/verify-magic-link
apps/web/src/app/users/continue/ContinueClient.tsx 16 Hard-coded "Signing you in" copy is wrong for the / and /auth/signin hops, which now run for already-authenticated users following in-app links to /
apps/web/src/app/auth/signin/route.ts 9 Only changed call site with no in-repo callers and no test; its contract changes from a pure 307 to a JS-dependent HTML interstitial, so out-of-repo consumers that follow the chain programmatically should be verified
Verified clean
  • No open redirect. Every UNIVERSAL_LINK_ROUTES row starts with a literal segment, so resolveHandoffDestination can only return a same-origin path. //host, ///host, /\host, javascript:, http://app.kilo.ai/profile, %2f%2f, embedded-credential, and tab/newline-injected forms (which new URL would strip into a protocol-relative URL) all fail either the string guards or the table lookup.
  • No XSS. to reaches only router.replace(to) and a JSX href, both of which React escapes; to must start with /.
  • Encoding round-trip is lossless for ?, #, &, +, %, and spaces — encodeURIComponent escapes + to %2B, so the usual URLSearchParams +→space trap does not apply.
  • No redirect loop. / is unclaimed (identity through browserLandingPath), maybeInterceptWithSurvey is currently an identity function, getProfileRedirectPath returns only unclaimed-or-handled paths, and the /profile fallback is reached by client-side router.replace, not a server redirect.
  • Middleware. /users/continue is inside the proxy.ts matcher, is not rewritten, and the existing CSP does not block hydration.
  • Non-browser consumers of /users/after-sign-in. The extension live test probes with maxRedirects: 0 while unauthenticated, so it lands on the unclaimed /users/sign_in and is unaffected. Device auth, /api/integrations/<platform>/connect, /sign-in-to-editor, /account-verification, /account-blocked, and /organizations/<id> are all unclaimed and pass through, as the new tests assert.
  • Mobile. Login uses native OTP / native Google / device auth with server polling; no WebView renders app.kilo.ai for login, and the interstitial does not break the Custom Tab / ASWebAuthenticationSession hop.
  • No memory leaks in ContinueClient — the effect only calls router.replace and holds no subscription, timer, or listener.
  • The <noscript> fallback link was checked and does not reintroduce interception: it is a same-domain link navigation, which neither iOS Universal Links nor Android App Links hand off.
Notes and assumptions
  • Same-class server redirects to claimed paths outside the five authentication sites (Stripe/payment returns, plus redirect('/profile') in account-blocked/page.tsx, OrganizationByPageLayout.tsx, and the org app-builder/cloud chat pages) are unchanged lines and were treated as out of scope per the PR description.
  • Native OS-level universal-link interception was not exercised; the assessment relies on the compiled AASA/assetlinks.json/intent-filter patterns plus the unit tests.
  • The three Playwright suites that assert the post-login URL (tests/e2e/auth.setup.ts, tests/setup-smoke/profile.spec.ts, tests/e2e/get-started-dashboard-link.spec.ts) should still pass because waitForURL observes same-document navigations, but they now prove the client-side hop rather than the server redirect target.
Files Reviewed (11 files)
  • apps/web/src/lib/app-link-safe-redirect.ts - 1 issue
  • apps/web/src/app/users/continue/page.tsx - 1 issue
  • apps/web/src/app/users/continue/ContinueClient.tsx - 1 issue
  • apps/web/src/app/auth/signin/route.ts - 1 issue
  • apps/web/src/app/users/after-sign-in/route.tsx - 0 issues
  • apps/web/src/app/account-verification/page.tsx - 0 issues
  • apps/web/src/app/get-started/page.tsx - 0 issues
  • apps/web/src/app/page.tsx - 0 issues
  • apps/web/src/lib/app-link-safe-redirect.test.ts - 0 issues
  • apps/web/src/app/users/after-sign-in/route.test.ts - 0 issues
  • apps/web/src/tests/account-verification-redirect.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by claude-opus-5 · Input: 32 · Output: 5.6K · Cached: 646.1K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Aug 1, 2026
@iscekic
iscekic merged commit fe46303 into main Aug 3, 2026
16 checks passed
@iscekic
iscekic deleted the oauth-callback-intercept-f613 branch August 3, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants