@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.js
CHANGED
|
@@ -1709,11 +1709,13 @@ import {
|
|
|
1709
1709
|
canonicalizeAddress as canonicalizeAddress2,
|
|
1710
1710
|
pkhDid as pkhDid2,
|
|
1711
1711
|
resolveTinyCloudHosts as resolveTinyCloudHosts2,
|
|
1712
|
+
publishLocationRecord,
|
|
1712
1713
|
principalDidEquals as principalDidEquals2,
|
|
1713
1714
|
parseNetworkId as parseNetworkId2,
|
|
1714
1715
|
resolveSecretPath as resolveSecretPath2,
|
|
1715
|
-
|
|
1716
|
-
|
|
1716
|
+
registerPolicyV3,
|
|
1717
|
+
CREDENTIAL_INVITATION_REQUEST_DOMAIN,
|
|
1718
|
+
validateShareDeliveryAuthorizationV3Bytes,
|
|
1717
1719
|
verifyEip191MessageSignature,
|
|
1718
1720
|
signCompactUcanRootAuthorization
|
|
1719
1721
|
} from "@tinycloud/sdk-core";
|
|
@@ -2084,6 +2086,11 @@ var NodeSecretsService = class {
|
|
|
2084
2086
|
if (!permission.ok) return permission;
|
|
2085
2087
|
return options === void 0 ? this.service.list() : this.service.list(options);
|
|
2086
2088
|
}
|
|
2089
|
+
async listAll() {
|
|
2090
|
+
const permission = await this.ensurePermission("", void 0, "list");
|
|
2091
|
+
if (!permission.ok) return permission;
|
|
2092
|
+
return this.service.listAll();
|
|
2093
|
+
}
|
|
2087
2094
|
get service() {
|
|
2088
2095
|
return this.config.getService();
|
|
2089
2096
|
}
|
|
@@ -2145,7 +2152,11 @@ var NodeSecretsService = class {
|
|
|
2145
2152
|
(entry) => manifests.some((candidate) => {
|
|
2146
2153
|
const resolved = resolveManifest(candidate);
|
|
2147
2154
|
return resolved.resources.some(
|
|
2148
|
-
(resource) => resource.service === entry.service && spaceMatches(
|
|
2155
|
+
(resource) => resource.service === entry.service && spaceMatches(
|
|
2156
|
+
resource.space,
|
|
2157
|
+
entry.space,
|
|
2158
|
+
this.config.resolveSpace
|
|
2159
|
+
) && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
|
|
2149
2160
|
);
|
|
2150
2161
|
})
|
|
2151
2162
|
);
|
|
@@ -2278,6 +2289,22 @@ function cloneRecapCaveats(caveats) {
|
|
|
2278
2289
|
throw new Error("Verified persisted session ReCap contains invalid caveats.");
|
|
2279
2290
|
}
|
|
2280
2291
|
}
|
|
2292
|
+
function jsonFromVerifier(value) {
|
|
2293
|
+
if (value === void 0) return null;
|
|
2294
|
+
if (Array.isArray(value)) return value.map(jsonFromVerifier);
|
|
2295
|
+
if (value instanceof Map) {
|
|
2296
|
+
const entries = [...value.entries()];
|
|
2297
|
+
if (!entries.every(([key]) => typeof key === "string")) return value;
|
|
2298
|
+
return Object.fromEntries(entries.map(([key, nested]) => [key, jsonFromVerifier(nested)]));
|
|
2299
|
+
}
|
|
2300
|
+
if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
2301
|
+
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, jsonFromVerifier(nested)]));
|
|
2302
|
+
}
|
|
2303
|
+
return value;
|
|
2304
|
+
}
|
|
2305
|
+
function verifierRecapEntries(entries) {
|
|
2306
|
+
return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
|
|
2307
|
+
}
|
|
2281
2308
|
var ServiceGraphLifetime = class {
|
|
2282
2309
|
constructor(invokeFn, invokeAnyFn, fetchFn) {
|
|
2283
2310
|
this.invokeFn = invokeFn;
|
|
@@ -2350,18 +2377,23 @@ var UnsupportedSessionRestoreError = class extends Error {
|
|
|
2350
2377
|
this.name = "UnsupportedSessionRestoreError";
|
|
2351
2378
|
}
|
|
2352
2379
|
};
|
|
2380
|
+
var InvalidRestoredSessionError = class extends Error {
|
|
2381
|
+
constructor(message = "Persisted session authority is invalid or expired.") {
|
|
2382
|
+
super(message);
|
|
2383
|
+
this.code = "AUTH_EXPIRED";
|
|
2384
|
+
this.name = "InvalidRestoredSessionError";
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2353
2387
|
function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
|
|
2354
2388
|
if (typeof persistedVerificationMethod !== "string") return void 0;
|
|
2355
2389
|
const principal = canonicalVerificationMethod.split("#", 1)[0];
|
|
2356
2390
|
return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
|
|
2357
2391
|
}
|
|
2358
2392
|
function persistedExpiry(value) {
|
|
2359
|
-
if (typeof value !== "string")
|
|
2360
|
-
throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
|
|
2361
|
-
}
|
|
2393
|
+
if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
|
|
2362
2394
|
const expiry = new Date(value);
|
|
2363
2395
|
if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
|
|
2364
|
-
throw new
|
|
2396
|
+
throw new InvalidRestoredSessionError();
|
|
2365
2397
|
}
|
|
2366
2398
|
return expiry;
|
|
2367
2399
|
}
|
|
@@ -2840,6 +2872,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2840
2872
|
const authHosts = this.auth?.hosts ?? [];
|
|
2841
2873
|
return authHosts.length > 0 ? authHosts : [this.config.host];
|
|
2842
2874
|
}
|
|
2875
|
+
/** The registry-resolved Node endpoint and the identity returned by that exact Node. */
|
|
2876
|
+
async activeNodeIdentity() {
|
|
2877
|
+
const host = this.hosts[0];
|
|
2878
|
+
if (host === void 0) throw new Error("TinyCloud node host has not been resolved");
|
|
2879
|
+
const parsed = new URL(host);
|
|
2880
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) || parsed.origin !== host) {
|
|
2881
|
+
throw new Error("TinyCloud node host is not a canonical origin");
|
|
2882
|
+
}
|
|
2883
|
+
const nodeDid = await this.fetchNodeId();
|
|
2884
|
+
if (!nodeDid.startsWith("did:key:z")) throw new Error("TinyCloud node identity is not a did:key");
|
|
2885
|
+
return Object.freeze({ origin: parsed.origin, nodeDid });
|
|
2886
|
+
}
|
|
2887
|
+
/** Publish the active owner Node as a session-signed registry record. */
|
|
2888
|
+
async publishActiveNodeLocation(registryUrl, fetchFn = globalThis.fetch.bind(globalThis)) {
|
|
2889
|
+
const node = await this.activeNodeIdentity();
|
|
2890
|
+
return publishLocationRecord({
|
|
2891
|
+
registryUrl,
|
|
2892
|
+
subject: this.credentialHolderDid,
|
|
2893
|
+
nodeOrigin: node.origin,
|
|
2894
|
+
signer: { type: "did:key", signBytes: (bytes) => this.signSessionBytes(bytes) },
|
|
2895
|
+
fetch: fetchFn
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2843
2898
|
/**
|
|
2844
2899
|
* Get the primary identity DID for this user.
|
|
2845
2900
|
* - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
|
|
@@ -2881,16 +2936,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2881
2936
|
*/
|
|
2882
2937
|
getVerifiedSessionCapabilities() {
|
|
2883
2938
|
const session = this.currentTinyCloudSession();
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
).map((entry) =>
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2939
|
+
return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
|
|
2940
|
+
}
|
|
2941
|
+
/** Normalize signed capabilities without collapsing nested encryption into raw authority. */
|
|
2942
|
+
projectSignedRecapCapabilities(siwe) {
|
|
2943
|
+
const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
|
|
2944
|
+
const normalized = parseRecapCapabilities(() => [entry], siwe)[0];
|
|
2945
|
+
if (normalized.service !== "tinycloud.encryption") return normalized;
|
|
2946
|
+
if (!this.isEncryptionNetworkOperation("encryption", entry.path, entry.space)) {
|
|
2947
|
+
return { ...normalized, space: entry.space };
|
|
2948
|
+
}
|
|
2949
|
+
return {
|
|
2950
|
+
service: normalized.service,
|
|
2951
|
+
path: normalized.path,
|
|
2952
|
+
actions: normalized.actions,
|
|
2953
|
+
...normalized.caveats === void 0 ? {} : { caveats: normalized.caveats }
|
|
2954
|
+
};
|
|
2955
|
+
});
|
|
2956
|
+
return projected.sort(
|
|
2957
|
+
(left, right) => (left.space ?? "").localeCompare(right.space ?? "") || left.service.localeCompare(right.service) || left.path.localeCompare(right.path)
|
|
2958
|
+
);
|
|
2894
2959
|
}
|
|
2895
2960
|
/**
|
|
2896
2961
|
* Get the Ethereum address for this user.
|
|
@@ -3345,9 +3410,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3345
3410
|
* We must keep the full owner-scoped URI so a synthetic primary grant can
|
|
3346
3411
|
* never cover an operation on a different owner's space.
|
|
3347
3412
|
*
|
|
3348
|
-
*
|
|
3349
|
-
*
|
|
3350
|
-
*
|
|
3413
|
+
* Only top-level encryption network entries (`space: "encryption"` or
|
|
3414
|
+
* absent) become `resource` ops. A network-shaped path nested under a
|
|
3415
|
+
* data space remains a `spaceId` op, not raw network authority.
|
|
3351
3416
|
* One op per action.
|
|
3352
3417
|
*
|
|
3353
3418
|
* Returns `[]` for session-only / restored-without-siwe modes and for any
|
|
@@ -3368,7 +3433,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3368
3433
|
operations = entries.flatMap((entry) => {
|
|
3369
3434
|
const service = this.invocationServiceName(entry.service);
|
|
3370
3435
|
return entry.actions.map((action) => ({
|
|
3371
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
3436
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
|
|
3372
3437
|
service,
|
|
3373
3438
|
path: entry.path,
|
|
3374
3439
|
action,
|
|
@@ -3530,7 +3595,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3530
3595
|
return;
|
|
3531
3596
|
} catch (error) {
|
|
3532
3597
|
const message = error instanceof Error ? error.message : String(error);
|
|
3533
|
-
if (/Unauthorized Action|\
|
|
3598
|
+
if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
|
|
3534
3599
|
console.warn(
|
|
3535
3600
|
"TinyCloud account registry sync stopped: authorization verdict is not retryable",
|
|
3536
3601
|
error
|
|
@@ -3780,9 +3845,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3780
3845
|
*/
|
|
3781
3846
|
async restoreSession(sessionData) {
|
|
3782
3847
|
await this.wasmBindings.ensureInitialized?.();
|
|
3783
|
-
|
|
3848
|
+
let restoredJwk;
|
|
3849
|
+
try {
|
|
3850
|
+
restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
|
|
3851
|
+
} catch {
|
|
3852
|
+
throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
|
|
3853
|
+
}
|
|
3784
3854
|
if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
|
|
3785
|
-
throw new
|
|
3855
|
+
throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
|
|
3786
3856
|
}
|
|
3787
3857
|
const stagedManager = this.wasmBindings.createSessionManager();
|
|
3788
3858
|
const stagedReplace = stagedManager.replaceSessionKey;
|
|
@@ -3794,32 +3864,31 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3794
3864
|
let stagedKeyId;
|
|
3795
3865
|
let stagedJwk;
|
|
3796
3866
|
let canonicalVerificationMethod;
|
|
3867
|
+
const keyIds = liveKeys.call(this.sessionManager);
|
|
3868
|
+
if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
|
|
3869
|
+
throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
|
|
3870
|
+
}
|
|
3871
|
+
for (const keyId of keyIds) {
|
|
3872
|
+
if (keyId === this.sessionKeyId) continue;
|
|
3873
|
+
const jwk = this.sessionManager.jwk(keyId);
|
|
3874
|
+
if (!jwk) throw new Error("missing live session key");
|
|
3875
|
+
stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
|
|
3876
|
+
}
|
|
3797
3877
|
try {
|
|
3798
|
-
const keyIds = liveKeys.call(this.sessionManager);
|
|
3799
|
-
if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
|
|
3800
|
-
throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
|
|
3801
|
-
}
|
|
3802
|
-
for (const keyId of keyIds) {
|
|
3803
|
-
if (keyId === this.sessionKeyId) continue;
|
|
3804
|
-
const jwk = this.sessionManager.jwk(keyId);
|
|
3805
|
-
if (!jwk) throw new Error("missing live session key");
|
|
3806
|
-
stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
|
|
3807
|
-
}
|
|
3808
3878
|
stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
|
|
3812
|
-
canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
|
|
3813
|
-
} catch (error) {
|
|
3814
|
-
if (error instanceof UnsupportedSessionRestoreError) throw error;
|
|
3815
|
-
throw new Error("Persisted session has an invalid private Ed25519 session key.");
|
|
3879
|
+
} catch {
|
|
3880
|
+
throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
|
|
3816
3881
|
}
|
|
3882
|
+
const stagedJwkJson = stagedManager.jwk(stagedKeyId);
|
|
3883
|
+
if (!stagedJwkJson) throw new Error("missing restored session key");
|
|
3884
|
+
stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
|
|
3885
|
+
canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
|
|
3817
3886
|
const restoredVerificationMethod = canonicalRestoredVerificationMethod(
|
|
3818
3887
|
canonicalVerificationMethod,
|
|
3819
3888
|
sessionData.verificationMethod
|
|
3820
3889
|
);
|
|
3821
3890
|
if (!restoredVerificationMethod) {
|
|
3822
|
-
throw new
|
|
3891
|
+
throw new InvalidRestoredSessionError(
|
|
3823
3892
|
"Persisted session verification method does not match its private Ed25519 session key."
|
|
3824
3893
|
);
|
|
3825
3894
|
}
|
|
@@ -3831,26 +3900,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3831
3900
|
];
|
|
3832
3901
|
const hasPersistedProof = proofValues.every((value) => value !== void 0);
|
|
3833
3902
|
if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
|
|
3834
|
-
throw new
|
|
3903
|
+
throw new InvalidRestoredSessionError("Persisted session authority metadata is incomplete.");
|
|
3904
|
+
}
|
|
3905
|
+
let restoredAddress;
|
|
3906
|
+
try {
|
|
3907
|
+
restoredAddress = hasPersistedProof ? canonicalizeAddress2(sessionData.address) : void 0;
|
|
3908
|
+
} catch {
|
|
3909
|
+
throw new InvalidRestoredSessionError("Persisted session has an invalid wallet address.");
|
|
3835
3910
|
}
|
|
3836
|
-
const restoredAddress = hasPersistedProof ? canonicalizeAddress2(sessionData.address) : void 0;
|
|
3837
3911
|
let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
|
|
3838
3912
|
let stagedRecap = [];
|
|
3839
3913
|
if (hasPersistedProof) {
|
|
3840
3914
|
if (typeof this.wasmBindings.validatePersistedSession !== "function") {
|
|
3841
3915
|
throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
|
|
3842
3916
|
}
|
|
3843
|
-
const verified =
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3917
|
+
const verified = (() => {
|
|
3918
|
+
try {
|
|
3919
|
+
return this.wasmBindings.validatePersistedSession({
|
|
3920
|
+
delegationHeader: sessionData.delegationHeader,
|
|
3921
|
+
delegationCid: sessionData.delegationCid,
|
|
3922
|
+
spaceId: sessionData.spaceId,
|
|
3923
|
+
jwk: stagedJwk,
|
|
3924
|
+
address: restoredAddress,
|
|
3925
|
+
chainId: sessionData.chainId,
|
|
3926
|
+
siwe: sessionData.siwe,
|
|
3927
|
+
signature: sessionData.signature
|
|
3928
|
+
});
|
|
3929
|
+
} catch (error) {
|
|
3930
|
+
throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
|
|
3931
|
+
}
|
|
3932
|
+
})();
|
|
3933
|
+
const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
|
|
3854
3934
|
if (!Array.isArray(exactRecap) || !exactRecap.every(
|
|
3855
3935
|
(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(
|
|
3856
3936
|
(caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
|
|
@@ -3870,7 +3950,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3870
3950
|
const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
|
|
3871
3951
|
const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
|
|
3872
3952
|
if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
|
|
3873
|
-
throw new
|
|
3953
|
+
throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
|
|
3874
3954
|
}
|
|
3875
3955
|
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
3876
3956
|
}
|
|
@@ -3967,7 +4047,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3967
4047
|
operations = recap.flatMap((entry) => {
|
|
3968
4048
|
const service = this.invocationServiceName(entry.service);
|
|
3969
4049
|
return entry.actions.map((action) => ({
|
|
3970
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
4050
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
|
|
3971
4051
|
service,
|
|
3972
4052
|
path: entry.path,
|
|
3973
4053
|
action,
|
|
@@ -4063,7 +4143,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4063
4143
|
const delegations = input.recap.map((entry) => ({
|
|
4064
4144
|
cid: session.delegationCid,
|
|
4065
4145
|
delegateDID: session.verificationMethod,
|
|
4066
|
-
spaceId: entry.space,
|
|
4146
|
+
spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
|
|
4067
4147
|
path: entry.path,
|
|
4068
4148
|
actions: [...entry.actions],
|
|
4069
4149
|
caveats: cloneRecapCaveats(entry.caveats),
|
|
@@ -4753,16 +4833,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4753
4833
|
);
|
|
4754
4834
|
graph.assertActive();
|
|
4755
4835
|
if (!response.ok) {
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
|
|
4762
|
-
permissionHint = parsePermissionHint(record?.permissionHint) ?? parsePermissionHint(nested?.permissionHint);
|
|
4763
|
-
} catch {
|
|
4764
|
-
}
|
|
4765
|
-
}
|
|
4836
|
+
const permissionHint = response.status === 401 || response.status === 403 ? parsePermissionHint({
|
|
4837
|
+
service: "tinycloud.encryption",
|
|
4838
|
+
path: networkId,
|
|
4839
|
+
actions: ["tinycloud.encryption/decrypt"]
|
|
4840
|
+
}) : void 0;
|
|
4766
4841
|
throw new DecryptTransportResponseError(response.status, permissionHint);
|
|
4767
4842
|
}
|
|
4768
4843
|
return await response.json();
|
|
@@ -5187,63 +5262,62 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5187
5262
|
}
|
|
5188
5263
|
};
|
|
5189
5264
|
}
|
|
5190
|
-
/** Register a policy
|
|
5191
|
-
async
|
|
5265
|
+
/** Register a signed policy and sibling roots with this Node's Policy/v3 runtime. */
|
|
5266
|
+
async registerPolicy(params) {
|
|
5192
5267
|
this._serviceGraph.assertActive();
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
body: JSON.stringify({
|
|
5198
|
-
policy: { bytes: base64UrlEncode(params.policy.bytes), cid: params.policy.cid, proof: params.policy.proof },
|
|
5199
|
-
ownerDelegation: { cid: params.ownerDelegation.delegationCid, dagCbor: base64UrlEncode(params.ownerDelegation.signedDagCbor) },
|
|
5200
|
-
enforcementDelegation: params.enforcementDelegation,
|
|
5201
|
-
contentSourceDigest: params.contentSourceDigest
|
|
5202
|
-
})
|
|
5268
|
+
return registerPolicyV3({
|
|
5269
|
+
...params,
|
|
5270
|
+
nodeOrigin: this.config.host,
|
|
5271
|
+
fetch: globalThis.fetch.bind(globalThis)
|
|
5203
5272
|
});
|
|
5204
|
-
if (!response.ok) {
|
|
5205
|
-
let code = "";
|
|
5206
|
-
try {
|
|
5207
|
-
const body = await response.clone().json();
|
|
5208
|
-
if (typeof body.error?.code === "string") code = ` ${body.error.code}`;
|
|
5209
|
-
} catch {
|
|
5210
|
-
}
|
|
5211
|
-
throw new Error(`Owner share policy registration failed: ${response.status}${code}`);
|
|
5212
|
-
}
|
|
5213
|
-
const responseBytes = new Uint8Array(await response.arrayBuffer());
|
|
5214
|
-
return validateOwnerSharePolicyRegistrationBytes(responseBytes, params);
|
|
5215
5273
|
}
|
|
5216
|
-
/** Authorize
|
|
5217
|
-
async
|
|
5274
|
+
/** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
|
|
5275
|
+
async authorizeShareDeliveryV3(input) {
|
|
5218
5276
|
const session = this.currentTinyCloudSession();
|
|
5219
5277
|
const serviceSession = this._serviceContext?.session;
|
|
5220
5278
|
if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
|
|
5279
|
+
const idempotencyBytes = new TextEncoder().encode(input.idempotencyKey);
|
|
5280
|
+
if (idempotencyBytes.byteLength === 0 || idempotencyBytes.byteLength > 512) {
|
|
5281
|
+
throw new Error("Share delivery idempotency key is invalid");
|
|
5282
|
+
}
|
|
5283
|
+
const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
|
|
5221
5284
|
const body = {
|
|
5222
|
-
|
|
5285
|
+
envelope: input.envelope,
|
|
5286
|
+
sealedEnvelope: input.sealedEnvelope,
|
|
5287
|
+
envelopeKey: input.envelopeKey,
|
|
5223
5288
|
shareCid: input.shareCid,
|
|
5224
|
-
shareId: input.shareId,
|
|
5225
|
-
registrationCid: input.registrationCid,
|
|
5226
|
-
policyCid: input.policyCid,
|
|
5227
|
-
delegationCid: input.delegationCid,
|
|
5228
|
-
enforcementDelegationCid: input.enforcementDelegationCid,
|
|
5229
5289
|
recipientEmail: input.recipientEmail,
|
|
5230
5290
|
shareUrl: input.shareUrl,
|
|
5231
5291
|
documentName: input.documentName,
|
|
5232
|
-
jti: base64UrlEncode(
|
|
5233
|
-
|
|
5234
|
-
expiresAt: input.expiresAt
|
|
5292
|
+
jti: base64UrlEncode(jtiDigest.slice(0, 16)),
|
|
5293
|
+
expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
5235
5294
|
};
|
|
5236
5295
|
const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalizeEncryptionJson(body)))));
|
|
5237
5296
|
const request = { ...body, requestBodyDigest };
|
|
5238
5297
|
const authorization = authorizationHeader(this.invokeAnyWithRuntimePermissions(serviceSession, [{ spaceId: session.spaceId, service: "kv", path: input.resourcePath, action: "tinycloud.kv/get" }]));
|
|
5239
|
-
const response = await fetch(`${this.config.host}/
|
|
5298
|
+
const response = await fetch(`${this.config.host}/policy/v3/deliveries/authorize`, {
|
|
5240
5299
|
method: "POST",
|
|
5241
5300
|
headers: { accept: "application/json", "content-type": "application/json", authorization },
|
|
5242
5301
|
body: canonicalizeEncryptionJson(request)
|
|
5243
5302
|
});
|
|
5244
|
-
if (!response.ok) throw new Error(`
|
|
5245
|
-
const
|
|
5246
|
-
|
|
5303
|
+
if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
|
|
5304
|
+
const verified = validateShareDeliveryAuthorizationV3Bytes(new Uint8Array(await response.arrayBuffer()), {
|
|
5305
|
+
request,
|
|
5306
|
+
senderKeyDid: this.credentialHolderDid,
|
|
5307
|
+
deliveryAudience: input.deliveryAudience
|
|
5308
|
+
});
|
|
5309
|
+
const requestDigest = new Uint8Array(await crypto.subtle.digest(
|
|
5310
|
+
"SHA-256",
|
|
5311
|
+
new TextEncoder().encode(`${CREDENTIAL_INVITATION_REQUEST_DOMAIN}${canonicalizeEncryptionJson(verified.request)}`)
|
|
5312
|
+
));
|
|
5313
|
+
return {
|
|
5314
|
+
...verified,
|
|
5315
|
+
proof: {
|
|
5316
|
+
alg: "EdDSA",
|
|
5317
|
+
kid: this.credentialHolderDid,
|
|
5318
|
+
signature: base64UrlEncode(await this.signSessionBytes(requestDigest))
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
5247
5321
|
}
|
|
5248
5322
|
async createRootDelegationForSharing(params, assertActive) {
|
|
5249
5323
|
try {
|
|
@@ -5698,7 +5772,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5698
5772
|
continue;
|
|
5699
5773
|
}
|
|
5700
5774
|
let space;
|
|
5701
|
-
if (service !== "tinycloud.encryption") {
|
|
5775
|
+
if (service !== "tinycloud.encryption" || operation.resource === void 0) {
|
|
5702
5776
|
if (typeof operation.spaceId !== "string") continue;
|
|
5703
5777
|
try {
|
|
5704
5778
|
space = this.resolvePermissionSpace(operation.spaceId, session);
|
|
@@ -6243,11 +6317,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6243
6317
|
);
|
|
6244
6318
|
return { delegation: delegation2, prompted: true };
|
|
6245
6319
|
}
|
|
6246
|
-
const granted =
|
|
6247
|
-
|
|
6248
|
-
session.siwe
|
|
6249
|
-
);
|
|
6250
|
-
const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
|
|
6320
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
6321
|
+
const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
|
|
6251
6322
|
if (!subset) {
|
|
6252
6323
|
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
6253
6324
|
const runtimeGrant = this.findGrantForOperations(
|
|
@@ -6567,7 +6638,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6567
6638
|
return abilities;
|
|
6568
6639
|
}
|
|
6569
6640
|
isEncryptionPermissionEntry(entry) {
|
|
6570
|
-
return entry.service === ENCRYPTION_PERMISSION_SERVICE2 &&
|
|
6641
|
+
return entry.service === ENCRYPTION_PERMISSION_SERVICE2 && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
|
|
6571
6642
|
}
|
|
6572
6643
|
permissionsToRawAbilities(entries) {
|
|
6573
6644
|
const rawAbilities = {};
|
|
@@ -6591,7 +6662,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6591
6662
|
return entries.flatMap((entry) => {
|
|
6592
6663
|
const service = this.shortServiceName(entry.service);
|
|
6593
6664
|
return entry.actions.map((action) => ({
|
|
6594
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
6665
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
|
|
6595
6666
|
service,
|
|
6596
6667
|
path: entry.path,
|
|
6597
6668
|
action,
|
|
@@ -6599,13 +6670,21 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6599
6670
|
}));
|
|
6600
6671
|
});
|
|
6601
6672
|
}
|
|
6673
|
+
/** Raw network authority cannot be covered by a nested encryption space. */
|
|
6674
|
+
signedCapabilitySubset(requested, granted) {
|
|
6675
|
+
const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
|
|
6676
|
+
const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
|
|
6677
|
+
const missing = requested.filter((entry) => {
|
|
6678
|
+
const raw = this.isEncryptionPermissionEntry(entry);
|
|
6679
|
+
const comparable = raw ? { ...entry, space: "encryption" } : entry;
|
|
6680
|
+
return !isCapabilitySubset([comparable], raw ? rawGranted : spaceGranted).subset;
|
|
6681
|
+
});
|
|
6682
|
+
return { subset: missing.length === 0, missing };
|
|
6683
|
+
}
|
|
6602
6684
|
sessionCoversPermissionEntries(session, entries) {
|
|
6603
6685
|
try {
|
|
6604
|
-
const granted =
|
|
6605
|
-
|
|
6606
|
-
session.siwe
|
|
6607
|
-
);
|
|
6608
|
-
return isCapabilitySubset(entries, granted).subset;
|
|
6686
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
6687
|
+
return this.signedCapabilitySubset(entries, granted).subset;
|
|
6609
6688
|
} catch {
|
|
6610
6689
|
return false;
|
|
6611
6690
|
}
|
|
@@ -6615,7 +6694,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6615
6694
|
const spaceId = this.resolvePermissionSpace(entry.space, session);
|
|
6616
6695
|
const service = this.shortServiceName(entry.service);
|
|
6617
6696
|
return entry.actions.map((action) => ({
|
|
6618
|
-
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
6697
|
+
...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
|
|
6619
6698
|
service,
|
|
6620
6699
|
path: entry.path,
|
|
6621
6700
|
action,
|
|
@@ -6704,7 +6783,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6704
6783
|
return resources.flatMap((resource) => {
|
|
6705
6784
|
const service = this.invocationServiceName(resource.service);
|
|
6706
6785
|
return resource.actions.map((action) => ({
|
|
6707
|
-
...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
|
|
6786
|
+
...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
|
|
6708
6787
|
service,
|
|
6709
6788
|
path: resource.path,
|
|
6710
6789
|
action,
|
|
@@ -6765,7 +6844,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6765
6844
|
};
|
|
6766
6845
|
for (const resource of resources) {
|
|
6767
6846
|
const service = this.invocationServiceName(resource.service);
|
|
6768
|
-
if (this.isEncryptionNetworkOperation(service, resource.path)) {
|
|
6847
|
+
if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
|
|
6769
6848
|
rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
|
|
6770
6849
|
addActions(rawAbilities[resource.path], resource.actions);
|
|
6771
6850
|
continue;
|
|
@@ -6834,21 +6913,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6834
6913
|
}
|
|
6835
6914
|
return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
|
|
6836
6915
|
}
|
|
6837
|
-
// Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`.
|
|
6838
|
-
// EIP-155 address
|
|
6839
|
-
//
|
|
6840
|
-
// the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
|
|
6841
|
-
// an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
|
|
6842
|
-
// segment and leave everything else (crucially the case-sensitive space NAME)
|
|
6843
|
-
// byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
|
|
6916
|
+
// Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. Compare
|
|
6917
|
+
// only the embedded EIP-155 address without case sensitivity; chain, name,
|
|
6918
|
+
// and all non-PKH space identifiers remain byte-exact.
|
|
6844
6919
|
spaceIdsEqual(a, b) {
|
|
6845
6920
|
return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
|
|
6846
6921
|
}
|
|
6847
6922
|
normalizeSpaceAddress(space) {
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
(_match, prefix, addr) => prefix + addr.toLowerCase()
|
|
6851
|
-
);
|
|
6923
|
+
const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
|
|
6924
|
+
return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
|
|
6852
6925
|
}
|
|
6853
6926
|
actionContains(grantedAction, requestedAction) {
|
|
6854
6927
|
if (grantedAction === requestedAction) {
|
|
@@ -6865,10 +6938,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6865
6938
|
}
|
|
6866
6939
|
/** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
|
|
6867
6940
|
parseRecapWithCaveats(siwe) {
|
|
6868
|
-
|
|
6941
|
+
const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
|
|
6942
|
+
return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
|
|
6869
6943
|
}
|
|
6870
|
-
isEncryptionNetworkOperation(service, path) {
|
|
6871
|
-
return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
|
|
6944
|
+
isEncryptionNetworkOperation(service, path, space) {
|
|
6945
|
+
return service === "encryption" && (space === void 0 || space === "encryption") && path.startsWith("urn:tinycloud:encryption:");
|
|
6872
6946
|
}
|
|
6873
6947
|
operationFromInvokeAnyEntry(entry) {
|
|
6874
6948
|
const service = this.invocationServiceName(entry.service);
|
|
@@ -6881,7 +6955,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6881
6955
|
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
6882
6956
|
};
|
|
6883
6957
|
}
|
|
6884
|
-
if (this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
6958
|
+
if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
6885
6959
|
return {
|
|
6886
6960
|
resource: entry.path,
|
|
6887
6961
|
service,
|
|
@@ -7166,8 +7240,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7166
7240
|
const jwk = mySession.jwk;
|
|
7167
7241
|
const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
|
|
7168
7242
|
const now = /* @__PURE__ */ new Date();
|
|
7169
|
-
const
|
|
7170
|
-
const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
|
|
7243
|
+
const expirationTime = delegation.expiry;
|
|
7171
7244
|
const prepared = this.wasmBindings.prepareSession({
|
|
7172
7245
|
abilities,
|
|
7173
7246
|
address: this.wasmBindings.ensureEip55(mySession.address),
|
|
@@ -7271,8 +7344,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7271
7344
|
);
|
|
7272
7345
|
}
|
|
7273
7346
|
const now = /* @__PURE__ */ new Date();
|
|
7274
|
-
const
|
|
7275
|
-
const requestedExpiry = new Date(now.getTime() + expiryMs);
|
|
7347
|
+
const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
|
|
7276
7348
|
const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
|
|
7277
7349
|
const abilities = {};
|
|
7278
7350
|
const rawAbilities = {};
|
|
@@ -7740,6 +7812,7 @@ import {
|
|
|
7740
7812
|
SecretsService as SecretsService2,
|
|
7741
7813
|
SECRET_NAME_RE,
|
|
7742
7814
|
canonicalizeSecretScope,
|
|
7815
|
+
parseSecretCatalogKey,
|
|
7743
7816
|
resolveSecretListPrefix as resolveSecretListPrefix2,
|
|
7744
7817
|
resolveSecretPath as resolveSecretPath3
|
|
7745
7818
|
} from "@tinycloud/sdk-core";
|
|
@@ -7791,6 +7864,7 @@ export {
|
|
|
7791
7864
|
DuckDbService3 as DuckDbService,
|
|
7792
7865
|
FileSessionStorage,
|
|
7793
7866
|
IdentityParseError,
|
|
7867
|
+
InvalidRestoredSessionError,
|
|
7794
7868
|
KVService3 as KVService,
|
|
7795
7869
|
ManifestValidationError,
|
|
7796
7870
|
MemorySessionStorage,
|
|
@@ -7856,6 +7930,7 @@ export {
|
|
|
7856
7930
|
parseCanonicalNetworkId,
|
|
7857
7931
|
parseExpiry2 as parseExpiry,
|
|
7858
7932
|
parsePkhDid,
|
|
7933
|
+
parseSecretCatalogKey,
|
|
7859
7934
|
parseSpaceUri,
|
|
7860
7935
|
pkhDid3 as pkhDid,
|
|
7861
7936
|
principalDid,
|