@mathismeadows/roamer-device-auth 1.5.4 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/roamer-device-auth.mjs +20 -5
package/package.json
CHANGED
package/roamer-device-auth.mjs
CHANGED
|
@@ -355,6 +355,13 @@ async function readCachedTokens(clientSlug, identityKey = null) {
|
|
|
355
355
|
// A cache from an incompatible prior format must never be trusted just because it
|
|
356
356
|
// happens to have the right field names — see CACHE_VERSION's comment.
|
|
357
357
|
if (tokens?.cacheVersion !== CACHE_VERSION) return null;
|
|
358
|
+
// AUTH-38/AUTH-device-code-cache-issuer-blind: same protection readLoopbackTokens already
|
|
359
|
+
// has — a token cached against a prior authorization server (e.g. Cloudflare Access
|
|
360
|
+
// Managed OAuth, before AUTH-38) must never be trusted just because cacheVersion matches.
|
|
361
|
+
// No live discovery needed here (unlike the loopback cache): RoamerMcp declares itself as
|
|
362
|
+
// its own AS in its .well-known/oauth-protected-resource document, so the fixed
|
|
363
|
+
// ROAMER_MCP_ORIGIN constant is exactly as authoritative as a freshly-discovered value.
|
|
364
|
+
if (tokens?.issuerUrl !== ROAMER_MCP_ORIGIN) return null;
|
|
358
365
|
return tokens;
|
|
359
366
|
} catch {
|
|
360
367
|
return null;
|
|
@@ -368,7 +375,12 @@ async function writeCachedTokens(clientSlug, tokens, identityKey = null) {
|
|
|
368
375
|
const path = cacheFilePath("roamer_tokens", clientSlug, identityKey);
|
|
369
376
|
if (!path) return; // AUTH-51: no usable client identity — never persisted.
|
|
370
377
|
await mkdir(CACHE_DIR, { recursive: true, mode: 0o700 });
|
|
371
|
-
const stamped = {
|
|
378
|
+
const stamped = {
|
|
379
|
+
...tokens,
|
|
380
|
+
cacheVersion: CACHE_VERSION,
|
|
381
|
+
issuerUrl: ROAMER_MCP_ORIGIN,
|
|
382
|
+
label: identityLabelFromTokens(tokens),
|
|
383
|
+
};
|
|
372
384
|
await writeFile(path, JSON.stringify(stamped, null, 2), { mode: 0o600 });
|
|
373
385
|
}
|
|
374
386
|
|
|
@@ -639,10 +651,12 @@ async function doGetValidTokens(clientSlug, forceRefresh, forceFreshLogin = fals
|
|
|
639
651
|
}
|
|
640
652
|
|
|
641
653
|
log(`Go to ${device.verification_uri} and enter code: ${device.user_code}`);
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
|
|
654
|
+
// AUTH-61: launch the browser first, then show the fallback dialog — both are best-effort
|
|
655
|
+
// OS-level UI activations, and requesting them in the same instant can leave Safari
|
|
656
|
+
// intermittently stuck showing "Application Not Responding" on launch (macOS-only; see
|
|
657
|
+
// thread AUTH-safari-device-flow-hang). The dialog stays fire-and-forget: polling below
|
|
658
|
+
// must not wait on it being dismissed. Skip both on a resume — the human already saw them
|
|
659
|
+
// (or is mid-flow in the browser) from the process that started this same code.
|
|
646
660
|
if (!resuming) {
|
|
647
661
|
try {
|
|
648
662
|
const { default: open } = await import("open");
|
|
@@ -651,6 +665,7 @@ async function doGetValidTokens(clientSlug, forceRefresh, forceFreshLogin = fals
|
|
|
651
665
|
// Best-effort only — the dialog and QR code below already carry the URL and code.
|
|
652
666
|
}
|
|
653
667
|
}
|
|
668
|
+
if (!resuming) showDeviceCodeDialog(device.verification_uri, device.user_code);
|
|
654
669
|
// Terminal-visible for any client where a human sees this process's stderr/log output
|
|
655
670
|
// (Claude Code CLI, VS Code's integrated terminal) — QR codes are a real usability win
|
|
656
671
|
// for CLI auth over typing an 8-character code by hand.
|