Skip to content

fix(auth): extension CoinPay sign-in never picked up the session - #49

Merged
ralyodio merged 2 commits into
mainfrom
worktree-conpay-ext-signin
Jul 31, 2026
Merged

fix(auth): extension CoinPay sign-in never picked up the session#49
ralyodio merged 2 commits into
mainfrom
worktree-conpay-ext-signin

Conversation

@ralyodio

@ralyodio ralyodio commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

The bug

Clicking Sign in with CoinPay in the extension's options page (chrome-extension://…/options.html) signs the website in and leaves the extension signed out — exactly the reported "tronbrowser.dev is authed but not the web extension".

Root cause

coinpay-auth.js asked the backend to redirect back to chrome.identity.getRedirectURL()https://<ext-id>.chromiumapp.org/.

That target is off-origin, so safeRedirect() rejected it — correctly, since the session token rides in the redirect fragment. With the redirect dropped, /api/auth/coinpay/callback fell through to c.redirect(${APP_URL}/?signedin=1). The OAuth dance completed, tronbrowser.dev got its tb_session cookie, and the extension got nothing.

Ungoogled Chromium independently rewrites chromiumapp.org to a non-resolving .qjz9zk host, so launchWebAuthFlow could not have worked either. bittorrented.js already worked around this via ext-callback; CoinPay never got the same treatment.

The fix

  • New GET /api/auth/ext-login — validates an on-origin callback, and when the user is already signed in on the website hands a session straight back rather than running CoinPay a second time. Otherwise forwards to the CoinPay dance, preserving the callback.
  • coinpaySignIn() opens that URL in a normal tab and waits for the token, matching the bittorrented flow. The storage listener is registered before the tab is created so a fast redirect can't race it.
  • ext-callback.js now serves both flows, keyed on ?src=tb (tbAuthToken vs btrToken); background.js handles the generalized ext-token message.
  • Dropped the now-unused identity permission from the manifest.

Decision logic lives in services/api/src/ext-login.ts so it's unit-testable — index.ts calls serve() at import and can't be imported from a test.

Security

Redirect targets remain same-origin only. A drive-by top-level navigation to /api/auth/ext-login mints a token that lands in the fragment of a URL on our origin, which the navigating site cannot read. Covered by tests, including an explicit case rejecting the chromiumapp.org URL that caused this.

Verification

  • vitest run in services/api57 passed (9 files), including 5 new ext-login tests
  • tsc --noEmit — clean
  • eslint on all changed files — 0 errors (8 pre-existing any warnings on untouched lines)
  • Extension JS syntax-checked; manifest re-parsed

Not verified: a live browser round-trip through CoinPay — that needs real OAuth credentials.

Note for the release

This is unreleased, so tronbrowser update correctly reports "already up to date (v3.8.3)" — there is nothing newer to pull yet. This needs to ship in v3.8.4 before it reaches anyone.

Separately worth a look: COINPAY_REDIRECT_URI defaults to https://tronbrowser**.com**/api/auth/coinpay/callback (services/api/src/index.ts:20) while APP_URL defaults to tronbrowser.dev. Left alone here since production presumably sets the env var, but if it doesn't, that's a second broken hop.

🤖 Generated with Claude Code


Second commit: unbreaking CI (unrelated to the auth fix)

build was already failing on main before this branch existed — CI went red at 5bad141 (#48, Moshpit resolve); #47 before it was green.

noUncheckedIndexedAccess types the destructured label/tld in parseRegistryName as string | undefined, because TS can't narrow an array to a 2-tuple from a parts.length !== 2 check. Four TS2345/TS2322 errors failed apps/desktop and took the whole pnpm -r build down with it.

Fixed with an explicit guard. Behavior is unchanged: length 2 already guarantees both values exist, and an empty label would fail LABEL.test() on the very next line.

It's scope bleed, but the PR could not go green without it — and neither could anything else landing on main.

Full workspace verification after both commits:

Clicking "Sign in with CoinPay" in the extension's options page signed the
WEBSITE in and left the extension signed out.

coinpay-auth.js asked the backend to redirect back to
chrome.identity.getRedirectURL() -> https://<ext-id>.chromiumapp.org/.
That target is off-origin, so safeRedirect() rejected it (correctly — the
session token rides in the redirect fragment) and /api/auth/coinpay/callback
fell through to `c.redirect(`${APP_URL}/?signedin=1`)`. The OAuth dance
completed, tronbrowser.dev got its tb_session cookie, and the extension got
nothing. (Ungoogled Chromium also rewrites chromiumapp.org to a
non-resolving .qjz9zk host, so launchWebAuthFlow could not have worked
either.) bittorrented.js already worked around this with ext-callback;
CoinPay never got the same treatment.

- Add GET /api/auth/ext-login: validates an on-origin callback, and when the
  user is ALREADY signed in on the website hands a session straight back
  instead of running CoinPay a second time. Otherwise forwards to the
  CoinPay dance preserving the callback.
- coinpaySignIn() opens that URL in a tab and waits for the token, matching
  the bittorrented flow. Listener is registered before the tab is created so
  a fast redirect can't race it.
- ext-callback.js now serves both flows, keyed on `?src=tb`, and background
  handles the generalized 'ext-token' message.
- Drop the now-unused "identity" permission from the manifest.

Redirect targets stay same-origin only, so a drive-by navigation to
/api/auth/ext-login lands the token in a fragment the navigating site
cannot read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio marked this pull request as ready for review July 31, 2026 03:24
…ured parts

CI has been red on main since #48: noUncheckedIndexedAccess types the
destructured `label`/`tld` as `string | undefined`, because TS can't narrow an
array to a 2-tuple from a `parts.length !== 2` check. Four TS2345/TS2322
errors failed `apps/desktop` and took the whole `pnpm -r build` with it.

Guard explicitly. Behavior is unchanged: length 2 already guarantees both
values, and an empty label would fail LABEL.test() on the next line anyway.

Unrelated to this branch's auth work, but it blocks the PR from going green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit f7d5316 into main Jul 31, 2026
7 checks passed
@ralyodio ralyodio mentioned this pull request Jul 31, 2026
ralyodio added a commit that referenced this pull request Jul 31, 2026
Ships the extension CoinPay sign-in fix (#49): the extension asked the
backend to redirect to an off-origin chromiumapp.org callback, safeRedirect()
rejected it, and only the website ended up signed in.

Bumps every workspace package, the Expo app, and the AI sidebar extension
manifest to 3.8.4 via scripts/set-version.mjs.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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