Skip to content

fix(pwa): return the caller's own host in the CLI device-flow URLs - #105

Merged
ralyodio merged 1 commit into
masterfrom
fix/cli-device-uri-request-origin
Jul 30, 2026
Merged

fix(pwa): return the caller's own host in the CLI device-flow URLs#105
ralyodio merged 1 commit into
masterfrom
fix/cli-device-uri-request-origin

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

logicsrc login --device printed this, even on the real domain:

🔑 Authorize the LogicSRC CLI from any browser:
   1. open https://logicsrc-credentials-production.up.railway.app/cli/device
   2. enter the code: 83HN-5QSM

Root cause

The URL never came from the CLI. The CLI just prints what the server sends:

// packages/cli/src/teams.ts:141
console.error(`   1. open ${start.verification_uri}`);

And the server built it from a single fixed value:

// apps/pwa/src/routes/cli.mjs:133
verification_uri: `${config.origin}/cli/device`
// apps/pwa/src/config.mjs:26
const origin = process.env.PUBLIC_ORIGIN || `http://localhost:${PORT}`;

$PUBLIC_ORIGIN names exactly one hostname, so the response is wrong for every
other host the app answers on. (This is also why #104 — which changed the CLI's
DEFAULT_API_URL — had no effect on the printed URL: wrong layer.)

Fix

Derive the origin from the request, so whatever host the CLI called is the host it
gets sent back to. trust proxy is already set in server.mjs, so
X-Forwarded-Proto/X-Forwarded-Host are honoured behind Railway's TLS terminator.

Scoped to the two device-flow URLs on purpose. expectedOrigin in passkey.mjs
stays pinned to config.origin
— validating a WebAuthn signature against a
caller-supplied host would defeat the check.

Tests

New apps/pwa/test/origin.test.mjs, 5 cases, all passing — including that the
Railway hostname still self-describes correctly when that is the host, so this
follows the request rather than swapping one hardcode for another.

Still required separately

This fixes which URL is printed. The link only loads once that host routes to
this service — currently logicsrc.com/cli/device returns 404.

🤖 Generated with Claude Code

`logicsrc login --device` told users to open
https://logicsrc-credentials-production.up.railway.app/cli/device even when they
had reached the app on the real domain. /cli/device/code built verification_uri
from `config.origin`, which is a single fixed value read from $PUBLIC_ORIGIN, so
the response was wrong for every hostname except the one that variable happened
to name.

Derive the origin from the request instead: whatever host the CLI called is the
host it gets sent back to. Express honours X-Forwarded-Proto/Host here because
server.mjs sets `trust proxy` behind Railway's TLS terminator.

Deliberately scoped to the two device-flow URLs. The WebAuthn expectedOrigin in
passkey.mjs stays pinned to config.origin — validating a signature against a
host the caller supplied would defeat the check.

Note this fixes which URL is *printed*; the host still has to route to this
service for the link to load.

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 merged commit 60c0cbf into master Jul 30, 2026
5 checks passed
@ralyodio
ralyodio deleted the fix/cli-device-uri-request-origin branch July 30, 2026 16:19
ralyodio added a commit that referenced this pull request Jul 30, 2026
…I hint (#108)

Two bugs on the dashboard, both fixed by handing appBar/CLI_HINT the request.

Sign-out was broken for everyone. csrfGuard rejects any POST whose _csrf does
not match the mc_csrf cookie, and /auth/logout is a POST that is not on the
exempt list, but the sign-out form carried no hidden field -- every click
answered 403 "bad csrf token". appBar now takes the request rather than the
user, because it needs the token as well as the identity. The field is written
out instead of reusing csrfInput(): html.mjs is the view layer and imports
nothing, and pulling in session.mjs would drag the database driver with it.

The "Connect the CLI" snippet still printed $PUBLIC_ORIGIN, so users on
app.logicsrc.com were told to point LOGICSRC_API at the generated Railway
hostname. #105 added requestOrigin() for exactly this and fixed the device-flow
URLs; the dashboard hint was missed. It now follows the request too, which is
not a hardcode swap -- the same deployment answering on its Railway hostname
still self-describes correctly.

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