@tinycloud/node-sdk 3.0.0-beta.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{core-WJQ9KiSD.d.cts → core-DufYfxi-.d.cts} +31 -70
- package/dist/{core-WJQ9KiSD.d.ts → core-DufYfxi-.d.ts} +31 -70
- package/dist/core.cjs +171 -108
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +171 -108
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +173 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +172 -108
- 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,
|
|
@@ -2285,6 +2286,22 @@ function cloneRecapCaveats(caveats) {
|
|
|
2285
2286
|
throw new Error("Verified persisted session ReCap contains invalid caveats.");
|
|
2286
2287
|
}
|
|
2287
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
|
+
}
|
|
2288
2305
|
var ServiceGraphLifetime = class {
|
|
2289
2306
|
constructor(invokeFn, invokeAnyFn, fetchFn) {
|
|
2290
2307
|
this.invokeFn = invokeFn;
|
|
@@ -2357,18 +2374,23 @@ var UnsupportedSessionRestoreError = class extends Error {
|
|
|
2357
2374
|
this.name = "UnsupportedSessionRestoreError";
|
|
2358
2375
|
}
|
|
2359
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
|
+
};
|
|
2360
2384
|
function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
|
|
2361
2385
|
if (typeof persistedVerificationMethod !== "string") return void 0;
|
|
2362
2386
|
const principal = canonicalVerificationMethod.split("#", 1)[0];
|
|
2363
2387
|
return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
|
|
2364
2388
|
}
|
|
2365
2389
|
function persistedExpiry(value) {
|
|
2366
|
-
if (typeof value !== "string")
|
|
2367
|
-
throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
|
|
2368
|
-
}
|
|
2390
|
+
if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
|
|
2369
2391
|
const expiry = new Date(value);
|
|
2370
2392
|
if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
|
|
2371
|
-
throw new
|
|
2393
|
+
throw new InvalidRestoredSessionError();
|
|
2372
2394
|
}
|
|
2373
2395
|
return expiry;
|
|
2374
2396
|
}
|
|
@@ -2847,6 +2869,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2847
2869
|
const authHosts = this.auth?.hosts ?? [];
|
|
2848
2870
|
return authHosts.length > 0 ? authHosts : [this.config.host];
|
|
2849
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
|
+
}
|
|
2850
2895
|
/**
|
|
2851
2896
|
* Get the primary identity DID for this user.
|
|
2852
2897
|
* - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
|
|
@@ -2888,16 +2933,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2888
2933
|
*/
|
|
2889
2934
|
getVerifiedSessionCapabilities() {
|
|
2890
2935
|
const session = this.currentTinyCloudSession();
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
).map((entry) =>
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
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
|
+
);
|
|
2901
2956
|
}
|
|
2902
2957
|
/**
|
|
2903
2958
|
* Get the Ethereum address for this user.
|
|
@@ -3352,9 +3407,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3352
3407
|
* We must keep the full owner-scoped URI so a synthetic primary grant can
|
|
3353
3408
|
* never cover an operation on a different owner's space.
|
|
3354
3409
|
*
|
|
3355
|
-
*
|
|
3356
|
-
*
|
|
3357
|
-
*
|
|
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.
|
|
3358
3413
|
* One op per action.
|
|
3359
3414
|
*
|
|
3360
3415
|
* Returns `[]` for session-only / restored-without-siwe modes and for any
|
|
@@ -3375,7 +3430,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3375
3430
|
operations = entries.flatMap((entry) => {
|
|
3376
3431
|
const service = this.invocationServiceName(entry.service);
|
|
3377
3432
|
return entry.actions.map((action) => ({
|
|
3378
|
-
...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 },
|
|
3379
3434
|
service,
|
|
3380
3435
|
path: entry.path,
|
|
3381
3436
|
action,
|
|
@@ -3537,7 +3592,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3537
3592
|
return;
|
|
3538
3593
|
} catch (error) {
|
|
3539
3594
|
const message = error instanceof Error ? error.message : String(error);
|
|
3540
|
-
if (/Unauthorized Action|\
|
|
3595
|
+
if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
|
|
3541
3596
|
console.warn(
|
|
3542
3597
|
"TinyCloud account registry sync stopped: authorization verdict is not retryable",
|
|
3543
3598
|
error
|
|
@@ -3787,9 +3842,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3787
3842
|
*/
|
|
3788
3843
|
async restoreSession(sessionData) {
|
|
3789
3844
|
await this.wasmBindings.ensureInitialized?.();
|
|
3790
|
-
|
|
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
|
+
}
|
|
3791
3851
|
if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
|
|
3792
|
-
throw new
|
|
3852
|
+
throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
|
|
3793
3853
|
}
|
|
3794
3854
|
const stagedManager = this.wasmBindings.createSessionManager();
|
|
3795
3855
|
const stagedReplace = stagedManager.replaceSessionKey;
|
|
@@ -3801,32 +3861,31 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3801
3861
|
let stagedKeyId;
|
|
3802
3862
|
let stagedJwk;
|
|
3803
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
|
+
}
|
|
3804
3874
|
try {
|
|
3805
|
-
const keyIds = liveKeys.call(this.sessionManager);
|
|
3806
|
-
if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
|
|
3807
|
-
throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
|
|
3808
|
-
}
|
|
3809
|
-
for (const keyId of keyIds) {
|
|
3810
|
-
if (keyId === this.sessionKeyId) continue;
|
|
3811
|
-
const jwk = this.sessionManager.jwk(keyId);
|
|
3812
|
-
if (!jwk) throw new Error("missing live session key");
|
|
3813
|
-
stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
|
|
3814
|
-
}
|
|
3815
3875
|
stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
|
|
3819
|
-
canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
|
|
3820
|
-
} catch (error) {
|
|
3821
|
-
if (error instanceof UnsupportedSessionRestoreError) throw error;
|
|
3822
|
-
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.");
|
|
3823
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);
|
|
3824
3883
|
const restoredVerificationMethod = canonicalRestoredVerificationMethod(
|
|
3825
3884
|
canonicalVerificationMethod,
|
|
3826
3885
|
sessionData.verificationMethod
|
|
3827
3886
|
);
|
|
3828
3887
|
if (!restoredVerificationMethod) {
|
|
3829
|
-
throw new
|
|
3888
|
+
throw new InvalidRestoredSessionError(
|
|
3830
3889
|
"Persisted session verification method does not match its private Ed25519 session key."
|
|
3831
3890
|
);
|
|
3832
3891
|
}
|
|
@@ -3838,26 +3897,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3838
3897
|
];
|
|
3839
3898
|
const hasPersistedProof = proofValues.every((value) => value !== void 0);
|
|
3840
3899
|
if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
|
|
3841
|
-
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.");
|
|
3842
3907
|
}
|
|
3843
|
-
const restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
|
|
3844
3908
|
let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
|
|
3845
3909
|
let stagedRecap = [];
|
|
3846
3910
|
if (hasPersistedProof) {
|
|
3847
3911
|
if (typeof this.wasmBindings.validatePersistedSession !== "function") {
|
|
3848
3912
|
throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
|
|
3849
3913
|
}
|
|
3850
|
-
const verified =
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
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;
|
|
3861
3931
|
if (!Array.isArray(exactRecap) || !exactRecap.every(
|
|
3862
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(
|
|
3863
3933
|
(caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
|
|
@@ -3877,7 +3947,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3877
3947
|
const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
|
|
3878
3948
|
const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
|
|
3879
3949
|
if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
|
|
3880
|
-
throw new
|
|
3950
|
+
throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
|
|
3881
3951
|
}
|
|
3882
3952
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
3883
3953
|
}
|
|
@@ -3974,7 +4044,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3974
4044
|
operations = recap.flatMap((entry) => {
|
|
3975
4045
|
const service = this.invocationServiceName(entry.service);
|
|
3976
4046
|
return entry.actions.map((action) => ({
|
|
3977
|
-
...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 },
|
|
3978
4048
|
service,
|
|
3979
4049
|
path: entry.path,
|
|
3980
4050
|
action,
|
|
@@ -4070,7 +4140,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4070
4140
|
const delegations = input.recap.map((entry) => ({
|
|
4071
4141
|
cid: session.delegationCid,
|
|
4072
4142
|
delegateDID: session.verificationMethod,
|
|
4073
|
-
spaceId: entry.space,
|
|
4143
|
+
spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
|
|
4074
4144
|
path: entry.path,
|
|
4075
4145
|
actions: [...entry.actions],
|
|
4076
4146
|
caveats: cloneRecapCaveats(entry.caveats),
|
|
@@ -4760,16 +4830,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4760
4830
|
);
|
|
4761
4831
|
graph.assertActive();
|
|
4762
4832
|
if (!response.ok) {
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
|
|
4769
|
-
permissionHint = (0, import_sdk_services.parsePermissionHint)(record?.permissionHint) ?? (0, import_sdk_services.parsePermissionHint)(nested?.permissionHint);
|
|
4770
|
-
} catch {
|
|
4771
|
-
}
|
|
4772
|
-
}
|
|
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;
|
|
4773
4838
|
throw new import_sdk_core8.DecryptTransportResponseError(response.status, permissionHint);
|
|
4774
4839
|
}
|
|
4775
4840
|
return await response.json();
|
|
@@ -5203,16 +5268,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5203
5268
|
fetch: globalThis.fetch.bind(globalThis)
|
|
5204
5269
|
});
|
|
5205
5270
|
}
|
|
5206
|
-
/** @deprecated Policy/v2 delivery transport is retired. Use authorizeShareDeliveryV3. */
|
|
5207
|
-
async authorizeShareDelivery(input) {
|
|
5208
|
-
void input;
|
|
5209
|
-
throw new Error("Policy/v2 share delivery is retired; use authorizeShareDeliveryV3");
|
|
5210
|
-
}
|
|
5211
5271
|
/** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
|
|
5212
5272
|
async authorizeShareDeliveryV3(input) {
|
|
5213
5273
|
const session = this.currentTinyCloudSession();
|
|
5214
5274
|
const serviceSession = this._serviceContext?.session;
|
|
5215
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));
|
|
5216
5281
|
const body = {
|
|
5217
5282
|
envelope: input.envelope,
|
|
5218
5283
|
sealedEnvelope: input.sealedEnvelope,
|
|
@@ -5221,7 +5286,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5221
5286
|
recipientEmail: input.recipientEmail,
|
|
5222
5287
|
shareUrl: input.shareUrl,
|
|
5223
5288
|
documentName: input.documentName,
|
|
5224
|
-
jti: base64UrlEncode(
|
|
5289
|
+
jti: base64UrlEncode(jtiDigest.slice(0, 16)),
|
|
5225
5290
|
expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
5226
5291
|
};
|
|
5227
5292
|
const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core8.canonicalizeEncryptionJson)(body)))));
|
|
@@ -5236,9 +5301,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5236
5301
|
const verified = (0, import_sdk_core8.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
|
|
5237
5302
|
request,
|
|
5238
5303
|
senderKeyDid: this.credentialHolderDid,
|
|
5239
|
-
|
|
5304
|
+
deliveryAudience: input.deliveryAudience
|
|
5240
5305
|
});
|
|
5241
|
-
void input.nodeProof;
|
|
5242
5306
|
const requestDigest = new Uint8Array(await crypto.subtle.digest(
|
|
5243
5307
|
"SHA-256",
|
|
5244
5308
|
new TextEncoder().encode(`${import_sdk_core8.CREDENTIAL_INVITATION_REQUEST_DOMAIN}${(0, import_sdk_core8.canonicalizeEncryptionJson)(verified.request)}`)
|
|
@@ -5705,7 +5769,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5705
5769
|
continue;
|
|
5706
5770
|
}
|
|
5707
5771
|
let space;
|
|
5708
|
-
if (service !== "tinycloud.encryption") {
|
|
5772
|
+
if (service !== "tinycloud.encryption" || operation.resource === void 0) {
|
|
5709
5773
|
if (typeof operation.spaceId !== "string") continue;
|
|
5710
5774
|
try {
|
|
5711
5775
|
space = this.resolvePermissionSpace(operation.spaceId, session);
|
|
@@ -6250,11 +6314,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6250
6314
|
);
|
|
6251
6315
|
return { delegation: delegation2, prompted: true };
|
|
6252
6316
|
}
|
|
6253
|
-
const granted = (
|
|
6254
|
-
|
|
6255
|
-
session.siwe
|
|
6256
|
-
);
|
|
6257
|
-
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);
|
|
6258
6319
|
if (!subset) {
|
|
6259
6320
|
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
6260
6321
|
const runtimeGrant = this.findGrantForOperations(
|
|
@@ -6574,7 +6635,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6574
6635
|
return abilities;
|
|
6575
6636
|
}
|
|
6576
6637
|
isEncryptionPermissionEntry(entry) {
|
|
6577
|
-
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);
|
|
6578
6639
|
}
|
|
6579
6640
|
permissionsToRawAbilities(entries) {
|
|
6580
6641
|
const rawAbilities = {};
|
|
@@ -6598,7 +6659,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6598
6659
|
return entries.flatMap((entry) => {
|
|
6599
6660
|
const service = this.shortServiceName(entry.service);
|
|
6600
6661
|
return entry.actions.map((action) => ({
|
|
6601
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
6662
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
|
|
6602
6663
|
service,
|
|
6603
6664
|
path: entry.path,
|
|
6604
6665
|
action,
|
|
@@ -6606,13 +6667,21 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6606
6667
|
}));
|
|
6607
6668
|
});
|
|
6608
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
|
+
}
|
|
6609
6681
|
sessionCoversPermissionEntries(session, entries) {
|
|
6610
6682
|
try {
|
|
6611
|
-
const granted = (
|
|
6612
|
-
|
|
6613
|
-
session.siwe
|
|
6614
|
-
);
|
|
6615
|
-
return (0, import_sdk_core8.isCapabilitySubset)(entries, granted).subset;
|
|
6683
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
6684
|
+
return this.signedCapabilitySubset(entries, granted).subset;
|
|
6616
6685
|
} catch {
|
|
6617
6686
|
return false;
|
|
6618
6687
|
}
|
|
@@ -6622,7 +6691,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6622
6691
|
const spaceId = this.resolvePermissionSpace(entry.space, session);
|
|
6623
6692
|
const service = this.shortServiceName(entry.service);
|
|
6624
6693
|
return entry.actions.map((action) => ({
|
|
6625
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
6694
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
|
|
6626
6695
|
service,
|
|
6627
6696
|
path: entry.path,
|
|
6628
6697
|
action,
|
|
@@ -6711,7 +6780,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6711
6780
|
return resources.flatMap((resource) => {
|
|
6712
6781
|
const service = this.invocationServiceName(resource.service);
|
|
6713
6782
|
return resource.actions.map((action) => ({
|
|
6714
|
-
...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 },
|
|
6715
6784
|
service,
|
|
6716
6785
|
path: resource.path,
|
|
6717
6786
|
action,
|
|
@@ -6772,7 +6841,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6772
6841
|
};
|
|
6773
6842
|
for (const resource of resources) {
|
|
6774
6843
|
const service = this.invocationServiceName(resource.service);
|
|
6775
|
-
if (this.isEncryptionNetworkOperation(service, resource.path)) {
|
|
6844
|
+
if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
|
|
6776
6845
|
rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
|
|
6777
6846
|
addActions(rawAbilities[resource.path], resource.actions);
|
|
6778
6847
|
continue;
|
|
@@ -6841,21 +6910,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6841
6910
|
}
|
|
6842
6911
|
return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
|
|
6843
6912
|
}
|
|
6844
|
-
// Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`.
|
|
6845
|
-
// EIP-155 address
|
|
6846
|
-
//
|
|
6847
|
-
// the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
|
|
6848
|
-
// an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
|
|
6849
|
-
// segment and leave everything else (crucially the case-sensitive space NAME)
|
|
6850
|
-
// 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.
|
|
6851
6916
|
spaceIdsEqual(a, b) {
|
|
6852
6917
|
return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
|
|
6853
6918
|
}
|
|
6854
6919
|
normalizeSpaceAddress(space) {
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
(_match, prefix, addr) => prefix + addr.toLowerCase()
|
|
6858
|
-
);
|
|
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]}`;
|
|
6859
6922
|
}
|
|
6860
6923
|
actionContains(grantedAction, requestedAction) {
|
|
6861
6924
|
if (grantedAction === requestedAction) {
|
|
@@ -6872,10 +6935,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6872
6935
|
}
|
|
6873
6936
|
/** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
|
|
6874
6937
|
parseRecapWithCaveats(siwe) {
|
|
6875
|
-
|
|
6938
|
+
const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
|
|
6939
|
+
return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
|
|
6876
6940
|
}
|
|
6877
|
-
isEncryptionNetworkOperation(service, path) {
|
|
6878
|
-
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:");
|
|
6879
6943
|
}
|
|
6880
6944
|
operationFromInvokeAnyEntry(entry) {
|
|
6881
6945
|
const service = this.invocationServiceName(entry.service);
|
|
@@ -6888,7 +6952,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6888
6952
|
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
6889
6953
|
};
|
|
6890
6954
|
}
|
|
6891
|
-
if (this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
6955
|
+
if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
6892
6956
|
return {
|
|
6893
6957
|
resource: entry.path,
|
|
6894
6958
|
service,
|
|
@@ -7173,8 +7237,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7173
7237
|
const jwk = mySession.jwk;
|
|
7174
7238
|
const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
|
|
7175
7239
|
const now = /* @__PURE__ */ new Date();
|
|
7176
|
-
const
|
|
7177
|
-
const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
|
|
7240
|
+
const expirationTime = delegation.expiry;
|
|
7178
7241
|
const prepared = this.wasmBindings.prepareSession({
|
|
7179
7242
|
abilities,
|
|
7180
7243
|
address: this.wasmBindings.ensureEip55(mySession.address),
|
|
@@ -7278,8 +7341,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7278
7341
|
);
|
|
7279
7342
|
}
|
|
7280
7343
|
const now = /* @__PURE__ */ new Date();
|
|
7281
|
-
const
|
|
7282
|
-
const requestedExpiry = new Date(now.getTime() + expiryMs);
|
|
7344
|
+
const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
|
|
7283
7345
|
const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
|
|
7284
7346
|
const abilities = {};
|
|
7285
7347
|
const rawAbilities = {};
|
|
@@ -7735,6 +7797,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
|
|
|
7735
7797
|
DuckDbService,
|
|
7736
7798
|
FileSessionStorage,
|
|
7737
7799
|
IdentityParseError,
|
|
7800
|
+
InvalidRestoredSessionError,
|
|
7738
7801
|
KVService,
|
|
7739
7802
|
ManifestValidationError,
|
|
7740
7803
|
MemorySessionStorage,
|