@tinycloud/node-sdk 3.0.0-beta.2 → 3.0.0-beta.20
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/{core-C6Czyr52.d.cts → core-DufYfxi-.d.cts} +39 -61
- package/dist/{core-C6Czyr52.d.ts → core-DufYfxi-.d.ts} +39 -61
- package/dist/core.cjs +213 -139
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +216 -141
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +215 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +217 -141
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/core.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(core_exports, {
|
|
|
50
50
|
DuckDbService: () => import_sdk_core15.DuckDbService,
|
|
51
51
|
FileSessionStorage: () => FileSessionStorage,
|
|
52
52
|
IdentityParseError: () => import_sdk_core11.IdentityParseError,
|
|
53
|
+
InvalidRestoredSessionError: () => InvalidRestoredSessionError,
|
|
53
54
|
KVService: () => import_sdk_core13.KVService,
|
|
54
55
|
ManifestValidationError: () => import_sdk_core12.ManifestValidationError,
|
|
55
56
|
MemorySessionStorage: () => MemorySessionStorage,
|
|
@@ -115,6 +116,7 @@ __export(core_exports, {
|
|
|
115
116
|
parseCanonicalNetworkId: () => import_sdk_core11.parseCanonicalNetworkId,
|
|
116
117
|
parseExpiry: () => import_sdk_core12.parseExpiry,
|
|
117
118
|
parsePkhDid: () => import_sdk_core11.parsePkhDid,
|
|
119
|
+
parseSecretCatalogKey: () => import_sdk_core16.parseSecretCatalogKey,
|
|
118
120
|
parseSpaceUri: () => import_sdk_core19.parseSpaceUri,
|
|
119
121
|
pkhDid: () => import_sdk_core11.pkhDid,
|
|
120
122
|
principalDid: () => import_sdk_core11.principalDid,
|
|
@@ -2081,6 +2083,11 @@ var NodeSecretsService = class {
|
|
|
2081
2083
|
if (!permission.ok) return permission;
|
|
2082
2084
|
return options === void 0 ? this.service.list() : this.service.list(options);
|
|
2083
2085
|
}
|
|
2086
|
+
async listAll() {
|
|
2087
|
+
const permission = await this.ensurePermission("", void 0, "list");
|
|
2088
|
+
if (!permission.ok) return permission;
|
|
2089
|
+
return this.service.listAll();
|
|
2090
|
+
}
|
|
2084
2091
|
get service() {
|
|
2085
2092
|
return this.config.getService();
|
|
2086
2093
|
}
|
|
@@ -2142,7 +2149,11 @@ var NodeSecretsService = class {
|
|
|
2142
2149
|
(entry) => manifests.some((candidate) => {
|
|
2143
2150
|
const resolved = (0, import_sdk_core7.resolveManifest)(candidate);
|
|
2144
2151
|
return resolved.resources.some(
|
|
2145
|
-
(resource) => resource.service === entry.service && spaceMatches(
|
|
2152
|
+
(resource) => resource.service === entry.service && spaceMatches(
|
|
2153
|
+
resource.space,
|
|
2154
|
+
entry.space,
|
|
2155
|
+
this.config.resolveSpace
|
|
2156
|
+
) && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
|
|
2146
2157
|
);
|
|
2147
2158
|
})
|
|
2148
2159
|
);
|
|
@@ -2275,6 +2286,22 @@ function cloneRecapCaveats(caveats) {
|
|
|
2275
2286
|
throw new Error("Verified persisted session ReCap contains invalid caveats.");
|
|
2276
2287
|
}
|
|
2277
2288
|
}
|
|
2289
|
+
function jsonFromVerifier(value) {
|
|
2290
|
+
if (value === void 0) return null;
|
|
2291
|
+
if (Array.isArray(value)) return value.map(jsonFromVerifier);
|
|
2292
|
+
if (value instanceof Map) {
|
|
2293
|
+
const entries = [...value.entries()];
|
|
2294
|
+
if (!entries.every(([key]) => typeof key === "string")) return value;
|
|
2295
|
+
return Object.fromEntries(entries.map(([key, nested]) => [key, jsonFromVerifier(nested)]));
|
|
2296
|
+
}
|
|
2297
|
+
if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
2298
|
+
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, jsonFromVerifier(nested)]));
|
|
2299
|
+
}
|
|
2300
|
+
return value;
|
|
2301
|
+
}
|
|
2302
|
+
function verifierRecapEntries(entries) {
|
|
2303
|
+
return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
|
|
2304
|
+
}
|
|
2278
2305
|
var ServiceGraphLifetime = class {
|
|
2279
2306
|
constructor(invokeFn, invokeAnyFn, fetchFn) {
|
|
2280
2307
|
this.invokeFn = invokeFn;
|
|
@@ -2347,18 +2374,23 @@ var UnsupportedSessionRestoreError = class extends Error {
|
|
|
2347
2374
|
this.name = "UnsupportedSessionRestoreError";
|
|
2348
2375
|
}
|
|
2349
2376
|
};
|
|
2377
|
+
var InvalidRestoredSessionError = class extends Error {
|
|
2378
|
+
constructor(message = "Persisted session authority is invalid or expired.") {
|
|
2379
|
+
super(message);
|
|
2380
|
+
this.code = "AUTH_EXPIRED";
|
|
2381
|
+
this.name = "InvalidRestoredSessionError";
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2350
2384
|
function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
|
|
2351
2385
|
if (typeof persistedVerificationMethod !== "string") return void 0;
|
|
2352
2386
|
const principal = canonicalVerificationMethod.split("#", 1)[0];
|
|
2353
2387
|
return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
|
|
2354
2388
|
}
|
|
2355
2389
|
function persistedExpiry(value) {
|
|
2356
|
-
if (typeof value !== "string")
|
|
2357
|
-
throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
|
|
2358
|
-
}
|
|
2390
|
+
if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
|
|
2359
2391
|
const expiry = new Date(value);
|
|
2360
2392
|
if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
|
|
2361
|
-
throw new
|
|
2393
|
+
throw new InvalidRestoredSessionError();
|
|
2362
2394
|
}
|
|
2363
2395
|
return expiry;
|
|
2364
2396
|
}
|
|
@@ -2837,6 +2869,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2837
2869
|
const authHosts = this.auth?.hosts ?? [];
|
|
2838
2870
|
return authHosts.length > 0 ? authHosts : [this.config.host];
|
|
2839
2871
|
}
|
|
2872
|
+
/** The registry-resolved Node endpoint and the identity returned by that exact Node. */
|
|
2873
|
+
async activeNodeIdentity() {
|
|
2874
|
+
const host = this.hosts[0];
|
|
2875
|
+
if (host === void 0) throw new Error("TinyCloud node host has not been resolved");
|
|
2876
|
+
const parsed = new URL(host);
|
|
2877
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) || parsed.origin !== host) {
|
|
2878
|
+
throw new Error("TinyCloud node host is not a canonical origin");
|
|
2879
|
+
}
|
|
2880
|
+
const nodeDid = await this.fetchNodeId();
|
|
2881
|
+
if (!nodeDid.startsWith("did:key:z")) throw new Error("TinyCloud node identity is not a did:key");
|
|
2882
|
+
return Object.freeze({ origin: parsed.origin, nodeDid });
|
|
2883
|
+
}
|
|
2884
|
+
/** Publish the active owner Node as a session-signed registry record. */
|
|
2885
|
+
async publishActiveNodeLocation(registryUrl, fetchFn = globalThis.fetch.bind(globalThis)) {
|
|
2886
|
+
const node = await this.activeNodeIdentity();
|
|
2887
|
+
return (0, import_sdk_core8.publishLocationRecord)({
|
|
2888
|
+
registryUrl,
|
|
2889
|
+
subject: this.credentialHolderDid,
|
|
2890
|
+
nodeOrigin: node.origin,
|
|
2891
|
+
signer: { type: "did:key", signBytes: (bytes) => this.signSessionBytes(bytes) },
|
|
2892
|
+
fetch: fetchFn
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2840
2895
|
/**
|
|
2841
2896
|
* Get the primary identity DID for this user.
|
|
2842
2897
|
* - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
|
|
@@ -2878,16 +2933,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2878
2933
|
*/
|
|
2879
2934
|
getVerifiedSessionCapabilities() {
|
|
2880
2935
|
const session = this.currentTinyCloudSession();
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
).map((entry) =>
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2936
|
+
return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
|
|
2937
|
+
}
|
|
2938
|
+
/** Normalize signed capabilities without collapsing nested encryption into raw authority. */
|
|
2939
|
+
projectSignedRecapCapabilities(siwe) {
|
|
2940
|
+
const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
|
|
2941
|
+
const normalized = (0, import_sdk_core8.parseRecapCapabilities)(() => [entry], siwe)[0];
|
|
2942
|
+
if (normalized.service !== "tinycloud.encryption") return normalized;
|
|
2943
|
+
if (!this.isEncryptionNetworkOperation("encryption", entry.path, entry.space)) {
|
|
2944
|
+
return { ...normalized, space: entry.space };
|
|
2945
|
+
}
|
|
2946
|
+
return {
|
|
2947
|
+
service: normalized.service,
|
|
2948
|
+
path: normalized.path,
|
|
2949
|
+
actions: normalized.actions,
|
|
2950
|
+
...normalized.caveats === void 0 ? {} : { caveats: normalized.caveats }
|
|
2951
|
+
};
|
|
2952
|
+
});
|
|
2953
|
+
return projected.sort(
|
|
2954
|
+
(left, right) => (left.space ?? "").localeCompare(right.space ?? "") || left.service.localeCompare(right.service) || left.path.localeCompare(right.path)
|
|
2955
|
+
);
|
|
2891
2956
|
}
|
|
2892
2957
|
/**
|
|
2893
2958
|
* Get the Ethereum address for this user.
|
|
@@ -3342,9 +3407,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3342
3407
|
* We must keep the full owner-scoped URI so a synthetic primary grant can
|
|
3343
3408
|
* never cover an operation on a different owner's space.
|
|
3344
3409
|
*
|
|
3345
|
-
*
|
|
3346
|
-
*
|
|
3347
|
-
*
|
|
3410
|
+
* Only top-level encryption network entries (`space: "encryption"` or
|
|
3411
|
+
* absent) become `resource` ops. A network-shaped path nested under a
|
|
3412
|
+
* data space remains a `spaceId` op, not raw network authority.
|
|
3348
3413
|
* One op per action.
|
|
3349
3414
|
*
|
|
3350
3415
|
* Returns `[]` for session-only / restored-without-siwe modes and for any
|
|
@@ -3365,7 +3430,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3365
3430
|
operations = entries.flatMap((entry) => {
|
|
3366
3431
|
const service = this.invocationServiceName(entry.service);
|
|
3367
3432
|
return entry.actions.map((action) => ({
|
|
3368
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
3433
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
|
|
3369
3434
|
service,
|
|
3370
3435
|
path: entry.path,
|
|
3371
3436
|
action,
|
|
@@ -3527,7 +3592,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3527
3592
|
return;
|
|
3528
3593
|
} catch (error) {
|
|
3529
3594
|
const message = error instanceof Error ? error.message : String(error);
|
|
3530
|
-
if (/Unauthorized Action|\
|
|
3595
|
+
if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
|
|
3531
3596
|
console.warn(
|
|
3532
3597
|
"TinyCloud account registry sync stopped: authorization verdict is not retryable",
|
|
3533
3598
|
error
|
|
@@ -3777,9 +3842,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3777
3842
|
*/
|
|
3778
3843
|
async restoreSession(sessionData) {
|
|
3779
3844
|
await this.wasmBindings.ensureInitialized?.();
|
|
3780
|
-
|
|
3845
|
+
let restoredJwk;
|
|
3846
|
+
try {
|
|
3847
|
+
restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
|
|
3848
|
+
} catch {
|
|
3849
|
+
throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
|
|
3850
|
+
}
|
|
3781
3851
|
if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
|
|
3782
|
-
throw new
|
|
3852
|
+
throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
|
|
3783
3853
|
}
|
|
3784
3854
|
const stagedManager = this.wasmBindings.createSessionManager();
|
|
3785
3855
|
const stagedReplace = stagedManager.replaceSessionKey;
|
|
@@ -3791,32 +3861,31 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3791
3861
|
let stagedKeyId;
|
|
3792
3862
|
let stagedJwk;
|
|
3793
3863
|
let canonicalVerificationMethod;
|
|
3864
|
+
const keyIds = liveKeys.call(this.sessionManager);
|
|
3865
|
+
if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
|
|
3866
|
+
throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
|
|
3867
|
+
}
|
|
3868
|
+
for (const keyId of keyIds) {
|
|
3869
|
+
if (keyId === this.sessionKeyId) continue;
|
|
3870
|
+
const jwk = this.sessionManager.jwk(keyId);
|
|
3871
|
+
if (!jwk) throw new Error("missing live session key");
|
|
3872
|
+
stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
|
|
3873
|
+
}
|
|
3794
3874
|
try {
|
|
3795
|
-
const keyIds = liveKeys.call(this.sessionManager);
|
|
3796
|
-
if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
|
|
3797
|
-
throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
|
|
3798
|
-
}
|
|
3799
|
-
for (const keyId of keyIds) {
|
|
3800
|
-
if (keyId === this.sessionKeyId) continue;
|
|
3801
|
-
const jwk = this.sessionManager.jwk(keyId);
|
|
3802
|
-
if (!jwk) throw new Error("missing live session key");
|
|
3803
|
-
stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
|
|
3804
|
-
}
|
|
3805
3875
|
stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
|
|
3809
|
-
canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
|
|
3810
|
-
} catch (error) {
|
|
3811
|
-
if (error instanceof UnsupportedSessionRestoreError) throw error;
|
|
3812
|
-
throw new Error("Persisted session has an invalid private Ed25519 session key.");
|
|
3876
|
+
} catch {
|
|
3877
|
+
throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
|
|
3813
3878
|
}
|
|
3879
|
+
const stagedJwkJson = stagedManager.jwk(stagedKeyId);
|
|
3880
|
+
if (!stagedJwkJson) throw new Error("missing restored session key");
|
|
3881
|
+
stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
|
|
3882
|
+
canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
|
|
3814
3883
|
const restoredVerificationMethod = canonicalRestoredVerificationMethod(
|
|
3815
3884
|
canonicalVerificationMethod,
|
|
3816
3885
|
sessionData.verificationMethod
|
|
3817
3886
|
);
|
|
3818
3887
|
if (!restoredVerificationMethod) {
|
|
3819
|
-
throw new
|
|
3888
|
+
throw new InvalidRestoredSessionError(
|
|
3820
3889
|
"Persisted session verification method does not match its private Ed25519 session key."
|
|
3821
3890
|
);
|
|
3822
3891
|
}
|
|
@@ -3828,26 +3897,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3828
3897
|
];
|
|
3829
3898
|
const hasPersistedProof = proofValues.every((value) => value !== void 0);
|
|
3830
3899
|
if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
|
|
3831
|
-
throw new
|
|
3900
|
+
throw new InvalidRestoredSessionError("Persisted session authority metadata is incomplete.");
|
|
3901
|
+
}
|
|
3902
|
+
let restoredAddress;
|
|
3903
|
+
try {
|
|
3904
|
+
restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
|
|
3905
|
+
} catch {
|
|
3906
|
+
throw new InvalidRestoredSessionError("Persisted session has an invalid wallet address.");
|
|
3832
3907
|
}
|
|
3833
|
-
const restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
|
|
3834
3908
|
let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
|
|
3835
3909
|
let stagedRecap = [];
|
|
3836
3910
|
if (hasPersistedProof) {
|
|
3837
3911
|
if (typeof this.wasmBindings.validatePersistedSession !== "function") {
|
|
3838
3912
|
throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
|
|
3839
3913
|
}
|
|
3840
|
-
const verified =
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3914
|
+
const verified = (() => {
|
|
3915
|
+
try {
|
|
3916
|
+
return this.wasmBindings.validatePersistedSession({
|
|
3917
|
+
delegationHeader: sessionData.delegationHeader,
|
|
3918
|
+
delegationCid: sessionData.delegationCid,
|
|
3919
|
+
spaceId: sessionData.spaceId,
|
|
3920
|
+
jwk: stagedJwk,
|
|
3921
|
+
address: restoredAddress,
|
|
3922
|
+
chainId: sessionData.chainId,
|
|
3923
|
+
siwe: sessionData.siwe,
|
|
3924
|
+
signature: sessionData.signature
|
|
3925
|
+
});
|
|
3926
|
+
} catch (error) {
|
|
3927
|
+
throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
|
|
3928
|
+
}
|
|
3929
|
+
})();
|
|
3930
|
+
const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
|
|
3851
3931
|
if (!Array.isArray(exactRecap) || !exactRecap.every(
|
|
3852
3932
|
(entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
|
|
3853
3933
|
(caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
|
|
@@ -3867,7 +3947,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3867
3947
|
const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
|
|
3868
3948
|
const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
|
|
3869
3949
|
if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
|
|
3870
|
-
throw new
|
|
3950
|
+
throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
|
|
3871
3951
|
}
|
|
3872
3952
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
3873
3953
|
}
|
|
@@ -3964,7 +4044,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3964
4044
|
operations = recap.flatMap((entry) => {
|
|
3965
4045
|
const service = this.invocationServiceName(entry.service);
|
|
3966
4046
|
return entry.actions.map((action) => ({
|
|
3967
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
4047
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
|
|
3968
4048
|
service,
|
|
3969
4049
|
path: entry.path,
|
|
3970
4050
|
action,
|
|
@@ -4060,7 +4140,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4060
4140
|
const delegations = input.recap.map((entry) => ({
|
|
4061
4141
|
cid: session.delegationCid,
|
|
4062
4142
|
delegateDID: session.verificationMethod,
|
|
4063
|
-
spaceId: entry.space,
|
|
4143
|
+
spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
|
|
4064
4144
|
path: entry.path,
|
|
4065
4145
|
actions: [...entry.actions],
|
|
4066
4146
|
caveats: cloneRecapCaveats(entry.caveats),
|
|
@@ -4750,16 +4830,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4750
4830
|
);
|
|
4751
4831
|
graph.assertActive();
|
|
4752
4832
|
if (!response.ok) {
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
|
|
4759
|
-
permissionHint = (0, import_sdk_services.parsePermissionHint)(record?.permissionHint) ?? (0, import_sdk_services.parsePermissionHint)(nested?.permissionHint);
|
|
4760
|
-
} catch {
|
|
4761
|
-
}
|
|
4762
|
-
}
|
|
4833
|
+
const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services.parsePermissionHint)({
|
|
4834
|
+
service: "tinycloud.encryption",
|
|
4835
|
+
path: networkId,
|
|
4836
|
+
actions: ["tinycloud.encryption/decrypt"]
|
|
4837
|
+
}) : void 0;
|
|
4763
4838
|
throw new import_sdk_core8.DecryptTransportResponseError(response.status, permissionHint);
|
|
4764
4839
|
}
|
|
4765
4840
|
return await response.json();
|
|
@@ -5184,63 +5259,62 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5184
5259
|
}
|
|
5185
5260
|
};
|
|
5186
5261
|
}
|
|
5187
|
-
/** Register a policy
|
|
5188
|
-
async
|
|
5262
|
+
/** Register a signed policy and sibling roots with this Node's Policy/v3 runtime. */
|
|
5263
|
+
async registerPolicy(params) {
|
|
5189
5264
|
this._serviceGraph.assertActive();
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
body: JSON.stringify({
|
|
5195
|
-
policy: { bytes: base64UrlEncode(params.policy.bytes), cid: params.policy.cid, proof: params.policy.proof },
|
|
5196
|
-
ownerDelegation: { cid: params.ownerDelegation.delegationCid, dagCbor: base64UrlEncode(params.ownerDelegation.signedDagCbor) },
|
|
5197
|
-
enforcementDelegation: params.enforcementDelegation,
|
|
5198
|
-
contentSourceDigest: params.contentSourceDigest
|
|
5199
|
-
})
|
|
5265
|
+
return (0, import_sdk_core8.registerPolicyV3)({
|
|
5266
|
+
...params,
|
|
5267
|
+
nodeOrigin: this.config.host,
|
|
5268
|
+
fetch: globalThis.fetch.bind(globalThis)
|
|
5200
5269
|
});
|
|
5201
|
-
if (!response.ok) {
|
|
5202
|
-
let code = "";
|
|
5203
|
-
try {
|
|
5204
|
-
const body = await response.clone().json();
|
|
5205
|
-
if (typeof body.error?.code === "string") code = ` ${body.error.code}`;
|
|
5206
|
-
} catch {
|
|
5207
|
-
}
|
|
5208
|
-
throw new Error(`Owner share policy registration failed: ${response.status}${code}`);
|
|
5209
|
-
}
|
|
5210
|
-
const responseBytes = new Uint8Array(await response.arrayBuffer());
|
|
5211
|
-
return (0, import_sdk_core8.validateOwnerSharePolicyRegistrationBytes)(responseBytes, params);
|
|
5212
5270
|
}
|
|
5213
|
-
/** Authorize
|
|
5214
|
-
async
|
|
5271
|
+
/** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
|
|
5272
|
+
async authorizeShareDeliveryV3(input) {
|
|
5215
5273
|
const session = this.currentTinyCloudSession();
|
|
5216
5274
|
const serviceSession = this._serviceContext?.session;
|
|
5217
5275
|
if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
|
|
5276
|
+
const idempotencyBytes = new TextEncoder().encode(input.idempotencyKey);
|
|
5277
|
+
if (idempotencyBytes.byteLength === 0 || idempotencyBytes.byteLength > 512) {
|
|
5278
|
+
throw new Error("Share delivery idempotency key is invalid");
|
|
5279
|
+
}
|
|
5280
|
+
const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
|
|
5218
5281
|
const body = {
|
|
5219
|
-
|
|
5282
|
+
envelope: input.envelope,
|
|
5283
|
+
sealedEnvelope: input.sealedEnvelope,
|
|
5284
|
+
envelopeKey: input.envelopeKey,
|
|
5220
5285
|
shareCid: input.shareCid,
|
|
5221
|
-
shareId: input.shareId,
|
|
5222
|
-
registrationCid: input.registrationCid,
|
|
5223
|
-
policyCid: input.policyCid,
|
|
5224
|
-
delegationCid: input.delegationCid,
|
|
5225
|
-
enforcementDelegationCid: input.enforcementDelegationCid,
|
|
5226
5286
|
recipientEmail: input.recipientEmail,
|
|
5227
5287
|
shareUrl: input.shareUrl,
|
|
5228
5288
|
documentName: input.documentName,
|
|
5229
|
-
jti: base64UrlEncode(
|
|
5230
|
-
|
|
5231
|
-
expiresAt: input.expiresAt
|
|
5289
|
+
jti: base64UrlEncode(jtiDigest.slice(0, 16)),
|
|
5290
|
+
expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
5232
5291
|
};
|
|
5233
5292
|
const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core8.canonicalizeEncryptionJson)(body)))));
|
|
5234
5293
|
const request = { ...body, requestBodyDigest };
|
|
5235
5294
|
const authorization = authorizationHeader(this.invokeAnyWithRuntimePermissions(serviceSession, [{ spaceId: session.spaceId, service: "kv", path: input.resourcePath, action: "tinycloud.kv/get" }]));
|
|
5236
|
-
const response = await fetch(`${this.config.host}/
|
|
5295
|
+
const response = await fetch(`${this.config.host}/policy/v3/deliveries/authorize`, {
|
|
5237
5296
|
method: "POST",
|
|
5238
5297
|
headers: { accept: "application/json", "content-type": "application/json", authorization },
|
|
5239
5298
|
body: (0, import_sdk_core8.canonicalizeEncryptionJson)(request)
|
|
5240
5299
|
});
|
|
5241
|
-
if (!response.ok) throw new Error(`
|
|
5242
|
-
const
|
|
5243
|
-
|
|
5300
|
+
if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
|
|
5301
|
+
const verified = (0, import_sdk_core8.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
|
|
5302
|
+
request,
|
|
5303
|
+
senderKeyDid: this.credentialHolderDid,
|
|
5304
|
+
deliveryAudience: input.deliveryAudience
|
|
5305
|
+
});
|
|
5306
|
+
const requestDigest = new Uint8Array(await crypto.subtle.digest(
|
|
5307
|
+
"SHA-256",
|
|
5308
|
+
new TextEncoder().encode(`${import_sdk_core8.CREDENTIAL_INVITATION_REQUEST_DOMAIN}${(0, import_sdk_core8.canonicalizeEncryptionJson)(verified.request)}`)
|
|
5309
|
+
));
|
|
5310
|
+
return {
|
|
5311
|
+
...verified,
|
|
5312
|
+
proof: {
|
|
5313
|
+
alg: "EdDSA",
|
|
5314
|
+
kid: this.credentialHolderDid,
|
|
5315
|
+
signature: base64UrlEncode(await this.signSessionBytes(requestDigest))
|
|
5316
|
+
}
|
|
5317
|
+
};
|
|
5244
5318
|
}
|
|
5245
5319
|
async createRootDelegationForSharing(params, assertActive) {
|
|
5246
5320
|
try {
|
|
@@ -5695,7 +5769,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5695
5769
|
continue;
|
|
5696
5770
|
}
|
|
5697
5771
|
let space;
|
|
5698
|
-
if (service !== "tinycloud.encryption") {
|
|
5772
|
+
if (service !== "tinycloud.encryption" || operation.resource === void 0) {
|
|
5699
5773
|
if (typeof operation.spaceId !== "string") continue;
|
|
5700
5774
|
try {
|
|
5701
5775
|
space = this.resolvePermissionSpace(operation.spaceId, session);
|
|
@@ -6240,11 +6314,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6240
6314
|
);
|
|
6241
6315
|
return { delegation: delegation2, prompted: true };
|
|
6242
6316
|
}
|
|
6243
|
-
const granted = (
|
|
6244
|
-
|
|
6245
|
-
session.siwe
|
|
6246
|
-
);
|
|
6247
|
-
const { subset, missing } = (0, import_sdk_core8.isCapabilitySubset)(expandedEntries, granted);
|
|
6317
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
6318
|
+
const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
|
|
6248
6319
|
if (!subset) {
|
|
6249
6320
|
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
6250
6321
|
const runtimeGrant = this.findGrantForOperations(
|
|
@@ -6564,7 +6635,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6564
6635
|
return abilities;
|
|
6565
6636
|
}
|
|
6566
6637
|
isEncryptionPermissionEntry(entry) {
|
|
6567
|
-
return entry.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE &&
|
|
6638
|
+
return entry.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
|
|
6568
6639
|
}
|
|
6569
6640
|
permissionsToRawAbilities(entries) {
|
|
6570
6641
|
const rawAbilities = {};
|
|
@@ -6588,7 +6659,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6588
6659
|
return entries.flatMap((entry) => {
|
|
6589
6660
|
const service = this.shortServiceName(entry.service);
|
|
6590
6661
|
return entry.actions.map((action) => ({
|
|
6591
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
6662
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
|
|
6592
6663
|
service,
|
|
6593
6664
|
path: entry.path,
|
|
6594
6665
|
action,
|
|
@@ -6596,13 +6667,21 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6596
6667
|
}));
|
|
6597
6668
|
});
|
|
6598
6669
|
}
|
|
6670
|
+
/** Raw network authority cannot be covered by a nested encryption space. */
|
|
6671
|
+
signedCapabilitySubset(requested, granted) {
|
|
6672
|
+
const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
|
|
6673
|
+
const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
|
|
6674
|
+
const missing = requested.filter((entry) => {
|
|
6675
|
+
const raw = this.isEncryptionPermissionEntry(entry);
|
|
6676
|
+
const comparable = raw ? { ...entry, space: "encryption" } : entry;
|
|
6677
|
+
return !(0, import_sdk_core8.isCapabilitySubset)([comparable], raw ? rawGranted : spaceGranted).subset;
|
|
6678
|
+
});
|
|
6679
|
+
return { subset: missing.length === 0, missing };
|
|
6680
|
+
}
|
|
6599
6681
|
sessionCoversPermissionEntries(session, entries) {
|
|
6600
6682
|
try {
|
|
6601
|
-
const granted = (
|
|
6602
|
-
|
|
6603
|
-
session.siwe
|
|
6604
|
-
);
|
|
6605
|
-
return (0, import_sdk_core8.isCapabilitySubset)(entries, granted).subset;
|
|
6683
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
6684
|
+
return this.signedCapabilitySubset(entries, granted).subset;
|
|
6606
6685
|
} catch {
|
|
6607
6686
|
return false;
|
|
6608
6687
|
}
|
|
@@ -6612,7 +6691,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6612
6691
|
const spaceId = this.resolvePermissionSpace(entry.space, session);
|
|
6613
6692
|
const service = this.shortServiceName(entry.service);
|
|
6614
6693
|
return entry.actions.map((action) => ({
|
|
6615
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
6694
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
|
|
6616
6695
|
service,
|
|
6617
6696
|
path: entry.path,
|
|
6618
6697
|
action,
|
|
@@ -6701,7 +6780,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6701
6780
|
return resources.flatMap((resource) => {
|
|
6702
6781
|
const service = this.invocationServiceName(resource.service);
|
|
6703
6782
|
return resource.actions.map((action) => ({
|
|
6704
|
-
...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
|
|
6783
|
+
...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
|
|
6705
6784
|
service,
|
|
6706
6785
|
path: resource.path,
|
|
6707
6786
|
action,
|
|
@@ -6762,7 +6841,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6762
6841
|
};
|
|
6763
6842
|
for (const resource of resources) {
|
|
6764
6843
|
const service = this.invocationServiceName(resource.service);
|
|
6765
|
-
if (this.isEncryptionNetworkOperation(service, resource.path)) {
|
|
6844
|
+
if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
|
|
6766
6845
|
rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
|
|
6767
6846
|
addActions(rawAbilities[resource.path], resource.actions);
|
|
6768
6847
|
continue;
|
|
@@ -6831,21 +6910,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6831
6910
|
}
|
|
6832
6911
|
return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
|
|
6833
6912
|
}
|
|
6834
|
-
// Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`.
|
|
6835
|
-
// EIP-155 address
|
|
6836
|
-
//
|
|
6837
|
-
// the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
|
|
6838
|
-
// an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
|
|
6839
|
-
// segment and leave everything else (crucially the case-sensitive space NAME)
|
|
6840
|
-
// byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
|
|
6913
|
+
// Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. Compare
|
|
6914
|
+
// only the embedded EIP-155 address without case sensitivity; chain, name,
|
|
6915
|
+
// and all non-PKH space identifiers remain byte-exact.
|
|
6841
6916
|
spaceIdsEqual(a, b) {
|
|
6842
6917
|
return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
|
|
6843
6918
|
}
|
|
6844
6919
|
normalizeSpaceAddress(space) {
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
(_match, prefix, addr) => prefix + addr.toLowerCase()
|
|
6848
|
-
);
|
|
6920
|
+
const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
|
|
6921
|
+
return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
|
|
6849
6922
|
}
|
|
6850
6923
|
actionContains(grantedAction, requestedAction) {
|
|
6851
6924
|
if (grantedAction === requestedAction) {
|
|
@@ -6862,10 +6935,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6862
6935
|
}
|
|
6863
6936
|
/** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
|
|
6864
6937
|
parseRecapWithCaveats(siwe) {
|
|
6865
|
-
|
|
6938
|
+
const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
|
|
6939
|
+
return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
|
|
6866
6940
|
}
|
|
6867
|
-
isEncryptionNetworkOperation(service, path) {
|
|
6868
|
-
return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
|
|
6941
|
+
isEncryptionNetworkOperation(service, path, space) {
|
|
6942
|
+
return service === "encryption" && (space === void 0 || space === "encryption") && path.startsWith("urn:tinycloud:encryption:");
|
|
6869
6943
|
}
|
|
6870
6944
|
operationFromInvokeAnyEntry(entry) {
|
|
6871
6945
|
const service = this.invocationServiceName(entry.service);
|
|
@@ -6878,7 +6952,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6878
6952
|
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
6879
6953
|
};
|
|
6880
6954
|
}
|
|
6881
|
-
if (this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
6955
|
+
if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
6882
6956
|
return {
|
|
6883
6957
|
resource: entry.path,
|
|
6884
6958
|
service,
|
|
@@ -7163,8 +7237,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7163
7237
|
const jwk = mySession.jwk;
|
|
7164
7238
|
const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
|
|
7165
7239
|
const now = /* @__PURE__ */ new Date();
|
|
7166
|
-
const
|
|
7167
|
-
const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
|
|
7240
|
+
const expirationTime = delegation.expiry;
|
|
7168
7241
|
const prepared = this.wasmBindings.prepareSession({
|
|
7169
7242
|
abilities,
|
|
7170
7243
|
address: this.wasmBindings.ensureEip55(mySession.address),
|
|
@@ -7268,8 +7341,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7268
7341
|
);
|
|
7269
7342
|
}
|
|
7270
7343
|
const now = /* @__PURE__ */ new Date();
|
|
7271
|
-
const
|
|
7272
|
-
const requestedExpiry = new Date(now.getTime() + expiryMs);
|
|
7344
|
+
const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
|
|
7273
7345
|
const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
|
|
7274
7346
|
const abilities = {};
|
|
7275
7347
|
const rawAbilities = {};
|
|
@@ -7725,6 +7797,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
|
|
|
7725
7797
|
DuckDbService,
|
|
7726
7798
|
FileSessionStorage,
|
|
7727
7799
|
IdentityParseError,
|
|
7800
|
+
InvalidRestoredSessionError,
|
|
7728
7801
|
KVService,
|
|
7729
7802
|
ManifestValidationError,
|
|
7730
7803
|
MemorySessionStorage,
|
|
@@ -7790,6 +7863,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
|
|
|
7790
7863
|
parseCanonicalNetworkId,
|
|
7791
7864
|
parseExpiry,
|
|
7792
7865
|
parsePkhDid,
|
|
7866
|
+
parseSecretCatalogKey,
|
|
7793
7867
|
parseSpaceUri,
|
|
7794
7868
|
pkhDid,
|
|
7795
7869
|
principalDid,
|