@tokenoftrust/cli 4.11.3 → 4.12.0
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/public-binaries.mjs +3 -3
- package/src/viewer-session.mjs +42 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
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/public-binaries.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tenant binaries never live in the tenant repo. The platform's content-addressed `/_a/<sha256>`
|
|
3
|
-
* pool owns them and the store's repo holds references only.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* pool owns them and the store's repo holds references only. Publication captures source images;
|
|
4
|
+
* go-live requires zero uncaptured images in every type and sampled live owned-image responses
|
|
5
|
+
* before the source store is shut down. This decides,
|
|
6
6
|
* from a listing of the store's `public/` tree, whether the repo is carrying binaries it must not.
|
|
7
7
|
*
|
|
8
8
|
* Findings, each naming its remedy:
|
package/src/viewer-session.mjs
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
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
|
|
@@ -179,45 +180,46 @@ export async function resolveViewerTransport({
|
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
const base = tenantBase(tenant, storefrontUrl);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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:
|
|
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 } }>}
|