From 20f92740baaacec713540afbf67aeebdbdf2bf63 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 12:47:12 +0000 Subject: [PATCH] Approve the AWS sign-in without showing a window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AWS SSO's device authorization is a multi-step approval — confirm the request, sign in, grant access — and none of the approval steps ask for anything the user has to supply. Frost now clicks them itself: the login page loads in a window that stays off screen, and a refresh covered by a live identity provider session finishes without a window, a browser tab or a Dock icon appearing at all. The window comes up the moment the page needs the user — a password or a one-time code to type, a security key to touch, a passkey to pick — and on anything off the expected path: a page Frost cannot read, a load that fails, a flow that stops making progress. In default-browser mode that same moment opens the browser instead, which is where that user's passkeys and saved passwords live, and the hidden probe is destroyed once the browser is actually up. Clicking is deliberately narrow, because the buttons next to the ones we want deny the request: only on the device-authorization hosts, only controls matched by AWS's own ids or by an exact label, never one whose label reads like a refusal, and only a few per document. Anything unrecognised stalls, and stalling shows the window. - src/approve-overlay.ts drives the page, alongside the existing overlay and under the same compile; src/auto-approve.ts watches it and owns the timers that guarantee the login is always eventually handed over. - src/page-script.ts holds the loading and sub-frame injection both injected scripts need — the overlay's dom-ready fallback for frames the document-start hook cannot reach, and the approval driver. - attachLoginIndicator() takes an onUserNeeded callback: a WebAuthn wait, and especially the modal account picker, must not happen behind a window nobody can see. - Behavior → Approve automatically turns it off, on by default. Closes #1 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018XK1mi3rSPLzxu7XUhyC6a --- AGENTS.md | 72 ++++++- README.md | 25 ++- docs/docs/login.html | 40 ++++ docs/docs/settings-behavior.html | 16 ++ docs/docs/settings.html | 6 + src/approve-overlay.ts | 318 +++++++++++++++++++++++++++++++ src/auto-approve.ts | 159 ++++++++++++++++ src/aws-sso.ts | 118 ++++++++++-- src/config.ts | 7 + src/dashboard.html | 18 +- src/login-indicator.ts | 99 +++------- src/page-script.ts | 79 ++++++++ src/window.ts | 5 +- tsconfig.json | 7 +- tsconfig.overlay.json | 9 +- 15 files changed, 867 insertions(+), 111 deletions(-) create mode 100644 src/approve-overlay.ts create mode 100644 src/auto-approve.ts create mode 100644 src/page-script.ts diff --git a/AGENTS.md b/AGENTS.md index 15e3996..1124b4c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,11 +7,16 @@ or the release pipeline. Frost is an Electron tray app (an AWS SSO credentials refresher) for macOS, Windows and Linux. There is no bundler; almost every `src/*.ts` runs in the -main process. Two exceptions: - -- `src/login-overlay.ts` is browser code injected into the login page. It has - its own compile (`tsconfig.overlay.json`); the main `tsconfig.json` excludes - it. +main process. Three exceptions: + +- `src/login-overlay.ts` is browser code injected into the login page (the + WebAuthn toast). It has its own compile (`tsconfig.overlay.json`); the main + `tsconfig.json` excludes it. +- `src/approve-overlay.ts` is the same kind of thing under the same compile — + the driver that clicks the AWS approval steps. Neither may import anything: + an import makes the output a module, which is not injectable as a classic + script, and is why each signal constant is duplicated in its main-process + counterpart rather than shared. - `src/dashboard.html` is copied verbatim by `build:html` and is neither type-checked nor linted. @@ -40,6 +45,13 @@ missing one fails at runtime only. failure *after* the token was renewed keeps the expiry schedule rather than an error retry, which would reopen the login page for an unrelated failure. No electron imports. +- **`src/page-script.ts`** — `loadPageScript()` / `injectIntoEveryFrame()`, + used by both injected scripts. Injection follows sub-frames because + `executeJavaScript` on a `WebContents` reaches the top frame only, and a + sign-in page routinely puts the interesting part in a cross-origin `