@tokenoftrust/cli 4.11.2 → 4.11.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/bin/tot.mjs CHANGED
@@ -371,7 +371,6 @@ async function main() {
371
371
  const mcpUrl = mcpIndex < 0 ? undefined : rest[mcpIndex + 1];
372
372
  if (mcpIndex >= 0 && (!mcpUrl || mcpUrl.startsWith("--"))) throw new Error("--mcp requires an HTTPS URL.");
373
373
  // One invocation context drives auth, telemetry and every runner child.
374
- delete process.env.TOT_SELECTED_MCP_URL;
375
374
  process.env.TOT_SELECTED_MCP_URL = selectedAuthority(process.env, { mcpUrl, cwd: ctx.cwd });
376
375
  process.env.MCP_BASE_URL = process.env.TOT_SELECTED_MCP_URL;
377
376
  const startedAt = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "4.11.2",
3
+ "version": "4.11.4",
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",
@@ -2,8 +2,9 @@
2
2
  * Viewer-session transport for the ship surface — the NO-operator-secret path.
3
3
  *
4
4
  * An invited developer holds a `tot login` MCP session but no operator secret and no
5
- * storefront cookie, so the old ship-surface transport dead-ended on them. This mints
6
- * a storefront VIEWER session from the developer's OWN MCP token (POST
5
+ * storefront cookie. This discovers
6
+ * the storefront's trusted issuer without credentials, then mints a VIEWER session
7
+ * from the developer's matching MCP token (POST
7
8
  * /api/dev/cli-session on the tenant's own host, which resolves WHO the opaque token
8
9
  * is via the MCP and hands back a `tot_session` cookie), then hands the caller a
9
10
  * cookie-based transport pointed at the tenant host. Authorization is the server's
@@ -111,7 +112,7 @@ export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
111
112
  };
112
113
  }
113
114
 
114
- if (error === "unresolved_token" && trustedMcp) {
115
+ if (error === "unresolved_token" && trustedMcp && TOKEN_OF_TRUST_MCP_ORIGINS.has(trustedMcp)) {
115
116
  const signedInOrigin = originOf(signedInMcpUrl);
116
117
  if (signedInOrigin && signedInOrigin !== trustedMcp) {
117
118
  // `TOT_SELECTED_MCP_URL=… tot login` adds a session for the storefront's MCP
@@ -179,45 +180,46 @@ export async function resolveViewerTransport({
179
180
  }
180
181
 
181
182
  const base = tenantBase(tenant, storefrontUrl);
182
- const first = await mintSession(base, dev.token, fetchImpl, vlog);
183
- if (!first.ok) return first.refusal;
184
- /** @type {any} */
185
- let res = first.response;
186
-
187
- // The hosted storefront trusts ONE MCP issuer, independent of the tier this
188
- // checkout is bound to (a QA-registry store is still served by the shared
189
- // storefront). When it names a different issuer, the session to present is the
190
- // one for THAT issuer — the checkout's own binding is for the registry, not for
191
- // this transport. Use it when the developer holds one; otherwise fall through
192
- // to the refusal, which says how to get one without rebinding the checkout.
193
- if (res.status === 401) {
194
- const body = await readJson(res);
195
- const trustedMcp = typeof body?.trustedMcp === "string" ? body.trustedMcp : null;
196
- const signedInOrigin = originOf(dev.mcpUrl);
197
- if (
198
- body?.error === "unresolved_token" &&
199
- mayPresentSessionFor(base, trustedMcp) &&
200
- signedInOrigin && signedInOrigin !== trustedMcp
201
- ) {
202
- let trustedDev = null;
203
- try {
204
- trustedDev = await resolveDev(null, { ...env, TOT_SELECTED_MCP_URL: trustedMcp }, { fetchImpl });
205
- } catch {
206
- /* no usable session for the storefront's issuer — report the mismatch below */
207
- }
208
- if (trustedDev?.token && originOf(trustedDev.mcpUrl) === trustedMcp) {
209
- vlog(`cli-session: retrying with your ${trustedMcp} session`);
210
- const retry = await mintSession(base, trustedDev.token, fetchImpl, vlog);
211
- if (!retry.ok) return retry.refusal;
212
- res = retry.response;
213
- dev = trustedDev;
214
- } else {
215
- res = replayJson(res, body);
216
- }
217
- } else {
218
- res = replayJson(res, body);
183
+ // Discover the issuer without credentials before presenting any bearer.
184
+ let discovery;
185
+ try {
186
+ vlog(`GET ${base}/api/dev/cli-session`);
187
+ discovery = await fetchImpl(`${base}/api/dev/cli-session`, {
188
+ method: "GET", headers: { accept: "application/json" }, redirect: "manual",
189
+ });
190
+ } catch {
191
+ return { ok: false, message: `couldn't reach ${base} to discover its identity issuer.`,
192
+ hint: "check the --tenant domain / your network, then re-run." };
193
+ }
194
+ vlog(`cli-session discovery → HTTP ${discovery.status}`);
195
+ if (!discovery.ok) {
196
+ if (discovery.status === 502 || discovery.status === 503) {
197
+ return { ok: false, ...describeCliSessionRefusal(discovery.status, await readJson(discovery), dev.mcpUrl) };
219
198
  }
199
+ return { ok: false, message: `couldn't discover the store's identity issuer (HTTP ${discovery.status}).`,
200
+ hint: "retry shortly; if it persists, report it with `tot feedback`." };
220
201
  }
202
+ const advertised = await readJson(discovery);
203
+ const trustedMcp = typeof advertised?.trustedMcp === "string" ? advertised.trustedMcp : null;
204
+ if (!trustedMcp || !TOKEN_OF_TRUST_MCP_ORIGINS.has(trustedMcp)) {
205
+ return { ok: false, ...describeCliSessionRefusal(503, { code: "invalid_mcp_issuer" }, dev.mcpUrl) };
206
+ }
207
+ if (originOf(dev.mcpUrl) !== trustedMcp) {
208
+ /** @type {{ ok: false, message: string, hint: string }} */
209
+ const refusal = { ok: false, ...describeCliSessionRefusal(401, { error: "unresolved_token", trustedMcp }, dev.mcpUrl) };
210
+ if (!mayPresentSessionFor(base, trustedMcp)) return refusal;
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;
219
+ }
220
+ const mintedSession = await mintSession(base, dev.token, fetchImpl, vlog);
221
+ if (!mintedSession.ok) return mintedSession.refusal;
222
+ const res = mintedSession.response;
221
223
 
222
224
  if (res.status === 401 || res.status === 502 || res.status === 503) {
223
225
  const body = await readJson(res);
@@ -247,7 +249,7 @@ export async function resolveViewerTransport({
247
249
  ok: true,
248
250
  base,
249
251
  identity: {
250
- account: maskAccount(typeof minted?.email === "string" ? minted.email : null),
252
+ account: typeof minted?.account === "string" && /^[^@\s]…@[^@\s]+$/.test(minted.account) ? minted.account : null,
251
253
  mcp: originOf(dev.mcpUrl),
252
254
  },
253
255
  authHeaders: {
@@ -294,11 +296,6 @@ async function readJson(res) {
294
296
  }
295
297
  }
296
298
 
297
- /** A response whose body was already read, re-readable once more. */
298
- function replayJson(res, body) {
299
- return { ok: res.ok, status: res.status, headers: res.headers, json: async () => body };
300
- }
301
-
302
299
  /**
303
300
  * POST the bearer to `<base>/api/dev/cli-session`.
304
301
  * @returns {Promise<{ ok: true, response: any } | { ok: false, refusal: { ok: false, message: string, hint: string } }>}