@tokenoftrust/cli 4.12.0 → 4.12.1
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/src/viewer-session.mjs +18 -58
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "4.12.
|
|
3
|
+
"version": "4.12.1",
|
|
4
4
|
"description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Token of Trust",
|
package/src/viewer-session.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* An invited developer holds a `tot login` MCP session but no operator secret and no
|
|
5
5
|
* storefront cookie. This discovers
|
|
6
|
-
* the storefront's trusted
|
|
6
|
+
* the storefront's trusted issuers without credentials, then mints a VIEWER session
|
|
7
7
|
* from the developer's matching MCP token (POST
|
|
8
8
|
* /api/dev/cli-session on the tenant's own host, which resolves WHO the opaque token
|
|
9
9
|
* is via the MCP and hands back a `tot_session` cookie), then hands the caller a
|
|
@@ -55,42 +55,20 @@ function originOf(url) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
59
|
-
* storefront called at one of these EXACT origins may steer the CLI to present a
|
|
60
|
-
* session for a different MCP issuer: anything reached through `--url` /
|
|
61
|
-
* `TOT_STOREFRONT_URL` is user input, and a hostile one could otherwise name the
|
|
62
|
-
* prod issuer and collect that session. */
|
|
63
|
-
export const FIRST_PARTY_STOREFRONT_ORIGINS = Object.freeze(new Set([
|
|
64
|
-
DEFAULT_STOREFRONT_ORIGIN,
|
|
65
|
-
"https://storefront.tokenoftrust.workers.dev",
|
|
66
|
-
]));
|
|
67
|
-
|
|
68
|
-
/** The Token of Trust MCP issuers a first-party storefront may name. Exact origins. */
|
|
58
|
+
/** Exact identity issuers supported by hosted storefront sessions. */
|
|
69
59
|
export const TOKEN_OF_TRUST_MCP_ORIGINS = Object.freeze(new Set([
|
|
70
60
|
"https://mcp.tokenoftrust.com",
|
|
71
61
|
"https://mcp.qa.tokenoftrust.com",
|
|
72
62
|
]));
|
|
73
63
|
|
|
74
|
-
/** True only when BOTH sides are declared first-party, by exact origin. */
|
|
75
|
-
function mayPresentSessionFor(storefrontBase, issuer) {
|
|
76
|
-
const storefront = originOf(storefrontBase);
|
|
77
|
-
return Boolean(storefront && issuer &&
|
|
78
|
-
FIRST_PARTY_STOREFRONT_ORIGINS.has(storefront) && TOKEN_OF_TRUST_MCP_ORIGINS.has(issuer));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
64
|
/**
|
|
82
65
|
* Turn a non-2xx `/api/dev/cli-session` response into one clean, actionable
|
|
83
66
|
* `{ message, hint }` — the ONE place this CLI interprets that endpoint's refusal
|
|
84
67
|
* shape, so ship/preview-token (both routed through `resolveViewerTransport`)
|
|
85
68
|
* never duplicate or drift from each other.
|
|
86
69
|
*
|
|
87
|
-
* - 401
|
|
88
|
-
*
|
|
89
|
-
* — the genuine tier mismatch (signed in to qa, store trusts prod, or vice
|
|
90
|
-
* versa): say exactly which two origins disagree and the exact fix.
|
|
91
|
-
* - Any other 401 — a real "you're not recognized here" — keeps the plain
|
|
92
|
-
* login hint, but still surfaces the server's error code so a report isn't
|
|
93
|
-
* just "it didn't work".
|
|
70
|
+
* - 401 keeps recovery on the CLI's selected issuer. Discovery or refusal payloads
|
|
71
|
+
* never select another credential or steer sign-in to another authority.
|
|
94
72
|
* - 502/503 — the SEAM is broken (unconfigured, unreachable, malformed), which
|
|
95
73
|
* is never a login problem: say so, with the server's code, and suggest a
|
|
96
74
|
* retry instead of `tot login`.
|
|
@@ -103,7 +81,6 @@ function mayPresentSessionFor(storefrontBase, issuer) {
|
|
|
103
81
|
export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
|
|
104
82
|
const error = body && typeof body.error === "string" ? body.error : null;
|
|
105
83
|
const code = body && typeof body.code === "string" ? body.code : null;
|
|
106
|
-
const trustedMcp = body && typeof body.trustedMcp === "string" ? body.trustedMcp : null;
|
|
107
84
|
|
|
108
85
|
if (status === 502 || status === 503) {
|
|
109
86
|
return {
|
|
@@ -112,23 +89,13 @@ export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
|
|
|
112
89
|
};
|
|
113
90
|
}
|
|
114
91
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// `TOT_SELECTED_MCP_URL=… tot login` adds a session for the storefront's MCP
|
|
119
|
-
// WITHOUT rebinding this checkout (`tot login --mcp` inside a checkout would
|
|
120
|
-
// move every command there to the other tier). Once that session exists,
|
|
121
|
-
// `resolveViewerTransport` uses it for the hosted storefront on its own.
|
|
122
|
-
return {
|
|
123
|
-
message: `you're signed in to ${signedInOrigin}, but the hosted storefront trusts ${trustedMcp}.`,
|
|
124
|
-
hint: `sign in to it once with \`TOT_SELECTED_MCP_URL=${trustedMcp} tot login\` (this checkout stays on ${signedInOrigin}), then re-run.`,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
92
|
+
const selectedMcp = originOf(signedInMcpUrl);
|
|
93
|
+
const login = selectedMcp && TOKEN_OF_TRUST_MCP_ORIGINS.has(selectedMcp)
|
|
94
|
+
? `TOT_SELECTED_MCP_URL=${selectedMcp} tot login` : "tot login";
|
|
128
95
|
|
|
129
96
|
return {
|
|
130
97
|
message: `your \`tot\` session wasn't recognized for this store${error && error !== "unresolved_token" ? ` (${error})` : ""}.`,
|
|
131
|
-
hint:
|
|
98
|
+
hint: `run \`${login}\`, then re-run.`,
|
|
132
99
|
};
|
|
133
100
|
}
|
|
134
101
|
|
|
@@ -200,24 +167,17 @@ export async function resolveViewerTransport({
|
|
|
200
167
|
hint: "retry shortly; if it persists, report it with `tot feedback`." };
|
|
201
168
|
}
|
|
202
169
|
const advertised = await readJson(discovery);
|
|
203
|
-
const
|
|
204
|
-
if (!
|
|
170
|
+
const trustedMcps = advertised?.trustedMcps;
|
|
171
|
+
if (!Array.isArray(trustedMcps) || !trustedMcps.length ||
|
|
172
|
+
!trustedMcps.every((issuer) => typeof issuer === "string" && TOKEN_OF_TRUST_MCP_ORIGINS.has(issuer))) {
|
|
205
173
|
return { ok: false, ...describeCliSessionRefusal(503, { code: "invalid_mcp_issuer" }, dev.mcpUrl) };
|
|
206
174
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
let trustedDev = null;
|
|
212
|
-
try {
|
|
213
|
-
trustedDev = await resolveDev(null, { ...env, TOT_SELECTED_MCP_URL: trustedMcp }, { fetchImpl });
|
|
214
|
-
} catch {
|
|
215
|
-
/* No usable credential for the declared issuer: nothing is presented. */
|
|
216
|
-
}
|
|
217
|
-
if (!trustedDev?.token || originOf(trustedDev.mcpUrl) !== trustedMcp) return refusal;
|
|
218
|
-
dev = trustedDev;
|
|
175
|
+
const selectedMcp = originOf(dev.mcpUrl);
|
|
176
|
+
if (!selectedMcp || !trustedMcps.includes(selectedMcp)) {
|
|
177
|
+
return { ok: false, message: "your selected MCP issuer isn't trusted by this store.",
|
|
178
|
+
hint: "report it with `tot feedback`; this command keeps your selected issuer." };
|
|
219
179
|
}
|
|
220
|
-
const mintedSession = await mintSession(base, dev.token, fetchImpl, vlog);
|
|
180
|
+
const mintedSession = await mintSession(base, dev.token, selectedMcp, fetchImpl, vlog);
|
|
221
181
|
if (!mintedSession.ok) return mintedSession.refusal;
|
|
222
182
|
const res = mintedSession.response;
|
|
223
183
|
|
|
@@ -300,7 +260,7 @@ async function readJson(res) {
|
|
|
300
260
|
* POST the bearer to `<base>/api/dev/cli-session`.
|
|
301
261
|
* @returns {Promise<{ ok: true, response: any } | { ok: false, refusal: { ok: false, message: string, hint: string } }>}
|
|
302
262
|
*/
|
|
303
|
-
async function mintSession(base, token, fetchImpl, vlog) {
|
|
263
|
+
async function mintSession(base, token, mcpOrigin, fetchImpl, vlog) {
|
|
304
264
|
let res;
|
|
305
265
|
try {
|
|
306
266
|
// `content-type: application/json` is REQUIRED, not decoration. Astro's origin
|
|
@@ -320,7 +280,7 @@ async function mintSession(base, token, fetchImpl, vlog) {
|
|
|
320
280
|
authorization: `Bearer ${token}`,
|
|
321
281
|
"content-type": "application/json",
|
|
322
282
|
},
|
|
323
|
-
body:
|
|
283
|
+
body: JSON.stringify({ mcpOrigin }),
|
|
324
284
|
// Never follow a redirect with the bearer attached: a 3xx is a refusal here
|
|
325
285
|
// (non-2xx), so the token only ever reaches the origin it was addressed to.
|
|
326
286
|
redirect: "manual",
|