@mathismeadows/roamer-device-auth 1.5.3 → 1.5.4
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 +55 -1
package/package.json
CHANGED
package/roamer-device-auth.mjs
CHANGED
|
@@ -906,6 +906,37 @@ async function forwardLine(transport, line, getTokens, setTokens, getFreshTokens
|
|
|
906
906
|
}
|
|
907
907
|
}
|
|
908
908
|
|
|
909
|
+
// AUTH-60: a reserved, locally-answered resource — never forwarded to the real upstream
|
|
910
|
+
// server. Lets the exact connection asking report its own resolved clientSlug/identityLabel
|
|
911
|
+
// back through the MCP session itself, with no external file or PID correlation required
|
|
912
|
+
// (see AUTH-60's notes for why a file-based "list live connections" design was rejected).
|
|
913
|
+
function isWhoamiResourceRead(line) {
|
|
914
|
+
try {
|
|
915
|
+
const message = JSON.parse(line);
|
|
916
|
+
return message?.method === "resources/read" && message?.params?.uri === "roamer://whoami";
|
|
917
|
+
} catch {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function respondWhoami(line, clientSlug, identityLabel) {
|
|
923
|
+
let id = null;
|
|
924
|
+
try {
|
|
925
|
+
id = JSON.parse(line)?.id ?? null;
|
|
926
|
+
} catch {
|
|
927
|
+
// Malformed input never had a usable id anyway — respond with null per JSON-RPC convention.
|
|
928
|
+
}
|
|
929
|
+
const payload = { clientSlug: clientSlug ?? null, identityLabel: identityLabel ?? null };
|
|
930
|
+
const response = {
|
|
931
|
+
jsonrpc: "2.0",
|
|
932
|
+
id,
|
|
933
|
+
result: {
|
|
934
|
+
contents: [{ uri: "roamer://whoami", mimeType: "application/json", text: JSON.stringify(payload, null, 2) }],
|
|
935
|
+
},
|
|
936
|
+
};
|
|
937
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
938
|
+
}
|
|
939
|
+
|
|
909
940
|
// AUTH-28: a hard sign-in failure (denied/expired device code) must tell the host exactly
|
|
910
941
|
// that, per request, rather than leaving requests unanswered for the host to time out on.
|
|
911
942
|
function respondWithSignInError(line, err) {
|
|
@@ -934,6 +965,13 @@ async function main() {
|
|
|
934
965
|
let ready = false;
|
|
935
966
|
const pendingLines = [];
|
|
936
967
|
|
|
968
|
+
// AUTH-60: identityLabel is resolved independently of `ready` (well before transport
|
|
969
|
+
// connects — see identityResolved below), so roamer://whoami can answer even while the
|
|
970
|
+
// real upstream connection is still coming up.
|
|
971
|
+
let identityLabel = null;
|
|
972
|
+
let identityResolved = false;
|
|
973
|
+
const pendingWhoamiLines = [];
|
|
974
|
+
|
|
937
975
|
// AUTH-51: which client is asking, resolved once from the first stdin line (the MCP
|
|
938
976
|
// `initialize` request, always message #1 by protocol) before any credential is touched.
|
|
939
977
|
let clientSlug = null;
|
|
@@ -970,6 +1008,12 @@ async function main() {
|
|
|
970
1008
|
clientSlug = clientSlugFromInitializeLine(line);
|
|
971
1009
|
resolveFirstLine();
|
|
972
1010
|
}
|
|
1011
|
+
// AUTH-60: answered locally from this process's own state, never forwarded upstream.
|
|
1012
|
+
if (isWhoamiResourceRead(line)) {
|
|
1013
|
+
if (identityResolved) respondWhoami(line, clientSlug, identityLabel);
|
|
1014
|
+
else pendingWhoamiLines.push(line);
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
973
1017
|
if (ready) {
|
|
974
1018
|
forwardLine(transport, line, getTokens, setTokens, freshTokensForForward);
|
|
975
1019
|
} else {
|
|
@@ -1013,7 +1057,6 @@ async function main() {
|
|
|
1013
1057
|
freshTokensForForward = (force) => getFreshTokens(clientSlug, force).then((result) => result.tokens);
|
|
1014
1058
|
|
|
1015
1059
|
let reusedSilently = false;
|
|
1016
|
-
let identityLabel = null;
|
|
1017
1060
|
try {
|
|
1018
1061
|
const result = await getFreshTokens(clientSlug);
|
|
1019
1062
|
tokens = result.tokens;
|
|
@@ -1028,6 +1071,9 @@ async function main() {
|
|
|
1028
1071
|
// explicit failure instead of a process it has to silently time out on.
|
|
1029
1072
|
log(`Sign-in failed: ${err.message}`);
|
|
1030
1073
|
for (const line of pendingLines) respondWithSignInError(line, err);
|
|
1074
|
+
// AUTH-60: a whoami read queued during a sign-in that then fails can never get a real
|
|
1075
|
+
// answer — same failure treatment as every other queued line, not left hanging.
|
|
1076
|
+
for (const line of pendingWhoamiLines) respondWithSignInError(line, err);
|
|
1031
1077
|
process.exit(1);
|
|
1032
1078
|
}
|
|
1033
1079
|
|
|
@@ -1037,6 +1083,11 @@ async function main() {
|
|
|
1037
1083
|
// instant a transient OS notification fired.
|
|
1038
1084
|
log(identityLabel ? `Authenticated as ${identityLabel} for client "${clientSlug}".` : `Authenticated for client "${clientSlug}" (no identity claim found on the token).`);
|
|
1039
1085
|
|
|
1086
|
+
// AUTH-60: identityLabel is now final — answer any roamer://whoami reads that arrived
|
|
1087
|
+
// before sign-in completed, the same way pendingLines get flushed once ready flips true.
|
|
1088
|
+
identityResolved = true;
|
|
1089
|
+
for (const line of pendingWhoamiLines) respondWhoami(line, clientSlug, identityLabel);
|
|
1090
|
+
|
|
1040
1091
|
// AUTH-51: the moment that used to be completely silent — a process starting up and
|
|
1041
1092
|
// immediately using a credential it never interactively obtained this run.
|
|
1042
1093
|
if (reusedSilently) await notifySessionReused(clientSlug, identityLabel);
|
|
@@ -1240,4 +1291,7 @@ export {
|
|
|
1240
1291
|
loginCommand,
|
|
1241
1292
|
logoutCommand,
|
|
1242
1293
|
statusCommand,
|
|
1294
|
+
// AUTH-60
|
|
1295
|
+
isWhoamiResourceRead,
|
|
1296
|
+
respondWhoami,
|
|
1243
1297
|
};
|