mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
feat(stockbit): one browser sign-in in idx-core for the CLI and MCP
`stockbit::signin` is now the single sign-in flow. `idx stockbit login` waits on it in the foreground; the `stockbit_login` MCP tool runs it on a thread and `stockbit_status` reports `sign_in.state`, so an agent knows when to stop polling. Auto uses a browser unless the shell is over SSH or has no display, and falls back to the local page only when the browser cannot start. The browser way opens Stockbit's sign-in page in a profile only idx uses and reads the session cookie over DevTools. What a real sign-in showed: - The cookie is host-only on stockbit.com, so deleting by the assumed .stockbit.com removed nothing and left live tokens in the profile. It is now deleted by its own domain and path, with every other tab closed first, and read back to confirm. - A signed-out page writes the cookie without tokens; it no longer counts. - A profile belongs to one browser, so it records the executable that made it. - On macOS the config directory depends on XDG_CONFIG_HOME, so a shell and an app started from the Dock disagreed; both default locations are now checked. - A thread does not outlive main, so idx-mcp closes a waiting browser when stdin closes or on SIGTERM or Ctrl-C. Progress goes to stderr, which keeps `-o json` to one document on stdout. Google refused its own sign-in in this window on 2026-10-31, most likely over the debugging flag; password sign-in works, and the docs and roadmap say so. Claude-Session: https://claude.ai/code/session_01RqPPVaoFxLToGAXXBEoCtH
This commit is contained in:
parent
cb532b670b
commit
ca4d3d09c2
21 changed files with 2799 additions and 212 deletions
|
|
@ -193,16 +193,71 @@ WIB as the fallback.
|
|||
### Stockbit session (opt-in login)
|
||||
- `idx stockbit login` stores the user's session from a pasted
|
||||
`credentialStorage` cookie (or a bare refresh token) in
|
||||
`<config dir>/stockbit-session.json`, written atomically with mode 0600;
|
||||
`stockbit-session.json`, written atomically with mode 0600, in
|
||||
`auth::session_dir()`: the config directory, or on macOS the other default
|
||||
location when the session or profile is there, because a shell with
|
||||
`XDG_CONFIG_HOME=~/.config` and an app started from the Dock resolve to
|
||||
different places (a custom `XDG_CONFIG_HOME` stands alone, which keeps
|
||||
isolated runs off the user's session);
|
||||
`IDX_STOCKBIT_REFRESH_TOKEN` / `IDX_STOCKBIT_ACCESS_TOKEN` fill fields the
|
||||
file lacks. In a terminal it first serves a page on 127.0.0.1
|
||||
(`api/stockbit/loopback.rs`): a bookmarklet that reads the cookie in the
|
||||
file lacks. Stockbit publishes no OAuth client a third party can register,
|
||||
and its sign-in is a first-party flow guarded by reCAPTCHA, so idx cannot
|
||||
ask for a token itself; every way in ends with the cookie's two tokens.
|
||||
- What the cookie is (read from Stockbit's web bundle, 2026-10-29): the web
|
||||
app's persisted credential store, written by script, host-only on
|
||||
`stockbit.com`, for 30 days, as percent-encoded
|
||||
`{"state":{"access","refresh","user"},"version":0}`. Every sign-in
|
||||
(password, Google or Apple, a new device's one-time code, recovery) and the
|
||||
app's own `POST /login/refresh` go through one `setCredentials`, so the
|
||||
cookie always holds the newest pair. A signed-out page writes the store
|
||||
without tokens, so a cookie counts only when `auth::session_from_paste`
|
||||
finds tokens in it. The app refreshes by itself when it loads with a stale
|
||||
access token, which is why idx never leaves a session in a browser it does
|
||||
not own.
|
||||
- `api/stockbit/signin.rs` is the one sign-in flow; `idx stockbit login` waits
|
||||
on it in the foreground, and idx-mcp's `stockbit_login` runs it on a thread
|
||||
(`start_background`) that `stockbit_status` reports on (`sign_in`: waiting,
|
||||
signed_in or failed, with the error's code). A thread does not outlive
|
||||
`main`, so idx-mcp calls `signin::shutdown` when stdin closes or on SIGTERM
|
||||
or Ctrl-C, which closes a waiting browser (SIGKILL cannot). `Method::Auto` uses the browser
|
||||
unless the shell is over SSH or has no display, and falls back to the page
|
||||
only when the browser cannot start (none found, or it exits at launch); a
|
||||
sign-in that started and then timed out or was closed is an error. Both
|
||||
ways end in `signin::accept`, which is `stockbit::login`: store, check with
|
||||
one request, restore the previous session if refused. A stored session that
|
||||
still works means nothing to sign in to: both frontends verify it with one
|
||||
request first, which is the common case, because every refresh extends the
|
||||
session.
|
||||
- The browser way (`api/stockbit/browser.rs`): Chrome, Chromium, Edge, Brave
|
||||
or Helium on `stockbit.com/login`, with a profile of its own at
|
||||
`<session dir>/stockbit-browser` (mode 0700) and
|
||||
`--remote-debugging-port=0`. A profile belongs to one browser (its cookies
|
||||
use that browser's key, and an older Chromium may refuse a newer profile),
|
||||
so `<profile>/idx-browser` records the executable and the next run reuses
|
||||
it; `IDX_STOCKBIT_BROWSER` overrides that (and is the only browser the mock
|
||||
provider will start). The
|
||||
browser writes the port and WebSocket path to `<profile>/DevToolsActivePort`;
|
||||
idx connects and polls `Storage.getCookies`. On a session `accept` takes, it
|
||||
opens a blank tab, closes every other one (no Stockbit script may write the
|
||||
cookie back), deletes `credentialStorage` by the domain and path the browser
|
||||
reported, reads the cookies again to confirm it is gone, and asks the
|
||||
browser to close. Stockbit's device cookies stay, so a later
|
||||
sign-in needs no new one-time code. The window waits 15 minutes; closing it
|
||||
or the browser exiting ends the wait at once. The password, one-time codes
|
||||
and reCAPTCHA stay in the browser. Google refused its own sign-in in this
|
||||
window on 2026-10-31 ("This browser or app may not be secure"), most likely
|
||||
because of the debugging flag, so password sign-in is the supported way
|
||||
here and Google users go through the page.
|
||||
- The page way (`api/stockbit/loopback.rs`), for `--no-browser`, SSH, no
|
||||
display or no browser: a page on 127.0.0.1 (7878 when free, so an `ssh -L`
|
||||
forward stays the same) with a bookmarklet that reads the cookie in the
|
||||
user's own browser and opens the page with it in the URL fragment, or a
|
||||
clipboard button; a per-run secret guards the path, the page is `no-store`
|
||||
with a strict CSP, and the server stops on success or after five minutes.
|
||||
Piped stdin keeps the paste path. `api/stockbit/auth.rs` reads expiries
|
||||
from the JWTs (`exp`,
|
||||
unverified) and refreshes an access token within an hour of expiry with
|
||||
Piped stdin keeps the paste path. Sign-in progress goes to stderr (as JSON
|
||||
lines with `-o json`), so stdout holds only the result.
|
||||
- `api/stockbit/auth.rs` reads expiries from the JWTs (`exp`, unverified)
|
||||
and refreshes an access token within an hour of expiry with
|
||||
`POST /login/refresh` (bearer = refresh token), which rotates both tokens.
|
||||
Because a refresh token is spent once, refreshes run under an exclusive
|
||||
lock on `stockbit-session.lock` and re-read the file first, so concurrent
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue