@tokenoftrust/cli 4.7.4 → 4.7.5
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.5",
|
|
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",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `--json` prints `{ token, exp, ttlSeconds, tenantId, previewVersion }` instead.
|
|
14
14
|
* Treat the token like a password: anyone holding it sees the unpublished site.
|
|
15
15
|
*/
|
|
16
|
-
import { resolveViewerTransport } from "../viewer-session.mjs";
|
|
16
|
+
import { resolveViewerTransport, describeIdentity } from "../viewer-session.mjs";
|
|
17
17
|
import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
|
|
18
18
|
|
|
19
19
|
const MAX_TTL_SECONDS = 24 * 3600;
|
|
@@ -115,8 +115,10 @@ export async function run(argv, ctx, deps = {}) {
|
|
|
115
115
|
/* non-JSON error body — this endpoint answers 401/403 as plain text */
|
|
116
116
|
}
|
|
117
117
|
if (res.status === 401 || res.status === 403) {
|
|
118
|
+
const checked = describeIdentity(transport.identity);
|
|
118
119
|
err(
|
|
119
|
-
`✗ your account can't view ${tenant}'s preview.\n
|
|
120
|
+
`✗ your account can't view ${tenant}'s preview.\n` +
|
|
121
|
+
` checked: ${checked ?? "your `tot login` session"} at ${transport.base}\n\n` +
|
|
120
122
|
` → next: ${refusalHint(res.status, missingGrantsOf(data))}.`,
|
|
121
123
|
);
|
|
122
124
|
return 1;
|
package/src/viewer-session.mjs
CHANGED
|
@@ -133,7 +133,8 @@ export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
|
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* Resolve a viewer-session transport for `tenant`. Returns
|
|
136
|
-
* { ok:true, base, authHeaders }
|
|
136
|
+
* { ok:true, base, identity, authHeaders } — base = tenant host; cookie transport;
|
|
137
|
+
* identity = { account (masked), mcp origin }
|
|
137
138
|
* { ok:false, message, hint } — a clean, actionable refusal
|
|
138
139
|
* Never throws.
|
|
139
140
|
*
|
|
@@ -144,7 +145,8 @@ export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
|
|
|
144
145
|
* storefrontUrl?:string|null, resolveDev?:typeof resolveDeveloperSession,
|
|
145
146
|
* verbose?:boolean, log?:(s:string)=>void }} params
|
|
146
147
|
* @returns {Promise<
|
|
147
|
-
* { ok:true, base:string,
|
|
148
|
+
* { ok:true, base:string, identity:{ account:string|null, mcp:string|null },
|
|
149
|
+
* authHeaders:Record<string,string> } |
|
|
148
150
|
* { ok:false, message:string, hint:string }
|
|
149
151
|
* >}
|
|
150
152
|
*/
|
|
@@ -231,6 +233,7 @@ export async function resolveViewerTransport({
|
|
|
231
233
|
};
|
|
232
234
|
}
|
|
233
235
|
|
|
236
|
+
const minted = await readJson(res);
|
|
234
237
|
const cookie = parseSessionCookie(res.headers.get("set-cookie"));
|
|
235
238
|
if (!cookie) {
|
|
236
239
|
return {
|
|
@@ -243,6 +246,10 @@ export async function resolveViewerTransport({
|
|
|
243
246
|
return {
|
|
244
247
|
ok: true,
|
|
245
248
|
base,
|
|
249
|
+
identity: {
|
|
250
|
+
account: maskAccount(typeof minted?.email === "string" ? minted.email : null),
|
|
251
|
+
mcp: originOf(dev.mcpUrl),
|
|
252
|
+
},
|
|
246
253
|
authHeaders: {
|
|
247
254
|
cookie: `${SESSION_COOKIE}=${cookie}`,
|
|
248
255
|
// Matches the admin browser client; the server IGNORES this hint and re-reads
|
|
@@ -252,6 +259,28 @@ export async function resolveViewerTransport({
|
|
|
252
259
|
};
|
|
253
260
|
}
|
|
254
261
|
|
|
262
|
+
/** A recognizable stand-in for the signed-in account: first character and domain
|
|
263
|
+
* (`d…@example.com`), never the full address.
|
|
264
|
+
* @param {string|null} email @returns {string|null} */
|
|
265
|
+
export function maskAccount(email) {
|
|
266
|
+
const at = email ? email.lastIndexOf("@") : -1;
|
|
267
|
+
if (!email || at < 1) return null;
|
|
268
|
+
return `${email[0]}…${email.slice(at)}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Which identity and tier the store checked, for a refusal line:
|
|
273
|
+
* `d…@example.com via https://mcp.tokenoftrust.com`.
|
|
274
|
+
* @param {{ account?: string|null, mcp?: string|null }|null|undefined} identity
|
|
275
|
+
* @returns {string|null}
|
|
276
|
+
*/
|
|
277
|
+
export function describeIdentity(identity) {
|
|
278
|
+
const account = identity?.account || null;
|
|
279
|
+
const mcp = identity?.mcp || null;
|
|
280
|
+
if (account && mcp) return `${account} via ${mcp}`;
|
|
281
|
+
return account || (mcp ? `your ${mcp} sign-in` : null);
|
|
282
|
+
}
|
|
283
|
+
|
|
255
284
|
/** The refusal body, or null when the body is not JSON (describeCliSessionRefusal
|
|
256
285
|
* falls back cleanly). Any other failure — a body already consumed, a stream
|
|
257
286
|
* error — is a bug or a fault, not "no body", and propagates.
|