@tinycloud/cli 1.1.0-beta.7 → 1.1.0-beta.9
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/dist/index.js +101 -49
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +395 -178
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +5 -5
- package/skills/tc-cli/AUTH.md +1 -1
- package/skills/tc-cli/REFERENCE.md +18 -1
package/dist/legacy-entry.js
CHANGED
|
@@ -163,7 +163,7 @@ import ora from "ora";
|
|
|
163
163
|
function outputJson(data) {
|
|
164
164
|
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
165
165
|
}
|
|
166
|
-
function outputError(code3, message, hint) {
|
|
166
|
+
function outputError(code3, message, hint, meta) {
|
|
167
167
|
if (isInteractive()) {
|
|
168
168
|
process.stderr.write(
|
|
169
169
|
`${theme.error("\u2717")} ${theme.label(code3)}: ${message}
|
|
@@ -180,6 +180,7 @@ function outputError(code3, message, hint) {
|
|
|
180
180
|
error: { code: code3, message }
|
|
181
181
|
};
|
|
182
182
|
if (hint) payload.error.hint = hint;
|
|
183
|
+
if (meta) payload.error.meta = meta;
|
|
183
184
|
process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
|
|
184
185
|
}
|
|
185
186
|
}
|
|
@@ -260,24 +261,43 @@ var init_formatter = __esm({
|
|
|
260
261
|
// src/output/errors.ts
|
|
261
262
|
import { readFileSync, readdirSync } from "fs";
|
|
262
263
|
import { join as join2 } from "path";
|
|
263
|
-
import { ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
264
|
+
import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
265
|
+
import { authorizationVerdictOf, parseCapabilityResource, SERVICE_LONG_TO_SHORT, validatedCapabilityOf } from "@tinycloud/sdk-core";
|
|
264
266
|
function setActiveProfileName(name) {
|
|
265
267
|
activeProfileName = name;
|
|
266
268
|
}
|
|
269
|
+
function cliErrorFromService(error, message = error.message) {
|
|
270
|
+
if (error instanceof CLIError) return error;
|
|
271
|
+
const meta = { ...error.meta };
|
|
272
|
+
delete meta.hint;
|
|
273
|
+
const status = error.status ?? error.statusCode;
|
|
274
|
+
if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) meta.status = status;
|
|
275
|
+
const verdict = authorizationVerdictOf({ ...error, meta });
|
|
276
|
+
if (verdict === void 0 && message.includes("Missing private key parameter in JWK")) {
|
|
277
|
+
return missingPrivateKeyError();
|
|
278
|
+
}
|
|
279
|
+
const missingCapability = validatedCapabilityOf({ ...error, meta }) !== void 0;
|
|
280
|
+
const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability;
|
|
281
|
+
return new CLIError(
|
|
282
|
+
denied ? "PERMISSION_DENIED" : verdict === "unauthenticated" ? "AUTH_REQUIRED" : error.code,
|
|
283
|
+
message,
|
|
284
|
+
denied ? ExitCode.PERMISSION_DENIED : verdict === "unauthenticated" ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
|
|
285
|
+
meta
|
|
286
|
+
);
|
|
287
|
+
}
|
|
267
288
|
function wrapError(error) {
|
|
289
|
+
if (error instanceof CLIError) return error;
|
|
268
290
|
const message = error instanceof Error ? error.message : String(error);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return new CLIError(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
);
|
|
291
|
+
const verdict = authorizationVerdictOf(error);
|
|
292
|
+
if (verdict === "unauthenticated") {
|
|
293
|
+
return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
|
|
294
|
+
}
|
|
295
|
+
if (verdict === "forbidden") {
|
|
296
|
+
return new CLIError("PERMISSION_DENIED", message, ExitCode.PERMISSION_DENIED);
|
|
297
|
+
}
|
|
298
|
+
if (verdict === void 0 && message.includes("Missing private key parameter in JWK")) {
|
|
299
|
+
return missingPrivateKeyError();
|
|
279
300
|
}
|
|
280
|
-
if (error instanceof CLIError) return error;
|
|
281
301
|
if (error instanceof ProfileLockTimeoutError) {
|
|
282
302
|
return new CLIError(
|
|
283
303
|
"PROFILE_LOCK_TIMEOUT",
|
|
@@ -286,13 +306,16 @@ function wrapError(error) {
|
|
|
286
306
|
{ hint: "Wait for the other command to finish and retry. A crashed process's lock is reclaimed automatically after 30 s." }
|
|
287
307
|
);
|
|
288
308
|
}
|
|
289
|
-
if (
|
|
309
|
+
if (error instanceof ProfileDeletedError) {
|
|
310
|
+
return new CLIError("PROFILE_NOT_FOUND", message);
|
|
311
|
+
}
|
|
312
|
+
if (verdict === void 0 && (message.includes("Not signed in") || message.includes("AUTH_EXPIRED") || message.includes("Session expired"))) {
|
|
290
313
|
return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
|
|
291
314
|
}
|
|
292
315
|
if (message.includes("NOT_FOUND") || message.includes("KV_NOT_FOUND")) {
|
|
293
316
|
return new CLIError("NOT_FOUND", message, ExitCode.NOT_FOUND);
|
|
294
317
|
}
|
|
295
|
-
if (message.includes("PERMISSION_DENIED")) {
|
|
318
|
+
if (verdict === void 0 && message.includes("PERMISSION_DENIED")) {
|
|
296
319
|
return new CLIError("PERMISSION_DENIED", message, ExitCode.PERMISSION_DENIED);
|
|
297
320
|
}
|
|
298
321
|
if (message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("fetch failed")) {
|
|
@@ -300,39 +323,51 @@ function wrapError(error) {
|
|
|
300
323
|
}
|
|
301
324
|
return new CLIError("ERROR", message, ExitCode.ERROR);
|
|
302
325
|
}
|
|
326
|
+
function missingPrivateKeyError() {
|
|
327
|
+
const profileName = activeProfileName ?? process.env.TC_PROFILE ?? DEFAULT_PROFILE;
|
|
328
|
+
return new CLIError(
|
|
329
|
+
"AUTH_REQUIRED",
|
|
330
|
+
`Profile "${profileName}" cannot restore its session because its private key material is missing.`,
|
|
331
|
+
ExitCode.AUTH_REQUIRED,
|
|
332
|
+
{ hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey` }
|
|
333
|
+
);
|
|
334
|
+
}
|
|
303
335
|
function handleError(error) {
|
|
304
336
|
const cliError = wrapError(error);
|
|
305
337
|
const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
|
|
306
338
|
const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
|
|
307
|
-
|
|
339
|
+
const authMeta = cliError.code === "AUTH_REQUIRED" || cliError.code === "PERMISSION_DENIED" ? cliError.metadata : void 0;
|
|
340
|
+
const meta = {};
|
|
341
|
+
if (cliError.status !== void 0 && authMeta !== void 0) meta.status = cliError.status;
|
|
342
|
+
const capability = validatedCapabilityOf({ meta: authMeta });
|
|
343
|
+
if (capability) {
|
|
344
|
+
meta.resource = capability.resource;
|
|
345
|
+
meta.requiredAction = capability.requiredAction;
|
|
346
|
+
}
|
|
347
|
+
outputError(cliError.code, cliError.message, hint, Object.keys(meta).length ? meta : void 0);
|
|
308
348
|
process.exit(cliError.exitCode);
|
|
309
349
|
}
|
|
310
350
|
function buildAuthHint(error) {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return void 0;
|
|
315
|
-
}
|
|
316
|
-
const spec = capSpecFromAuthMeta(resource, requiredAction);
|
|
351
|
+
const capability = validatedCapabilityOf({ meta: error.metadata });
|
|
352
|
+
if (!capability) return void 0;
|
|
353
|
+
const spec = capSpecFromAuthMeta(capability.resource, capability.requiredAction);
|
|
317
354
|
if (!spec) return void 0;
|
|
318
355
|
return [
|
|
319
356
|
"The active session is missing a TinyCloud capability.",
|
|
320
|
-
`Request it with: tc auth request --cap
|
|
357
|
+
`Request it with: tc auth request --cap '${spec.replaceAll("'", "'\\''")}'`,
|
|
321
358
|
"Then retry the original command."
|
|
322
359
|
].join("\n");
|
|
323
360
|
}
|
|
324
361
|
function capSpecFromAuthMeta(resource, action) {
|
|
325
|
-
const slash =
|
|
326
|
-
if (slash
|
|
327
|
-
const
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
const spaceName = spaceUri.startsWith("tinycloud:") ? spaceUri.slice(spaceUri.lastIndexOf(":") + 1) : spaceUri;
|
|
335
|
-
return `tinycloud.${serviceShort}:${spaceName}:${path}:${actionName}`;
|
|
362
|
+
const slash = action.indexOf("/");
|
|
363
|
+
if (slash < 0) return void 0;
|
|
364
|
+
const longService = action.slice(0, slash);
|
|
365
|
+
const serviceShort = SERVICE_LONG_TO_SHORT[longService];
|
|
366
|
+
if (!serviceShort) return void 0;
|
|
367
|
+
const parsed = parseCapabilityResource(resource, serviceShort);
|
|
368
|
+
if (!parsed) return void 0;
|
|
369
|
+
const spaceName = parsed.space.slice(parsed.space.lastIndexOf(":") + 1);
|
|
370
|
+
return `${longService}:${spaceName}:${parsed.path}:${action.slice(slash + 1)}`;
|
|
336
371
|
}
|
|
337
372
|
function buildNetworkHint() {
|
|
338
373
|
const readHost = (name) => {
|
|
@@ -386,7 +421,9 @@ var init_errors = __esm({
|
|
|
386
421
|
this.exitCode = exitCode;
|
|
387
422
|
this.metadata = metadata;
|
|
388
423
|
this.name = "CLIError";
|
|
424
|
+
if (typeof metadata?.status === "number" && Number.isInteger(metadata.status) && metadata.status >= 400 && metadata.status <= 599) this.status = metadata.status;
|
|
389
425
|
}
|
|
426
|
+
status;
|
|
390
427
|
};
|
|
391
428
|
}
|
|
392
429
|
});
|
|
@@ -644,6 +681,8 @@ import { join as join3 } from "path";
|
|
|
644
681
|
import {
|
|
645
682
|
profilePath,
|
|
646
683
|
readSession,
|
|
684
|
+
recordProfileDeletion,
|
|
685
|
+
refuseWriteToDeletedProfile,
|
|
647
686
|
removeSession,
|
|
648
687
|
withProfileLock,
|
|
649
688
|
writeSession
|
|
@@ -758,10 +797,12 @@ var init_profiles = __esm({
|
|
|
758
797
|
* removed while holding the profile lock, so another writer's critical
|
|
759
798
|
* section never sees them vanish midway: session and key first, settings
|
|
760
799
|
* last, so a crash midway never leaves a session or key without its
|
|
761
|
-
* profile.
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
800
|
+
* profile. The deletion is then recorded, so a store write that waited for
|
|
801
|
+
* the lock meanwhile refuses rather than recreating a profile with only a
|
|
802
|
+
* session in it. `.lock` itself is left to the lock's release; the
|
|
803
|
+
* then-empty directory is removed afterwards unless another writer took
|
|
804
|
+
* the lock (or wrote) meanwhile. A profile directory that is a symlink is
|
|
805
|
+
* unlinked, its target left alone.
|
|
765
806
|
* Throws if the name is not one path segment or names the default profile.
|
|
766
807
|
*/
|
|
767
808
|
static async deleteProfile(name) {
|
|
@@ -782,11 +823,12 @@ var init_profiles = __esm({
|
|
|
782
823
|
`Cannot delete the default profile "${name}". Change the default first with \`tc profile default <other>\`.`
|
|
783
824
|
);
|
|
784
825
|
}
|
|
785
|
-
const
|
|
786
|
-
if (error.code === "ENOENT") return
|
|
826
|
+
const kind = await lstat(profileDir).then((stats) => stats.isSymbolicLink() ? "link" : "present", (error) => {
|
|
827
|
+
if (error.code === "ENOENT") return "missing";
|
|
787
828
|
throw error;
|
|
788
829
|
});
|
|
789
|
-
if (
|
|
830
|
+
if (kind === "missing") return;
|
|
831
|
+
if (kind === "link") {
|
|
790
832
|
await rm2(profileDir, { force: true });
|
|
791
833
|
return;
|
|
792
834
|
}
|
|
@@ -796,6 +838,7 @@ var init_profiles = __esm({
|
|
|
796
838
|
if (entry !== ".lock" && entry !== "profile.json") await rm2(join3(profileDir, entry), { recursive: true, force: true });
|
|
797
839
|
}
|
|
798
840
|
await rm2(join3(profileDir, "profile.json"), { force: true });
|
|
841
|
+
await recordProfileDeletion(name);
|
|
799
842
|
});
|
|
800
843
|
await rmdir(profileDir).catch((error) => {
|
|
801
844
|
if (error.code !== "ENOENT" && error.code !== "ENOTEMPTY" && error.code !== "EEXIST") throw error;
|
|
@@ -808,9 +851,14 @@ var init_profiles = __esm({
|
|
|
808
851
|
static async getKey(name) {
|
|
809
852
|
return readJson(join3(PROFILES_DIR, name, "key.json"));
|
|
810
853
|
}
|
|
811
|
-
/**
|
|
854
|
+
/**
|
|
855
|
+
* Saves a JWK key under the profile lock (0600, in an owner-only profile
|
|
856
|
+
* directory). Refused (PROFILE_NOT_FOUND) if the profile was deleted while
|
|
857
|
+
* this waited for the lock, rather than leaving a key-only profile.
|
|
858
|
+
*/
|
|
812
859
|
static async setKey(name, jwk) {
|
|
813
860
|
await _ProfileManager.withLock(name, async () => {
|
|
861
|
+
await refuseWriteToDeletedProfile(name);
|
|
814
862
|
await writeJson(join3(await _ProfileManager.ensureProfileDir(name), "key.json"), jwk);
|
|
815
863
|
});
|
|
816
864
|
}
|
|
@@ -1779,6 +1827,7 @@ import {
|
|
|
1779
1827
|
profileStoreMetadataPath,
|
|
1780
1828
|
readAdditionalDelegations,
|
|
1781
1829
|
readAuthRequests,
|
|
1830
|
+
refuseWriteToDeletedProfile as refuseWriteToDeletedProfile2,
|
|
1782
1831
|
updateProfileStore,
|
|
1783
1832
|
withProfileLock as withProfileLock2,
|
|
1784
1833
|
writeJsonAtomic
|
|
@@ -1865,6 +1914,7 @@ var CAPABILITIES2 = deriveServiceConstants("capabilities");
|
|
|
1865
1914
|
var HOOKS = deriveServiceConstants("hooks");
|
|
1866
1915
|
var ENCRYPTION = deriveServiceConstants("encryption");
|
|
1867
1916
|
var SPACE = deriveServiceConstants("space");
|
|
1917
|
+
var CAPABILITY_REGISTRY = CAPABILITIES;
|
|
1868
1918
|
var DEFAULT_MANIFEST_SPACE = "applications";
|
|
1869
1919
|
var ACCOUNT_REGISTRY_SPACE = "account";
|
|
1870
1920
|
var ACCOUNT_REGISTRY_PATH = "applications/";
|
|
@@ -6805,6 +6855,75 @@ var ServiceSessionSchema = external_exports.object({
|
|
|
6805
6855
|
/** The session key JWK (required for invoke) */
|
|
6806
6856
|
jwk: external_exports.object({}).passthrough()
|
|
6807
6857
|
});
|
|
6858
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
6859
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
6860
|
+
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
6861
|
+
var NetworkIdError = class extends Error {
|
|
6862
|
+
constructor(message) {
|
|
6863
|
+
super(message);
|
|
6864
|
+
this.name = "NetworkIdError";
|
|
6865
|
+
}
|
|
6866
|
+
};
|
|
6867
|
+
function parseNetworkId(networkId) {
|
|
6868
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
6869
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
6870
|
+
}
|
|
6871
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
6872
|
+
throw new NetworkIdError(
|
|
6873
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
6874
|
+
);
|
|
6875
|
+
}
|
|
6876
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
6877
|
+
const lastColon = body.lastIndexOf(":");
|
|
6878
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
6879
|
+
throw new NetworkIdError(
|
|
6880
|
+
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
6881
|
+
);
|
|
6882
|
+
}
|
|
6883
|
+
const ownerDid = body.slice(0, lastColon);
|
|
6884
|
+
const name = body.slice(lastColon + 1);
|
|
6885
|
+
if (!ownerDid.startsWith("did:")) {
|
|
6886
|
+
throw new NetworkIdError(
|
|
6887
|
+
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
6888
|
+
);
|
|
6889
|
+
}
|
|
6890
|
+
const didParts = ownerDid.split(":");
|
|
6891
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
6892
|
+
throw new NetworkIdError(
|
|
6893
|
+
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
6894
|
+
);
|
|
6895
|
+
}
|
|
6896
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
6897
|
+
throw new NetworkIdError(
|
|
6898
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6899
|
+
);
|
|
6900
|
+
}
|
|
6901
|
+
return { networkId, ownerDid, name };
|
|
6902
|
+
}
|
|
6903
|
+
function parsePkhOwnerDid(ownerDid) {
|
|
6904
|
+
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
6905
|
+
if (!match) return null;
|
|
6906
|
+
return {
|
|
6907
|
+
chainId: match[1],
|
|
6908
|
+
address: match[2].toLowerCase()
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6911
|
+
function ownerDidMatches(a, b) {
|
|
6912
|
+
const aPkh = parsePkhOwnerDid(a);
|
|
6913
|
+
const bPkh = parsePkhOwnerDid(b);
|
|
6914
|
+
if (aPkh && bPkh) {
|
|
6915
|
+
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
6916
|
+
}
|
|
6917
|
+
return a === b;
|
|
6918
|
+
}
|
|
6919
|
+
function networkDiscoveryKey(name) {
|
|
6920
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
6921
|
+
throw new NetworkIdError(
|
|
6922
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6923
|
+
);
|
|
6924
|
+
}
|
|
6925
|
+
return `.well-known/encryption/network/${name}`;
|
|
6926
|
+
}
|
|
6808
6927
|
function parsePermissionHint(value) {
|
|
6809
6928
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
6810
6929
|
const candidate = value;
|
|
@@ -6872,6 +6991,101 @@ function parseAuthError(responseText) {
|
|
|
6872
6991
|
function authUnauthorizedError(service, message, meta) {
|
|
6873
6992
|
return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
|
|
6874
6993
|
}
|
|
6994
|
+
var grantableActions = new Set(
|
|
6995
|
+
CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
|
|
6996
|
+
);
|
|
6997
|
+
var capabilityServices = new Set(
|
|
6998
|
+
CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
|
|
6999
|
+
);
|
|
7000
|
+
function parseCapabilityResource2(resource, service) {
|
|
7001
|
+
if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
|
|
7002
|
+
const segments = resource.split("/");
|
|
7003
|
+
if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
|
|
7004
|
+
const serviceIndex = segments.findIndex(
|
|
7005
|
+
(segment, index) => index > 0 && capabilityServices.has(segment)
|
|
7006
|
+
);
|
|
7007
|
+
if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
|
|
7008
|
+
if (segments.slice(1, serviceIndex).some(
|
|
7009
|
+
(segment) => segment.length === 0 || segment === "." || segment === ".."
|
|
7010
|
+
)) return void 0;
|
|
7011
|
+
const pathSegments = segments.slice(serviceIndex + 1);
|
|
7012
|
+
if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
|
|
7013
|
+
if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
|
|
7014
|
+
return {
|
|
7015
|
+
space: segments.slice(0, serviceIndex).join("/"),
|
|
7016
|
+
path: pathSegments.join("/")
|
|
7017
|
+
};
|
|
7018
|
+
}
|
|
7019
|
+
function safeCapabilityResource(value, service, action) {
|
|
7020
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
|
|
7021
|
+
if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
|
|
7022
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
|
|
7023
|
+
try {
|
|
7024
|
+
parseNetworkId(value);
|
|
7025
|
+
return true;
|
|
7026
|
+
} catch {
|
|
7027
|
+
return false;
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
const parsed = parseCapabilityResource2(value, service);
|
|
7031
|
+
return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
|
|
7032
|
+
}
|
|
7033
|
+
function validatedCapabilityOf2(error) {
|
|
7034
|
+
const verdict = authorizationVerdictOf2(error);
|
|
7035
|
+
if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
|
|
7036
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7037
|
+
let node = error;
|
|
7038
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
7039
|
+
seen.add(node);
|
|
7040
|
+
const record = node;
|
|
7041
|
+
const status = errorStatusOf(record);
|
|
7042
|
+
const meta = record.meta;
|
|
7043
|
+
if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
|
|
7044
|
+
const metadata = meta;
|
|
7045
|
+
const { resource, requiredAction } = metadata;
|
|
7046
|
+
if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
|
|
7047
|
+
const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
|
|
7048
|
+
const explicitService = record.service === void 0 ? metadata.service : record.service;
|
|
7049
|
+
if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
|
|
7050
|
+
return { resource, requiredAction };
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
7054
|
+
node = record.cause;
|
|
7055
|
+
}
|
|
7056
|
+
return void 0;
|
|
7057
|
+
}
|
|
7058
|
+
var MAX_CAUSE_DEPTH = 8;
|
|
7059
|
+
function errorStatusOf(node) {
|
|
7060
|
+
const meta = node.meta;
|
|
7061
|
+
const candidates = [
|
|
7062
|
+
node.status,
|
|
7063
|
+
node.statusCode,
|
|
7064
|
+
typeof meta === "object" && meta !== null ? meta.status : void 0
|
|
7065
|
+
];
|
|
7066
|
+
for (const candidate of candidates) {
|
|
7067
|
+
if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 400 && candidate <= 599) {
|
|
7068
|
+
return candidate;
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
return void 0;
|
|
7072
|
+
}
|
|
7073
|
+
function authorizationVerdictOf2(error) {
|
|
7074
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7075
|
+
let sawUnauthorizedCode = false;
|
|
7076
|
+
let node = error;
|
|
7077
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
7078
|
+
seen.add(node);
|
|
7079
|
+
const record = node;
|
|
7080
|
+
const status = errorStatusOf(record);
|
|
7081
|
+
if (status === 401) return "unauthenticated";
|
|
7082
|
+
if (status === 403) return "forbidden";
|
|
7083
|
+
if (status !== void 0) return "other";
|
|
7084
|
+
if (record.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;
|
|
7085
|
+
node = record.cause;
|
|
7086
|
+
}
|
|
7087
|
+
return sawUnauthorizedCode ? "forbidden" : void 0;
|
|
7088
|
+
}
|
|
6875
7089
|
function storageQuotaExceededError(service, message, meta) {
|
|
6876
7090
|
return {
|
|
6877
7091
|
code: ErrorCodes.STORAGE_QUOTA_EXCEEDED,
|
|
@@ -7567,12 +7781,14 @@ var KVService = class extends BaseService {
|
|
|
7567
7781
|
signal: this.combineSignals(options?.signal)
|
|
7568
7782
|
});
|
|
7569
7783
|
if (!response.ok) {
|
|
7570
|
-
const errorText = await response.text();
|
|
7784
|
+
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
7571
7785
|
if (response.status === 401 || response.status === 403) {
|
|
7572
7786
|
return this.authorizationFailure(
|
|
7573
7787
|
`Failed to batch read ${keys.length} key(s)`,
|
|
7574
7788
|
response,
|
|
7575
|
-
errorText
|
|
7789
|
+
errorText,
|
|
7790
|
+
paths,
|
|
7791
|
+
action
|
|
7576
7792
|
);
|
|
7577
7793
|
}
|
|
7578
7794
|
if (response.status === 413) {
|
|
@@ -7662,7 +7878,7 @@ var KVService = class extends BaseService {
|
|
|
7662
7878
|
return text;
|
|
7663
7879
|
}
|
|
7664
7880
|
}
|
|
7665
|
-
async createSignedReadUrlError(response, key) {
|
|
7881
|
+
async createSignedReadUrlError(response, key, path) {
|
|
7666
7882
|
let errorText = response.statusText;
|
|
7667
7883
|
try {
|
|
7668
7884
|
const text = await response.text();
|
|
@@ -7675,7 +7891,9 @@ var KVService = class extends BaseService {
|
|
|
7675
7891
|
return this.authorizationFailure(
|
|
7676
7892
|
`Failed to create signed read URL for key ${JSON.stringify(key)}`,
|
|
7677
7893
|
response,
|
|
7678
|
-
errorText
|
|
7894
|
+
errorText,
|
|
7895
|
+
[path],
|
|
7896
|
+
KVAction.GET
|
|
7679
7897
|
);
|
|
7680
7898
|
}
|
|
7681
7899
|
const code3 = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
@@ -7688,21 +7906,42 @@ var KVService = class extends BaseService {
|
|
|
7688
7906
|
)
|
|
7689
7907
|
);
|
|
7690
7908
|
}
|
|
7909
|
+
/** A known 401/403 keeps its typed status even if reading its body fails. */
|
|
7910
|
+
async readAuthorizationText(response) {
|
|
7911
|
+
try {
|
|
7912
|
+
return await response.text();
|
|
7913
|
+
} catch {
|
|
7914
|
+
return "";
|
|
7915
|
+
}
|
|
7916
|
+
}
|
|
7691
7917
|
/**
|
|
7692
7918
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
7693
7919
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
7694
7920
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
7695
7921
|
* still see the status; `meta.status` carries it for typed callers.
|
|
7696
|
-
*
|
|
7922
|
+
* Server hints must match the requested path/action (whether the node sends
|
|
7923
|
+
* a relative path or full resource); metadata uses only the request-derived
|
|
7924
|
+
* canonical resource under the authenticated session's space.
|
|
7697
7925
|
*/
|
|
7698
|
-
authorizationFailure(context, response, errorText, extraMeta = {}) {
|
|
7926
|
+
authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
|
|
7699
7927
|
const { resource, action } = parseAuthError(errorText);
|
|
7928
|
+
const requestedPath = paths.find(
|
|
7929
|
+
(path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
|
|
7930
|
+
);
|
|
7931
|
+
const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf2({
|
|
7932
|
+
service: "kv",
|
|
7933
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
7934
|
+
meta: {
|
|
7935
|
+
status: response.status,
|
|
7936
|
+
resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
|
|
7937
|
+
requiredAction: action
|
|
7938
|
+
}
|
|
7939
|
+
}) : void 0;
|
|
7700
7940
|
const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
|
|
7701
7941
|
return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
|
|
7702
7942
|
status: response.status,
|
|
7703
|
-
...
|
|
7704
|
-
...
|
|
7705
|
-
...extraMeta
|
|
7943
|
+
...extraMeta,
|
|
7944
|
+
...capability
|
|
7706
7945
|
}));
|
|
7707
7946
|
}
|
|
7708
7947
|
normalizeSignedReadUrlResponse(data) {
|
|
@@ -7746,13 +7985,24 @@ var KVService = class extends BaseService {
|
|
|
7746
7985
|
);
|
|
7747
7986
|
if (!response.ok) {
|
|
7748
7987
|
if (response.status === 401 || response.status === 403) {
|
|
7749
|
-
const errorText2 = await
|
|
7988
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
7750
7989
|
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
7990
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf2({
|
|
7991
|
+
service: "kv",
|
|
7992
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
7993
|
+
meta: {
|
|
7994
|
+
status: response.status,
|
|
7995
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
7996
|
+
requiredAction: KVAction.GET
|
|
7997
|
+
}
|
|
7998
|
+
}) : void 0;
|
|
7751
7999
|
return this.authorizationFailure(
|
|
7752
8000
|
`Failed to get key ${JSON.stringify(key)}`,
|
|
7753
8001
|
response,
|
|
7754
8002
|
errorText2,
|
|
7755
|
-
|
|
8003
|
+
[path],
|
|
8004
|
+
KVAction.GET,
|
|
8005
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
7756
8006
|
);
|
|
7757
8007
|
}
|
|
7758
8008
|
if (response.status === 404) {
|
|
@@ -7829,7 +8079,9 @@ var KVService = class extends BaseService {
|
|
|
7829
8079
|
return this.authorizationFailure(
|
|
7830
8080
|
`Failed to put key ${JSON.stringify(key)}`,
|
|
7831
8081
|
response,
|
|
7832
|
-
await
|
|
8082
|
+
await this.readAuthorizationText(response),
|
|
8083
|
+
[path],
|
|
8084
|
+
KVAction.PUT
|
|
7833
8085
|
);
|
|
7834
8086
|
}
|
|
7835
8087
|
if (!response.ok) {
|
|
@@ -7958,7 +8210,9 @@ var KVService = class extends BaseService {
|
|
|
7958
8210
|
return this.authorizationFailure(
|
|
7959
8211
|
`Failed to batch put ${items.length} key(s)`,
|
|
7960
8212
|
response,
|
|
7961
|
-
errorText
|
|
8213
|
+
errorText,
|
|
8214
|
+
paths,
|
|
8215
|
+
KVAction.PUT
|
|
7962
8216
|
);
|
|
7963
8217
|
}
|
|
7964
8218
|
const quotaError = this.handleQuotaErrorResponse(
|
|
@@ -8079,11 +8333,13 @@ var KVService = class extends BaseService {
|
|
|
8079
8333
|
}
|
|
8080
8334
|
);
|
|
8081
8335
|
if (!response.ok) {
|
|
8082
|
-
if (response.status === 401) {
|
|
8336
|
+
if (response.status === 401 || response.status === 403) {
|
|
8083
8337
|
return this.authorizationFailure(
|
|
8084
8338
|
"Failed to list keys",
|
|
8085
8339
|
response,
|
|
8086
|
-
await
|
|
8340
|
+
await this.readAuthorizationText(response),
|
|
8341
|
+
[listPath],
|
|
8342
|
+
KVAction.LIST
|
|
8087
8343
|
);
|
|
8088
8344
|
}
|
|
8089
8345
|
const errorText = await response.text();
|
|
@@ -8132,11 +8388,13 @@ var KVService = class extends BaseService {
|
|
|
8132
8388
|
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
8133
8389
|
);
|
|
8134
8390
|
if (!response.ok) {
|
|
8135
|
-
if (response.status === 401) {
|
|
8391
|
+
if (response.status === 401 || response.status === 403) {
|
|
8136
8392
|
return this.authorizationFailure(
|
|
8137
8393
|
`Failed to delete key ${JSON.stringify(key)}`,
|
|
8138
8394
|
response,
|
|
8139
|
-
await
|
|
8395
|
+
await this.readAuthorizationText(response),
|
|
8396
|
+
[path],
|
|
8397
|
+
KVAction.DELETE
|
|
8140
8398
|
);
|
|
8141
8399
|
}
|
|
8142
8400
|
if (response.status === 404) {
|
|
@@ -8194,11 +8452,13 @@ var KVService = class extends BaseService {
|
|
|
8194
8452
|
options?.signal
|
|
8195
8453
|
);
|
|
8196
8454
|
if (!response.ok) {
|
|
8197
|
-
if (response.status === 401) {
|
|
8455
|
+
if (response.status === 401 || response.status === 403) {
|
|
8198
8456
|
return this.authorizationFailure(
|
|
8199
8457
|
`Failed to get metadata for key ${JSON.stringify(key)}`,
|
|
8200
8458
|
response,
|
|
8201
|
-
await
|
|
8459
|
+
await this.readAuthorizationText(response),
|
|
8460
|
+
[path],
|
|
8461
|
+
KVAction.HEAD
|
|
8202
8462
|
);
|
|
8203
8463
|
}
|
|
8204
8464
|
if (response.status === 404) {
|
|
@@ -8265,7 +8525,7 @@ var KVService = class extends BaseService {
|
|
|
8265
8525
|
signal: this.combineSignals(options?.signal)
|
|
8266
8526
|
});
|
|
8267
8527
|
if (!response.ok) {
|
|
8268
|
-
return this.createSignedReadUrlError(response, key);
|
|
8528
|
+
return this.createSignedReadUrlError(response, key, path);
|
|
8269
8529
|
}
|
|
8270
8530
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
8271
8531
|
await response.json()
|
|
@@ -8489,14 +8749,15 @@ var SQLService = class extends BaseService {
|
|
|
8489
8749
|
};
|
|
8490
8750
|
if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
|
|
8491
8751
|
if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
|
|
8752
|
+
const action = this.actionForSql(sql, SQLAction.READ);
|
|
8492
8753
|
const response = await this.invokeSQL(
|
|
8493
8754
|
dbName,
|
|
8494
|
-
|
|
8755
|
+
action,
|
|
8495
8756
|
body,
|
|
8496
8757
|
options?.signal
|
|
8497
8758
|
);
|
|
8498
8759
|
if (!response.ok) {
|
|
8499
|
-
return this.handleErrorResponse(response, "query");
|
|
8760
|
+
return this.handleErrorResponse(response, "query", dbName, [action]);
|
|
8500
8761
|
}
|
|
8501
8762
|
const data = await response.json();
|
|
8502
8763
|
return ok(data);
|
|
@@ -8525,14 +8786,15 @@ var SQLService = class extends BaseService {
|
|
|
8525
8786
|
(statement) => this.actionForSql(statement, SQLAction.SCHEMA)
|
|
8526
8787
|
)
|
|
8527
8788
|
];
|
|
8789
|
+
const requestedActions2 = this.dedupeActions(actions);
|
|
8528
8790
|
const response = await this.invokeSQL(
|
|
8529
8791
|
dbName,
|
|
8530
|
-
|
|
8792
|
+
requestedActions2,
|
|
8531
8793
|
body,
|
|
8532
8794
|
options?.signal
|
|
8533
8795
|
);
|
|
8534
8796
|
if (!response.ok) {
|
|
8535
|
-
return this.handleErrorResponse(response, "execute");
|
|
8797
|
+
return this.handleErrorResponse(response, "execute", dbName, requestedActions2);
|
|
8536
8798
|
}
|
|
8537
8799
|
const data = await response.json();
|
|
8538
8800
|
return ok(data);
|
|
@@ -8547,14 +8809,15 @@ var SQLService = class extends BaseService {
|
|
|
8547
8809
|
return err(authRequiredError("sql"));
|
|
8548
8810
|
}
|
|
8549
8811
|
try {
|
|
8812
|
+
const requestedActions2 = this.actionsForSqlBatch(statements);
|
|
8550
8813
|
const response = await this.invokeSQL(
|
|
8551
8814
|
dbName,
|
|
8552
|
-
|
|
8815
|
+
requestedActions2,
|
|
8553
8816
|
{ action: "batch", statements },
|
|
8554
8817
|
options?.signal
|
|
8555
8818
|
);
|
|
8556
8819
|
if (!response.ok) {
|
|
8557
|
-
return this.handleErrorResponse(response, "batch");
|
|
8820
|
+
return this.handleErrorResponse(response, "batch", dbName, requestedActions2);
|
|
8558
8821
|
}
|
|
8559
8822
|
const data = await response.json();
|
|
8560
8823
|
return ok(data);
|
|
@@ -8576,7 +8839,7 @@ var SQLService = class extends BaseService {
|
|
|
8576
8839
|
options?.signal
|
|
8577
8840
|
);
|
|
8578
8841
|
if (!response.ok) {
|
|
8579
|
-
return this.handleErrorResponse(response, "executeStatement");
|
|
8842
|
+
return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
|
|
8580
8843
|
}
|
|
8581
8844
|
const data = await response.json();
|
|
8582
8845
|
return ok(data);
|
|
@@ -8598,7 +8861,7 @@ var SQLService = class extends BaseService {
|
|
|
8598
8861
|
options?.signal
|
|
8599
8862
|
);
|
|
8600
8863
|
if (!response.ok) {
|
|
8601
|
-
return this.handleErrorResponse(response, "export");
|
|
8864
|
+
return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
|
|
8602
8865
|
}
|
|
8603
8866
|
const resp = response;
|
|
8604
8867
|
if (typeof resp.blob === "function") {
|
|
@@ -8732,7 +8995,7 @@ var SQLService = class extends BaseService {
|
|
|
8732
8995
|
}))
|
|
8733
8996
|
);
|
|
8734
8997
|
}
|
|
8735
|
-
async handleErrorResponse(response, operation) {
|
|
8998
|
+
async handleErrorResponse(response, operation, dbName, requestedActions2) {
|
|
8736
8999
|
const errorText = await response.text();
|
|
8737
9000
|
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
8738
9001
|
if (response.status === 402) {
|
|
@@ -8751,9 +9014,11 @@ var SQLService = class extends BaseService {
|
|
|
8751
9014
|
errorBody
|
|
8752
9015
|
);
|
|
8753
9016
|
if (response.status === 401) {
|
|
8754
|
-
const {
|
|
8755
|
-
if (action
|
|
8756
|
-
|
|
9017
|
+
const { action } = parseAuthError(errorText);
|
|
9018
|
+
if (action && requestedActions2.includes(action)) {
|
|
9019
|
+
meta.requiredAction = action;
|
|
9020
|
+
meta.resource = `${this.context.session.spaceId}/sql/${dbName}`;
|
|
9021
|
+
}
|
|
8757
9022
|
}
|
|
8758
9023
|
return err(
|
|
8759
9024
|
serviceError(errorCode2, message, "sql", { meta })
|
|
@@ -11502,75 +11767,6 @@ function canonicalHashHex(sha2563, value) {
|
|
|
11502
11767
|
const canonical = canonicalize(value);
|
|
11503
11768
|
return hexEncode2(sha2563(utf8Encode(canonical)));
|
|
11504
11769
|
}
|
|
11505
|
-
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
11506
|
-
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
11507
|
-
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
11508
|
-
var NetworkIdError = class extends Error {
|
|
11509
|
-
constructor(message) {
|
|
11510
|
-
super(message);
|
|
11511
|
-
this.name = "NetworkIdError";
|
|
11512
|
-
}
|
|
11513
|
-
};
|
|
11514
|
-
function parseNetworkId(networkId) {
|
|
11515
|
-
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
11516
|
-
throw new NetworkIdError("networkId must be a non-empty string");
|
|
11517
|
-
}
|
|
11518
|
-
if (!networkId.startsWith(URN_PREFIX)) {
|
|
11519
|
-
throw new NetworkIdError(
|
|
11520
|
-
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
11521
|
-
);
|
|
11522
|
-
}
|
|
11523
|
-
const body = networkId.slice(URN_PREFIX.length);
|
|
11524
|
-
const lastColon = body.lastIndexOf(":");
|
|
11525
|
-
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
11526
|
-
throw new NetworkIdError(
|
|
11527
|
-
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
11528
|
-
);
|
|
11529
|
-
}
|
|
11530
|
-
const ownerDid = body.slice(0, lastColon);
|
|
11531
|
-
const name = body.slice(lastColon + 1);
|
|
11532
|
-
if (!ownerDid.startsWith("did:")) {
|
|
11533
|
-
throw new NetworkIdError(
|
|
11534
|
-
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
11535
|
-
);
|
|
11536
|
-
}
|
|
11537
|
-
const didParts = ownerDid.split(":");
|
|
11538
|
-
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
11539
|
-
throw new NetworkIdError(
|
|
11540
|
-
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
11541
|
-
);
|
|
11542
|
-
}
|
|
11543
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
11544
|
-
throw new NetworkIdError(
|
|
11545
|
-
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
11546
|
-
);
|
|
11547
|
-
}
|
|
11548
|
-
return { networkId, ownerDid, name };
|
|
11549
|
-
}
|
|
11550
|
-
function parsePkhOwnerDid(ownerDid) {
|
|
11551
|
-
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
11552
|
-
if (!match) return null;
|
|
11553
|
-
return {
|
|
11554
|
-
chainId: match[1],
|
|
11555
|
-
address: match[2].toLowerCase()
|
|
11556
|
-
};
|
|
11557
|
-
}
|
|
11558
|
-
function ownerDidMatches(a, b) {
|
|
11559
|
-
const aPkh = parsePkhOwnerDid(a);
|
|
11560
|
-
const bPkh = parsePkhOwnerDid(b);
|
|
11561
|
-
if (aPkh && bPkh) {
|
|
11562
|
-
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
11563
|
-
}
|
|
11564
|
-
return a === b;
|
|
11565
|
-
}
|
|
11566
|
-
function networkDiscoveryKey(name) {
|
|
11567
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
11568
|
-
throw new NetworkIdError(
|
|
11569
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
11570
|
-
);
|
|
11571
|
-
}
|
|
11572
|
-
return `.well-known/encryption/network/${name}`;
|
|
11573
|
-
}
|
|
11574
11770
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
11575
11771
|
var ENVELOPE_VERSION = 1;
|
|
11576
11772
|
var DEFAULT_KEY_VERSION = 1;
|
|
@@ -12406,7 +12602,7 @@ var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
|
12406
12602
|
});
|
|
12407
12603
|
var ENCRYPTION_PERMISSION_SERVICE2 = "tinycloud.encryption";
|
|
12408
12604
|
var ENCRYPTION_MANIFEST_SPACE2 = "encryption";
|
|
12409
|
-
var
|
|
12605
|
+
var SERVICE_LONG_TO_SHORT2 = Object.freeze(
|
|
12410
12606
|
Object.fromEntries(
|
|
12411
12607
|
Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
|
|
12412
12608
|
)
|
|
@@ -13146,6 +13342,7 @@ async function appendPermissionRequestArtifact(profile, artifact) {
|
|
|
13146
13342
|
});
|
|
13147
13343
|
}
|
|
13148
13344
|
async function writeSharedRecords(profile, store, entries) {
|
|
13345
|
+
await refuseWriteToDeletedProfile2(profile);
|
|
13149
13346
|
const path = store === "additional-delegations" ? additionalDelegationsPath(profile) : permissionRequestsPath(profile);
|
|
13150
13347
|
await writeJsonAtomic(path, entries);
|
|
13151
13348
|
await writeJsonAtomic(profileStoreMetadataPath(profile, store), { formatVersion: 1 });
|
|
@@ -13556,6 +13753,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
13556
13753
|
});
|
|
13557
13754
|
return node;
|
|
13558
13755
|
}
|
|
13756
|
+
var BOOTSTRAP_PROFILE_FIELDS = ["sessionDid", "spaceId"];
|
|
13559
13757
|
async function bootstrapDelegatedSession(ctx, delegation) {
|
|
13560
13758
|
const written = await ProfileManager.withLock(ctx.profile, async () => {
|
|
13561
13759
|
const previousProfile = await ProfileManager.getProfile(ctx.profile);
|
|
@@ -13605,13 +13803,13 @@ async function bootstrapDelegatedSession(ctx, delegation) {
|
|
|
13605
13803
|
throw error;
|
|
13606
13804
|
});
|
|
13607
13805
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
13608
|
-
if (
|
|
13806
|
+
if (profile === null || BOOTSTRAP_PROFILE_FIELDS.some((field) => profile[field] !== written.profile[field]) || JSON.stringify(session) !== JSON.stringify(written.session)) {
|
|
13609
13807
|
return `Profile "${ctx.profile}" changed while the import was pending (another login, logout or profile update), so its newer state was kept and the provisional session was not rolled back.`;
|
|
13610
13808
|
}
|
|
13611
13809
|
return restoreBeforeBootstrap(ctx.profile, written.previousProfile);
|
|
13612
13810
|
}, { timeoutMs: PROFILE_COMMIT_LOCK_TIMEOUT_MS }).catch((error) => (
|
|
13613
13811
|
// The lock or a read failed: the import's error stays the one reported.
|
|
13614
|
-
`Rolling back the provisional session of profile "${ctx.profile}" could not run (${
|
|
13812
|
+
`Rolling back the provisional session of profile "${ctx.profile}" could not run (${failureName(error)}); check \`tc --profile ${ctx.profile} context\`.`
|
|
13615
13813
|
));
|
|
13616
13814
|
throw annotate(cause, note);
|
|
13617
13815
|
};
|
|
@@ -13627,15 +13825,27 @@ async function restoreBeforeBootstrap(profileName, previousProfile) {
|
|
|
13627
13825
|
const failures = [];
|
|
13628
13826
|
for (const write of [
|
|
13629
13827
|
() => ProfileManager.clearSession(profileName),
|
|
13630
|
-
() => ProfileManager.
|
|
13828
|
+
() => ProfileManager.updateProfile(profileName, (current) => {
|
|
13829
|
+
const restored = { ...current };
|
|
13830
|
+
for (const field of BOOTSTRAP_PROFILE_FIELDS) {
|
|
13831
|
+
if (previousProfile[field] === void 0) delete restored[field];
|
|
13832
|
+
else restored[field] = previousProfile[field];
|
|
13833
|
+
}
|
|
13834
|
+
return restored;
|
|
13835
|
+
})
|
|
13631
13836
|
]) {
|
|
13632
13837
|
await write().catch((error) => {
|
|
13633
|
-
failures.push(
|
|
13838
|
+
failures.push(failureName(error));
|
|
13634
13839
|
});
|
|
13635
13840
|
}
|
|
13636
13841
|
if (failures.length === 0) return void 0;
|
|
13637
13842
|
return `Rolling back the provisional session of profile "${profileName}" failed too (${failures.join("; ")}); check \`tc --profile ${profileName} context\`.`;
|
|
13638
13843
|
}
|
|
13844
|
+
function failureName(error) {
|
|
13845
|
+
if (error instanceof SyntaxError) return "a profile file is not valid JSON";
|
|
13846
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") return error.code;
|
|
13847
|
+
return error instanceof Error ? error.name : "unknown error";
|
|
13848
|
+
}
|
|
13639
13849
|
function annotate(error, note) {
|
|
13640
13850
|
if (note === void 0) return error;
|
|
13641
13851
|
const cause = wrapError(error);
|
|
@@ -13926,7 +14136,7 @@ async function authenticatedNode(cmd) {
|
|
|
13926
14136
|
}
|
|
13927
14137
|
function assertOk(result) {
|
|
13928
14138
|
if (!result.ok) {
|
|
13929
|
-
throw
|
|
14139
|
+
throw cliErrorFromService(result.error);
|
|
13930
14140
|
}
|
|
13931
14141
|
}
|
|
13932
14142
|
async function loadManifestSource(source) {
|
|
@@ -15938,7 +16148,7 @@ async function importRequestBoundDelegation(ctx, artifact) {
|
|
|
15938
16148
|
ExitCode.ERROR
|
|
15939
16149
|
);
|
|
15940
16150
|
case "error":
|
|
15941
|
-
throw
|
|
16151
|
+
throw cliErrorFromService(result.error);
|
|
15942
16152
|
}
|
|
15943
16153
|
}
|
|
15944
16154
|
function isStoredDelegationLike(value) {
|
|
@@ -16673,7 +16883,7 @@ function registerDelegationCommand(program) {
|
|
|
16673
16883
|
expiry
|
|
16674
16884
|
});
|
|
16675
16885
|
if (!result.ok) {
|
|
16676
|
-
throw
|
|
16886
|
+
throw cliErrorFromService(result.error);
|
|
16677
16887
|
}
|
|
16678
16888
|
outputJson({
|
|
16679
16889
|
cid: result.data.cid,
|
|
@@ -16693,7 +16903,7 @@ function registerDelegationCommand(program) {
|
|
|
16693
16903
|
const node = await ensureAuthenticated(ctx);
|
|
16694
16904
|
const result = await node.delegationManager.list();
|
|
16695
16905
|
if (!result.ok) {
|
|
16696
|
-
throw
|
|
16906
|
+
throw cliErrorFromService(result.error);
|
|
16697
16907
|
}
|
|
16698
16908
|
let delegations = result.data;
|
|
16699
16909
|
if (options.granted) {
|
|
@@ -16739,7 +16949,7 @@ function registerDelegationCommand(program) {
|
|
|
16739
16949
|
const node = await ensureAuthenticated(ctx);
|
|
16740
16950
|
const result = await node.delegationManager.revoke(cid);
|
|
16741
16951
|
if (!result.ok) {
|
|
16742
|
-
throw
|
|
16952
|
+
throw cliErrorFromService(result.error);
|
|
16743
16953
|
}
|
|
16744
16954
|
outputJson({ cid, revoked: true });
|
|
16745
16955
|
} catch (error) {
|
|
@@ -16864,7 +17074,6 @@ function registerDoctorCommand(program) {
|
|
|
16864
17074
|
init_profiles();
|
|
16865
17075
|
init_formatter();
|
|
16866
17076
|
init_errors();
|
|
16867
|
-
init_constants();
|
|
16868
17077
|
import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
16869
17078
|
import { resolve } from "path";
|
|
16870
17079
|
init_theme();
|
|
@@ -16881,7 +17090,7 @@ function registerDuckdbCommand(program) {
|
|
|
16881
17090
|
() => node.duckdb.db(options.db).query(sqlStr, params)
|
|
16882
17091
|
);
|
|
16883
17092
|
if (!result.ok) {
|
|
16884
|
-
throw
|
|
17093
|
+
throw cliErrorFromService(result.error);
|
|
16885
17094
|
}
|
|
16886
17095
|
const { columns, rows, rowCount } = result.data;
|
|
16887
17096
|
if (shouldOutputJson()) {
|
|
@@ -16913,7 +17122,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
16913
17122
|
() => node.duckdb.db(options.db).execute(sqlStr, params)
|
|
16914
17123
|
);
|
|
16915
17124
|
if (!result.ok) {
|
|
16916
|
-
throw
|
|
17125
|
+
throw cliErrorFromService(result.error);
|
|
16917
17126
|
}
|
|
16918
17127
|
outputJson({ changes: result.data.changes });
|
|
16919
17128
|
} catch (error) {
|
|
@@ -16930,7 +17139,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
16930
17139
|
() => node.duckdb.db(options.db).describe()
|
|
16931
17140
|
);
|
|
16932
17141
|
if (!result.ok) {
|
|
16933
|
-
throw
|
|
17142
|
+
throw cliErrorFromService(result.error);
|
|
16934
17143
|
}
|
|
16935
17144
|
const schema = result.data;
|
|
16936
17145
|
if (shouldOutputJson()) {
|
|
@@ -16975,7 +17184,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
16975
17184
|
() => node.duckdb.db(options.db).export()
|
|
16976
17185
|
);
|
|
16977
17186
|
if (!result.ok) {
|
|
16978
|
-
throw
|
|
17187
|
+
throw cliErrorFromService(result.error);
|
|
16979
17188
|
}
|
|
16980
17189
|
const blob = result.data;
|
|
16981
17190
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
@@ -17002,7 +17211,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
17002
17211
|
() => node.duckdb.db(options.db).import(bytes)
|
|
17003
17212
|
);
|
|
17004
17213
|
if (!result.ok) {
|
|
17005
|
-
throw
|
|
17214
|
+
throw cliErrorFromService(result.error);
|
|
17006
17215
|
}
|
|
17007
17216
|
outputJson({
|
|
17008
17217
|
file: filePath,
|
|
@@ -17139,7 +17348,7 @@ init_theme();
|
|
|
17139
17348
|
async function throwKvError(error, spaceUri, profileName) {
|
|
17140
17349
|
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
17141
17350
|
if (hosted) throw hosted;
|
|
17142
|
-
throw
|
|
17351
|
+
throw cliErrorFromService(error);
|
|
17143
17352
|
}
|
|
17144
17353
|
function isByteCount(value) {
|
|
17145
17354
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
@@ -18656,7 +18865,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18656
18865
|
operation: () => secrets2.list(scopeOptions)
|
|
18657
18866
|
});
|
|
18658
18867
|
if (!result.ok) {
|
|
18659
|
-
throw
|
|
18868
|
+
throw cliErrorFromService(result.error);
|
|
18660
18869
|
}
|
|
18661
18870
|
const secretNames = Array.isArray(result.data) ? result.data : [];
|
|
18662
18871
|
outputJson({
|
|
@@ -18773,7 +18982,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18773
18982
|
operation: () => secrets2.put(name, secretValue, scopeOptions)
|
|
18774
18983
|
});
|
|
18775
18984
|
if (!result.ok) {
|
|
18776
|
-
throw
|
|
18985
|
+
throw cliErrorFromService(result.error);
|
|
18777
18986
|
}
|
|
18778
18987
|
outputJson({ name, written: true });
|
|
18779
18988
|
} catch (error) {
|
|
@@ -18799,7 +19008,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18799
19008
|
operation: () => secrets2.delete(name, scopeOptions)
|
|
18800
19009
|
});
|
|
18801
19010
|
if (!result.ok) {
|
|
18802
|
-
throw
|
|
19011
|
+
throw cliErrorFromService(result.error);
|
|
18803
19012
|
}
|
|
18804
19013
|
outputJson({ name, deleted: true });
|
|
18805
19014
|
} catch (error) {
|
|
@@ -27482,7 +27691,6 @@ async function notifyShare(input) {
|
|
|
27482
27691
|
};
|
|
27483
27692
|
}
|
|
27484
27693
|
let attempts = 0;
|
|
27485
|
-
let lastError;
|
|
27486
27694
|
while (attempts < attemptsLimit) {
|
|
27487
27695
|
if (input.signal?.aborted) throw new ShareNotifyError("share delivery was cancelled");
|
|
27488
27696
|
attempts += 1;
|
|
@@ -27509,10 +27717,19 @@ async function notifyShare(input) {
|
|
|
27509
27717
|
reason: "delivery-window-expired"
|
|
27510
27718
|
};
|
|
27511
27719
|
}
|
|
27512
|
-
|
|
27720
|
+
if (error !== null && typeof error === "object" && "status" in error && (error.status === 401 || error.status === 403)) {
|
|
27721
|
+
return {
|
|
27722
|
+
protocol: "tinycloud-share",
|
|
27723
|
+
version: 1,
|
|
27724
|
+
shareId: input.shareId,
|
|
27725
|
+
state: "partial-failure",
|
|
27726
|
+
idempotencyKey,
|
|
27727
|
+
attempts,
|
|
27728
|
+
retryable: false
|
|
27729
|
+
};
|
|
27730
|
+
}
|
|
27513
27731
|
}
|
|
27514
27732
|
}
|
|
27515
|
-
void lastError;
|
|
27516
27733
|
return { protocol: "tinycloud-share", version: 1, shareId: input.shareId, state: "partial-failure", idempotencyKey, attempts, retryable: true };
|
|
27517
27734
|
}
|
|
27518
27735
|
async function defaultIdempotencyKey(shareId, recipient) {
|
|
@@ -29514,7 +29731,7 @@ function registerSpaceCommand(program) {
|
|
|
29514
29731
|
const node = await ensureAuthenticated(ctx);
|
|
29515
29732
|
const result = await node.spaces.list();
|
|
29516
29733
|
if (!result.ok) {
|
|
29517
|
-
throw
|
|
29734
|
+
throw cliErrorFromService(result.error);
|
|
29518
29735
|
}
|
|
29519
29736
|
if (shouldOutputJson()) {
|
|
29520
29737
|
outputJson({ spaces: result.data, count: result.data.length });
|
|
@@ -29659,7 +29876,7 @@ async function throwSqlError(error, spaceUri, profileName, prefix) {
|
|
|
29659
29876
|
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
29660
29877
|
if (hosted) throw hosted;
|
|
29661
29878
|
const message = prefix ? `${prefix}${error.message}` : error.message;
|
|
29662
|
-
throw
|
|
29879
|
+
throw cliErrorFromService(error, message);
|
|
29663
29880
|
}
|
|
29664
29881
|
function registerSqlCommand(program) {
|
|
29665
29882
|
const sql = program.command("sql").description("SQLite database operations for your TinyCloud space").addHelpText("after", `
|
|
@@ -30371,7 +30588,7 @@ async function unlockVault(node, privateKey) {
|
|
|
30371
30588
|
const signer = new PrivateKeySigner2(privateKey);
|
|
30372
30589
|
const result = await node.vault.unlock(signer);
|
|
30373
30590
|
if (result && !result.ok) {
|
|
30374
|
-
throw
|
|
30591
|
+
throw cliErrorFromService(result.error);
|
|
30375
30592
|
}
|
|
30376
30593
|
}
|
|
30377
30594
|
function registerVaultCommand(program) {
|
|
@@ -30412,7 +30629,7 @@ function registerVaultCommand(program) {
|
|
|
30412
30629
|
}
|
|
30413
30630
|
const result = await withSpinner(`Writing ${key}...`, () => node.vault.put(key, putValue));
|
|
30414
30631
|
if (!result.ok) {
|
|
30415
|
-
throw
|
|
30632
|
+
throw cliErrorFromService(result.error);
|
|
30416
30633
|
}
|
|
30417
30634
|
outputJson({ key, written: true });
|
|
30418
30635
|
} catch (error) {
|
|
@@ -30431,7 +30648,7 @@ function registerVaultCommand(program) {
|
|
|
30431
30648
|
if (result.error.code === "NOT_FOUND") {
|
|
30432
30649
|
throw new CLIError("NOT_FOUND", `Key "${key}" not found`, ExitCode.NOT_FOUND);
|
|
30433
30650
|
}
|
|
30434
|
-
throw
|
|
30651
|
+
throw cliErrorFromService(result.error);
|
|
30435
30652
|
}
|
|
30436
30653
|
const data = result.data.data ?? result.data;
|
|
30437
30654
|
if (options.output) {
|
|
@@ -30462,7 +30679,7 @@ function registerVaultCommand(program) {
|
|
|
30462
30679
|
await withSpinner("Unlocking vault...", () => unlockVault(node, privateKey));
|
|
30463
30680
|
const result = await withSpinner(`Deleting ${key}...`, () => node.vault.delete(key));
|
|
30464
30681
|
if (!result.ok) {
|
|
30465
|
-
throw
|
|
30682
|
+
throw cliErrorFromService(result.error);
|
|
30466
30683
|
}
|
|
30467
30684
|
outputJson({ key, deleted: true });
|
|
30468
30685
|
} catch (error) {
|
|
@@ -30479,7 +30696,7 @@ function registerVaultCommand(program) {
|
|
|
30479
30696
|
const listOptions = options.prefix ? { prefix: options.prefix } : void 0;
|
|
30480
30697
|
const result = await withSpinner("Listing vault keys...", () => node.vault.list(listOptions));
|
|
30481
30698
|
if (!result.ok) {
|
|
30482
|
-
throw
|
|
30699
|
+
throw cliErrorFromService(result.error);
|
|
30483
30700
|
}
|
|
30484
30701
|
const keys = result.data.data ?? result.data;
|
|
30485
30702
|
const keyList = Array.isArray(keys) ? keys : [];
|
|
@@ -30505,7 +30722,7 @@ function registerVaultCommand(program) {
|
|
|
30505
30722
|
outputJson({ key, exists: false, metadata: {} });
|
|
30506
30723
|
return;
|
|
30507
30724
|
}
|
|
30508
|
-
throw
|
|
30725
|
+
throw cliErrorFromService(result.error);
|
|
30509
30726
|
}
|
|
30510
30727
|
outputJson({
|
|
30511
30728
|
key,
|
|
@@ -30555,7 +30772,7 @@ function registerVarsCommand(program) {
|
|
|
30555
30772
|
const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
|
|
30556
30773
|
const result = await withSpinner("Listing variables...", () => prefixedKv.list());
|
|
30557
30774
|
if (!result.ok) {
|
|
30558
|
-
throw
|
|
30775
|
+
throw cliErrorFromService(result.error);
|
|
30559
30776
|
}
|
|
30560
30777
|
const rawData = result.data.data ?? result.data;
|
|
30561
30778
|
const keyList = Array.isArray(rawData) ? rawData : rawData?.keys ?? [];
|
|
@@ -30579,7 +30796,7 @@ function registerVarsCommand(program) {
|
|
|
30579
30796
|
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
30580
30797
|
throw new CLIError("NOT_FOUND", `Variable "${name}" not found`, ExitCode.NOT_FOUND);
|
|
30581
30798
|
}
|
|
30582
|
-
throw
|
|
30799
|
+
throw cliErrorFromService(result.error);
|
|
30583
30800
|
}
|
|
30584
30801
|
const data = result.data.data;
|
|
30585
30802
|
let value;
|
|
@@ -30637,7 +30854,7 @@ function registerVarsCommand(program) {
|
|
|
30637
30854
|
const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
|
|
30638
30855
|
const result = await withSpinner(`Setting variable ${name}...`, () => prefixedKv.put(name, payload));
|
|
30639
30856
|
if (!result.ok) {
|
|
30640
|
-
throw
|
|
30857
|
+
throw cliErrorFromService(result.error);
|
|
30641
30858
|
}
|
|
30642
30859
|
outputJson({ name, written: true });
|
|
30643
30860
|
} catch (error) {
|
|
@@ -30653,7 +30870,7 @@ function registerVarsCommand(program) {
|
|
|
30653
30870
|
const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
|
|
30654
30871
|
const result = await withSpinner(`Deleting variable ${name}...`, () => prefixedKv.delete(name));
|
|
30655
30872
|
if (!result.ok) {
|
|
30656
|
-
throw
|
|
30873
|
+
throw cliErrorFromService(result.error);
|
|
30657
30874
|
}
|
|
30658
30875
|
outputJson({ name, deleted: true });
|
|
30659
30876
|
} catch (error) {
|