@tinycloud/node-sdk 2.7.0-beta.3 → 2.7.0-beta.4
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-DGFXEW74.d.cts → core-D_KNrE_y.d.cts} +151 -6
- package/dist/{core-DGFXEW74.d.ts → core-D_KNrE_y.d.ts} +151 -6
- package/dist/core.cjs +1150 -260
- 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 +1081 -181
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1468 -301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +1359 -181
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/core.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
parsePkhDid,
|
|
18
18
|
pkhDid as pkhDid3,
|
|
19
19
|
principalDid,
|
|
20
|
-
principalDidEquals as
|
|
20
|
+
principalDidEquals as principalDidEquals4,
|
|
21
21
|
parseCanonicalNetworkId,
|
|
22
22
|
TinyCloudDebugLogger,
|
|
23
23
|
tinyCloudDebugLogger,
|
|
@@ -391,6 +391,30 @@ var NodeUserAuthorization = class {
|
|
|
391
391
|
this.tinycloudHosts = [...hosts];
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
|
+
/** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
|
|
395
|
+
installRestoredSession(sessionManager, session, hosts) {
|
|
396
|
+
this.sessionManager = sessionManager;
|
|
397
|
+
this.tinycloudHosts = hosts && hosts.length > 0 ? [...hosts] : void 0;
|
|
398
|
+
this._lastActivationSkippedSpaceIds = [];
|
|
399
|
+
if (!session) {
|
|
400
|
+
this._session = void 0;
|
|
401
|
+
this._tinyCloudSession = void 0;
|
|
402
|
+
this._address = void 0;
|
|
403
|
+
this._chainId = void 0;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
this._tinyCloudSession = { ...session };
|
|
407
|
+
this._address = session.address;
|
|
408
|
+
this._chainId = session.chainId;
|
|
409
|
+
this._session = {
|
|
410
|
+
address: session.address,
|
|
411
|
+
walletAddress: session.address,
|
|
412
|
+
chainId: session.chainId,
|
|
413
|
+
sessionKey: session.sessionKey,
|
|
414
|
+
siwe: session.siwe,
|
|
415
|
+
signature: session.signature
|
|
416
|
+
};
|
|
417
|
+
}
|
|
394
418
|
/**
|
|
395
419
|
* Ensure `tinycloudHosts` are resolved before a host-needing call.
|
|
396
420
|
*
|
|
@@ -1171,6 +1195,7 @@ import {
|
|
|
1171
1195
|
HooksService as HooksService2,
|
|
1172
1196
|
DataVaultService,
|
|
1173
1197
|
EncryptionService,
|
|
1198
|
+
DecryptTransportResponseError,
|
|
1174
1199
|
SecretsService,
|
|
1175
1200
|
createVaultCrypto,
|
|
1176
1201
|
ServiceContext as ServiceContext2,
|
|
@@ -1188,12 +1213,16 @@ import {
|
|
|
1188
1213
|
TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
|
|
1189
1214
|
bootstrapSteps,
|
|
1190
1215
|
ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
|
|
1216
|
+
CaveatedDelegationUnsupportedError,
|
|
1191
1217
|
PermissionNotInManifestError,
|
|
1192
1218
|
SessionExpiredError,
|
|
1219
|
+
canonicalizeRecapCaveats,
|
|
1220
|
+
recapCaveatsEqual,
|
|
1193
1221
|
expandPermissionEntries as expandPermissionEntriesCore,
|
|
1194
1222
|
isCapabilitySubset,
|
|
1195
1223
|
parseRecapCapabilities,
|
|
1196
1224
|
SERVICE_LONG_TO_SHORT,
|
|
1225
|
+
SERVICE_SHORT_TO_LONG,
|
|
1197
1226
|
KV as KV2,
|
|
1198
1227
|
SQL as SQL2,
|
|
1199
1228
|
DUCKDB as DUCKDB2,
|
|
@@ -1206,8 +1235,12 @@ import {
|
|
|
1206
1235
|
pkhDid as pkhDid2,
|
|
1207
1236
|
resolveTinyCloudHosts as resolveTinyCloudHosts2,
|
|
1208
1237
|
principalDidEquals as principalDidEquals2,
|
|
1209
|
-
parseNetworkId as parseNetworkId2
|
|
1238
|
+
parseNetworkId as parseNetworkId2,
|
|
1239
|
+
resolveSecretPath as resolveSecretPath2
|
|
1210
1240
|
} from "@tinycloud/sdk-core";
|
|
1241
|
+
import {
|
|
1242
|
+
parsePermissionHint
|
|
1243
|
+
} from "@tinycloud/sdk-services";
|
|
1211
1244
|
|
|
1212
1245
|
// src/account/AccountService.ts
|
|
1213
1246
|
import { AccountService } from "@tinycloud/sdk-core";
|
|
@@ -1646,6 +1679,9 @@ var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
|
|
|
1646
1679
|
var NETWORK_CREATE_ACTION2 = ENCRYPTION2.NETWORK_CREATE;
|
|
1647
1680
|
var DECRYPT_ACTION2 = ENCRYPTION2.DECRYPT;
|
|
1648
1681
|
var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
|
|
1682
|
+
function isSecretPayload(value) {
|
|
1683
|
+
return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "createdAt" in value && typeof value.createdAt === "string" && "updatedAt" in value && typeof value.updatedAt === "string";
|
|
1684
|
+
}
|
|
1649
1685
|
var ROOT_DELEGATION_ACTIONS = [
|
|
1650
1686
|
KV2.PUT,
|
|
1651
1687
|
KV2.GET,
|
|
@@ -1700,6 +1736,141 @@ function didPrincipalMatches2(actual, expected) {
|
|
|
1700
1736
|
return actual === expected;
|
|
1701
1737
|
}
|
|
1702
1738
|
}
|
|
1739
|
+
function clonePersistedSessionJwk(jwk) {
|
|
1740
|
+
if (jwk === null || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
1741
|
+
throw new Error("Persisted session has an invalid private Ed25519 session key.");
|
|
1742
|
+
}
|
|
1743
|
+
try {
|
|
1744
|
+
const serialized = JSON.stringify(jwk);
|
|
1745
|
+
const cloned = serialized === void 0 ? void 0 : JSON.parse(serialized);
|
|
1746
|
+
if (cloned === null || typeof cloned !== "object" || Array.isArray(cloned)) {
|
|
1747
|
+
throw new Error("invalid JWK object");
|
|
1748
|
+
}
|
|
1749
|
+
return cloned;
|
|
1750
|
+
} catch {
|
|
1751
|
+
throw new Error("Persisted session has an invalid private Ed25519 session key.");
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
function cloneRecapCaveats(caveats) {
|
|
1755
|
+
if (!caveats) return [];
|
|
1756
|
+
const cloneJson = (value) => {
|
|
1757
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
1758
|
+
return value;
|
|
1759
|
+
}
|
|
1760
|
+
if (typeof value === "number") {
|
|
1761
|
+
if (Number.isFinite(value)) return value;
|
|
1762
|
+
throw new Error("ReCap caveats must contain only JSON values.");
|
|
1763
|
+
}
|
|
1764
|
+
if (Array.isArray(value)) return value.map(cloneJson);
|
|
1765
|
+
if (typeof value === "object") {
|
|
1766
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1767
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
1768
|
+
throw new Error("ReCap caveats must contain only JSON values.");
|
|
1769
|
+
}
|
|
1770
|
+
const copy = /* @__PURE__ */ Object.create(null);
|
|
1771
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
1772
|
+
copy[key] = cloneJson(nested);
|
|
1773
|
+
}
|
|
1774
|
+
return copy;
|
|
1775
|
+
}
|
|
1776
|
+
throw new Error("ReCap caveats must contain only JSON values.");
|
|
1777
|
+
};
|
|
1778
|
+
try {
|
|
1779
|
+
return JSON.parse(JSON.stringify(caveats.map(cloneJson)));
|
|
1780
|
+
} catch {
|
|
1781
|
+
throw new Error("Verified persisted session ReCap contains invalid caveats.");
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
var ServiceGraphLifetime = class {
|
|
1785
|
+
constructor(invokeFn, invokeAnyFn, fetchFn) {
|
|
1786
|
+
this.invokeFn = invokeFn;
|
|
1787
|
+
this.invokeAnyFn = invokeAnyFn;
|
|
1788
|
+
this.fetchFn = fetchFn;
|
|
1789
|
+
this.abortController = new AbortController();
|
|
1790
|
+
this.contexts = /* @__PURE__ */ new Set();
|
|
1791
|
+
this.retired = false;
|
|
1792
|
+
this.invoke = (session, service, path, action, facts) => {
|
|
1793
|
+
this.assertActive();
|
|
1794
|
+
return this.invokeFn(session, service, path, action, facts);
|
|
1795
|
+
};
|
|
1796
|
+
this.invokeAny = (session, entries, facts) => {
|
|
1797
|
+
this.assertActive();
|
|
1798
|
+
return this.invokeAnyFn(session, entries, facts);
|
|
1799
|
+
};
|
|
1800
|
+
this.fetch = (url, init) => {
|
|
1801
|
+
this.assertActive();
|
|
1802
|
+
return this.fetchFn(url, {
|
|
1803
|
+
...init,
|
|
1804
|
+
signal: this.combineSignals(init?.signal)
|
|
1805
|
+
});
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
track(context) {
|
|
1809
|
+
this.assertActive();
|
|
1810
|
+
this.contexts.add(context);
|
|
1811
|
+
return context;
|
|
1812
|
+
}
|
|
1813
|
+
retire() {
|
|
1814
|
+
if (this.retired) return;
|
|
1815
|
+
this.retired = true;
|
|
1816
|
+
this.abortController.abort();
|
|
1817
|
+
try {
|
|
1818
|
+
for (const context of this.contexts) {
|
|
1819
|
+
try {
|
|
1820
|
+
const retire = context.retire;
|
|
1821
|
+
if (retire) retire.call(context);
|
|
1822
|
+
else context.abort();
|
|
1823
|
+
} catch (error) {
|
|
1824
|
+
console.error("Error retiring service graph context:", error);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
} finally {
|
|
1828
|
+
this.contexts.clear();
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
assertActive() {
|
|
1832
|
+
if (this.retired) {
|
|
1833
|
+
throw new Error("Service graph has been retired by session replacement.");
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
combineSignals(signal) {
|
|
1837
|
+
if (!signal) return this.abortController.signal;
|
|
1838
|
+
const combined = new AbortController();
|
|
1839
|
+
const abort = () => combined.abort();
|
|
1840
|
+
if (signal.aborted || this.abortController.signal.aborted) {
|
|
1841
|
+
abort();
|
|
1842
|
+
} else {
|
|
1843
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1844
|
+
this.abortController.signal.addEventListener("abort", abort, { once: true });
|
|
1845
|
+
}
|
|
1846
|
+
return combined.signal;
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
var UnsupportedSessionRestoreError = class extends Error {
|
|
1850
|
+
constructor(reason = "it cannot replace and enumerate every live session signer") {
|
|
1851
|
+
super(`Persisted session restore is unsupported by this WASM binding because ${reason}.`);
|
|
1852
|
+
this.code = "RESTORE_SESSION_KEY_REPLACEMENT_UNSUPPORTED";
|
|
1853
|
+
this.name = "UnsupportedSessionRestoreError";
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
|
|
1857
|
+
if (typeof persistedVerificationMethod !== "string") return void 0;
|
|
1858
|
+
const principal = canonicalVerificationMethod.split("#", 1)[0];
|
|
1859
|
+
return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
|
|
1860
|
+
}
|
|
1861
|
+
function persistedExpiry(value) {
|
|
1862
|
+
if (typeof value !== "string") {
|
|
1863
|
+
throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
|
|
1864
|
+
}
|
|
1865
|
+
const expiry = new Date(value);
|
|
1866
|
+
if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
|
|
1867
|
+
throw new Error("Persisted session expiry is invalid or expired.");
|
|
1868
|
+
}
|
|
1869
|
+
return expiry;
|
|
1870
|
+
}
|
|
1871
|
+
function sameInstant(left, right) {
|
|
1872
|
+
return left.getTime() === right.getTime();
|
|
1873
|
+
}
|
|
1703
1874
|
function sharingActionsToAbilities(path, actions) {
|
|
1704
1875
|
var _a;
|
|
1705
1876
|
const abilities = {};
|
|
@@ -1826,6 +1997,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1826
1997
|
this.auth = null;
|
|
1827
1998
|
this.tc = null;
|
|
1828
1999
|
this._chainId = 1;
|
|
2000
|
+
this._baseVaults = /* @__PURE__ */ new Map();
|
|
1829
2001
|
this._baseSecrets = /* @__PURE__ */ new Map();
|
|
1830
2002
|
this._secrets = /* @__PURE__ */ new Map();
|
|
1831
2003
|
this.runtimePermissionGrants = [];
|
|
@@ -1845,26 +2017,55 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1845
2017
|
skipped: false
|
|
1846
2018
|
};
|
|
1847
2019
|
this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
service,
|
|
2020
|
+
const operation = {
|
|
2021
|
+
spaceId: session.spaceId,
|
|
2022
|
+
service: this.invocationServiceName(service),
|
|
1851
2023
|
path,
|
|
1852
|
-
action
|
|
1853
|
-
|
|
2024
|
+
action
|
|
2025
|
+
};
|
|
2026
|
+
const grant = this.findGrantForOperation(operation);
|
|
2027
|
+
const grantedOperation = grant?.operations.find(
|
|
2028
|
+
(candidate) => this.operationCovers(candidate, operation)
|
|
1854
2029
|
);
|
|
2030
|
+
const invocationSession = !grant || grant.provenance === "primary" ? session : grant.session;
|
|
2031
|
+
if ((grantedOperation?.caveats?.length ?? 0) > 0) {
|
|
2032
|
+
if (!this.wasmBindings.invokeAny) {
|
|
2033
|
+
throw new Error("WASM binding does not support caveat-preserving invocation");
|
|
2034
|
+
}
|
|
2035
|
+
return this.wasmBindings.invokeAny(invocationSession, [{
|
|
2036
|
+
spaceId: invocationSession.spaceId,
|
|
2037
|
+
service,
|
|
2038
|
+
path,
|
|
2039
|
+
action,
|
|
2040
|
+
caveats: cloneRecapCaveats(grantedOperation.caveats)
|
|
2041
|
+
}], facts);
|
|
2042
|
+
}
|
|
2043
|
+
return this.wasmBindings.invoke(invocationSession, service, path, action, facts);
|
|
1855
2044
|
};
|
|
1856
2045
|
this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
|
|
1857
2046
|
if (!this.wasmBindings.invokeAny) {
|
|
1858
2047
|
throw new Error("WASM binding does not support invokeAny");
|
|
1859
2048
|
}
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
);
|
|
2049
|
+
const operations = entries.flatMap((entry) => {
|
|
2050
|
+
const operation = this.operationFromInvokeAnyEntry(entry);
|
|
2051
|
+
return operation ? [operation] : [];
|
|
2052
|
+
});
|
|
2053
|
+
const grant = this.findGrantForOperations(operations);
|
|
1866
2054
|
const invocationSession = !grant || grant.provenance === "primary" ? session : grant.session;
|
|
1867
|
-
|
|
2055
|
+
const caveatPreservingEntries = grant ? entries.map((entry) => {
|
|
2056
|
+
const requested = this.operationFromInvokeAnyEntry(entry);
|
|
2057
|
+
const granted = requested && grant.operations.find(
|
|
2058
|
+
(candidate) => this.operationCovers(candidate, requested)
|
|
2059
|
+
);
|
|
2060
|
+
if (!granted?.caveats?.length) {
|
|
2061
|
+
return entry;
|
|
2062
|
+
}
|
|
2063
|
+
if (entry.caveats !== void 0 && !recapCaveatsEqual(entry.caveats, granted.caveats)) {
|
|
2064
|
+
throw new Error("Invocation caveats do not match signed ReCap authority.");
|
|
2065
|
+
}
|
|
2066
|
+
return { ...entry, caveats: cloneRecapCaveats(granted.caveats) };
|
|
2067
|
+
}) : entries;
|
|
2068
|
+
return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
|
|
1868
2069
|
};
|
|
1869
2070
|
this.explicitHost = config.host;
|
|
1870
2071
|
this.config = {
|
|
@@ -1893,16 +2094,19 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1893
2094
|
throw new Error("Failed to get session key JWK");
|
|
1894
2095
|
}
|
|
1895
2096
|
this.sessionKeyJwk = JSON.parse(jwkStr);
|
|
2097
|
+
this._serviceGraph = this.createServiceGraphLifetime();
|
|
1896
2098
|
this._capabilityRegistry = new CapabilityKeyRegistry();
|
|
1897
2099
|
this._keyProvider = new WasmKeyProvider({
|
|
1898
2100
|
sessionManager: this.sessionManager
|
|
1899
2101
|
});
|
|
1900
2102
|
this.notificationHandler = config.notificationHandler ?? new SilentNotificationHandler();
|
|
2103
|
+
const receiveOnlyGraph = this._serviceGraph;
|
|
1901
2104
|
this._sharingService = new SharingService({
|
|
1902
2105
|
hosts: [this.config.host],
|
|
1903
2106
|
// session: undefined - not needed for receive()
|
|
1904
|
-
invoke:
|
|
1905
|
-
fetch:
|
|
2107
|
+
invoke: receiveOnlyGraph.invoke,
|
|
2108
|
+
fetch: receiveOnlyGraph.fetch,
|
|
2109
|
+
assertActive: () => receiveOnlyGraph.assertActive(),
|
|
1906
2110
|
keyProvider: this._keyProvider,
|
|
1907
2111
|
registry: this._capabilityRegistry,
|
|
1908
2112
|
createDelegationWasm: (params) => this.createDelegationWrapper(params),
|
|
@@ -1914,12 +2118,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1914
2118
|
createKVService: (config2) => {
|
|
1915
2119
|
const prefix = config2.pathPrefix?.replace(/\/$/, "");
|
|
1916
2120
|
const kvService = new KVService2({ prefix });
|
|
1917
|
-
const kvContext = new ServiceContext2({
|
|
2121
|
+
const kvContext = receiveOnlyGraph.track(new ServiceContext2({
|
|
1918
2122
|
invoke: config2.invoke,
|
|
1919
2123
|
fetch: config2.fetch ?? globalThis.fetch.bind(globalThis),
|
|
1920
2124
|
hosts: config2.hosts,
|
|
1921
2125
|
telemetry: this.config.telemetry
|
|
1922
|
-
});
|
|
2126
|
+
}));
|
|
1923
2127
|
kvContext.setSession(config2.session);
|
|
1924
2128
|
kvService.initialize(kvContext);
|
|
1925
2129
|
return kvService;
|
|
@@ -1993,6 +2197,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1993
2197
|
shouldUseBootstrapSignInRequest(config) {
|
|
1994
2198
|
return config.autoBootstrapAccount !== false && config.manifest === void 0 && config.capabilityRequest === void 0 && (config.prefix ?? "default") === "default" && isOpenKeyAutoSignStrategy(config.signStrategy);
|
|
1995
2199
|
}
|
|
2200
|
+
createServiceGraphLifetime() {
|
|
2201
|
+
return new ServiceGraphLifetime(
|
|
2202
|
+
this.invokeWithRuntimePermissions,
|
|
2203
|
+
this.invokeAnyWithRuntimePermissions,
|
|
2204
|
+
// Resolve fetch at request time. This keeps the graph-owned lifetime
|
|
2205
|
+
// signal while preserving the SDK's existing injectable global fetch
|
|
2206
|
+
// behavior (including adapters that install it after construction).
|
|
2207
|
+
(url, init) => globalThis.fetch(url, init)
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
1996
2210
|
syncResolvedHostFromAuth() {
|
|
1997
2211
|
const host = this.auth?.hosts[0];
|
|
1998
2212
|
if (host) {
|
|
@@ -2062,6 +2276,27 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2062
2276
|
get sessionDid() {
|
|
2063
2277
|
return this.sessionManager.getDID(this.sessionKeyId);
|
|
2064
2278
|
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Return the current session's signed ReCap capabilities after the session
|
|
2281
|
+
* has been authenticated or restored. This is intentionally distinct from
|
|
2282
|
+
* installed runtime delegations: it reports base-session authority only.
|
|
2283
|
+
*
|
|
2284
|
+
* Invalid or unparseable signed ReCap material throws so callers fail closed
|
|
2285
|
+
* rather than treating a malformed session as unrestricted authority.
|
|
2286
|
+
*/
|
|
2287
|
+
getVerifiedSessionCapabilities() {
|
|
2288
|
+
const session = this.currentTinyCloudSession();
|
|
2289
|
+
if (!session || !session.siwe) return [];
|
|
2290
|
+
return parseRecapCapabilities(
|
|
2291
|
+
(siwe) => this.parseRecapWithCaveats(siwe),
|
|
2292
|
+
session.siwe
|
|
2293
|
+
).map((entry) => entry.service === "tinycloud.encryption" ? {
|
|
2294
|
+
service: entry.service,
|
|
2295
|
+
path: entry.path,
|
|
2296
|
+
actions: [...entry.actions],
|
|
2297
|
+
...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
|
|
2298
|
+
} : entry);
|
|
2299
|
+
}
|
|
2065
2300
|
/**
|
|
2066
2301
|
* Get the Ethereum address for this user.
|
|
2067
2302
|
*/
|
|
@@ -2151,6 +2386,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2151
2386
|
this._hooks = void 0;
|
|
2152
2387
|
this._vault = void 0;
|
|
2153
2388
|
this._encryption = void 0;
|
|
2389
|
+
this._baseVaults.clear();
|
|
2154
2390
|
this._baseSecrets.clear();
|
|
2155
2391
|
this._secrets.clear();
|
|
2156
2392
|
this._spaceService = void 0;
|
|
@@ -2158,6 +2394,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2158
2394
|
this.runtimePermissionGrants = [];
|
|
2159
2395
|
await this.tc.signIn(options);
|
|
2160
2396
|
this.syncResolvedHostFromAuth();
|
|
2397
|
+
const oldGraph = this._serviceGraph;
|
|
2398
|
+
this._serviceGraph = this.createServiceGraphLifetime();
|
|
2399
|
+
oldGraph.retire();
|
|
2400
|
+
this.tc.retireServices();
|
|
2161
2401
|
const signedInSession = this.currentTinyCloudSession();
|
|
2162
2402
|
if (signedInSession) {
|
|
2163
2403
|
this.sessionKeyId = signedInSession.sessionKey;
|
|
@@ -2442,7 +2682,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2442
2682
|
}
|
|
2443
2683
|
let operations = [];
|
|
2444
2684
|
try {
|
|
2445
|
-
const entries = this.
|
|
2685
|
+
const entries = this.parseRecapWithCaveats(siwe);
|
|
2446
2686
|
if (Array.isArray(entries)) {
|
|
2447
2687
|
operations = entries.flatMap((entry) => {
|
|
2448
2688
|
const service = this.invocationServiceName(entry.service);
|
|
@@ -2450,7 +2690,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2450
2690
|
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
2451
2691
|
service,
|
|
2452
2692
|
path: entry.path,
|
|
2453
|
-
action
|
|
2693
|
+
action,
|
|
2694
|
+
caveats: cloneRecapCaveats(entry.caveats)
|
|
2454
2695
|
}));
|
|
2455
2696
|
});
|
|
2456
2697
|
}
|
|
@@ -2803,86 +3044,441 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2803
3044
|
*/
|
|
2804
3045
|
async restoreSession(sessionData) {
|
|
2805
3046
|
await this.wasmBindings.ensureInitialized?.();
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
this.
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
3047
|
+
const restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
|
|
3048
|
+
if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
|
|
3049
|
+
throw new Error("Persisted session chain ID must be a positive safe integer.");
|
|
3050
|
+
}
|
|
3051
|
+
const stagedManager = this.wasmBindings.createSessionManager();
|
|
3052
|
+
const stagedReplace = stagedManager.replaceSessionKey;
|
|
3053
|
+
const liveKeys = this.sessionManager.listSessionKeys;
|
|
3054
|
+
const stagedKeys = stagedManager.listSessionKeys;
|
|
3055
|
+
if (typeof stagedReplace !== "function" || typeof liveKeys !== "function" || typeof stagedKeys !== "function") {
|
|
3056
|
+
throw new UnsupportedSessionRestoreError();
|
|
3057
|
+
}
|
|
3058
|
+
let stagedKeyId;
|
|
3059
|
+
let stagedJwk;
|
|
3060
|
+
let canonicalVerificationMethod;
|
|
3061
|
+
try {
|
|
3062
|
+
const keyIds = liveKeys.call(this.sessionManager);
|
|
3063
|
+
if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
|
|
3064
|
+
throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
|
|
3065
|
+
}
|
|
3066
|
+
for (const keyId of keyIds) {
|
|
3067
|
+
if (keyId === this.sessionKeyId) continue;
|
|
3068
|
+
const jwk = this.sessionManager.jwk(keyId);
|
|
3069
|
+
if (!jwk) throw new Error("missing live session key");
|
|
3070
|
+
stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
|
|
3071
|
+
}
|
|
3072
|
+
stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
|
|
3073
|
+
const stagedJwkJson = stagedManager.jwk(stagedKeyId);
|
|
3074
|
+
if (!stagedJwkJson) throw new Error("missing restored session key");
|
|
3075
|
+
stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
|
|
3076
|
+
canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
|
|
3077
|
+
} catch (error) {
|
|
3078
|
+
if (error instanceof UnsupportedSessionRestoreError) throw error;
|
|
3079
|
+
throw new Error("Persisted session has an invalid private Ed25519 session key.");
|
|
2820
3080
|
}
|
|
2821
|
-
|
|
2822
|
-
|
|
3081
|
+
const restoredVerificationMethod = canonicalRestoredVerificationMethod(
|
|
3082
|
+
canonicalVerificationMethod,
|
|
3083
|
+
sessionData.verificationMethod
|
|
3084
|
+
);
|
|
3085
|
+
if (!restoredVerificationMethod) {
|
|
3086
|
+
throw new Error(
|
|
3087
|
+
"Persisted session verification method does not match its private Ed25519 session key."
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
const proofValues = [
|
|
3091
|
+
sessionData.siwe,
|
|
3092
|
+
sessionData.signature,
|
|
3093
|
+
sessionData.address,
|
|
3094
|
+
sessionData.chainId
|
|
3095
|
+
];
|
|
3096
|
+
const hasPersistedProof = proofValues.every((value) => value !== void 0);
|
|
3097
|
+
if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
|
|
3098
|
+
throw new Error("Persisted session authority metadata is incomplete.");
|
|
3099
|
+
}
|
|
3100
|
+
const restoredAddress = hasPersistedProof ? canonicalizeAddress2(sessionData.address) : void 0;
|
|
3101
|
+
let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
|
|
3102
|
+
let stagedRecap = [];
|
|
3103
|
+
if (hasPersistedProof) {
|
|
3104
|
+
if (typeof this.wasmBindings.validatePersistedSession !== "function") {
|
|
3105
|
+
throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
|
|
3106
|
+
}
|
|
3107
|
+
const verified = this.wasmBindings.validatePersistedSession({
|
|
3108
|
+
delegationHeader: sessionData.delegationHeader,
|
|
3109
|
+
delegationCid: sessionData.delegationCid,
|
|
3110
|
+
spaceId: sessionData.spaceId,
|
|
3111
|
+
jwk: stagedJwk,
|
|
3112
|
+
address: restoredAddress,
|
|
3113
|
+
chainId: sessionData.chainId,
|
|
3114
|
+
siwe: sessionData.siwe,
|
|
3115
|
+
signature: sessionData.signature
|
|
3116
|
+
});
|
|
3117
|
+
const exactRecap = verified.verifiedRecap;
|
|
3118
|
+
if (!Array.isArray(exactRecap) || !exactRecap.every(
|
|
3119
|
+
(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(
|
|
3120
|
+
(caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
|
|
3121
|
+
)
|
|
3122
|
+
)) {
|
|
3123
|
+
throw new UnsupportedSessionRestoreError(
|
|
3124
|
+
"it cannot reconstruct caveat-preserving persisted ReCap authority"
|
|
3125
|
+
);
|
|
3126
|
+
}
|
|
3127
|
+
stagedRecap = exactRecap.map((entry) => ({
|
|
3128
|
+
service: entry.service,
|
|
3129
|
+
space: entry.space,
|
|
3130
|
+
path: entry.path,
|
|
3131
|
+
actions: [...entry.actions],
|
|
3132
|
+
caveats: cloneRecapCaveats(entry.caveats)
|
|
3133
|
+
}));
|
|
3134
|
+
const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
|
|
3135
|
+
const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
|
|
3136
|
+
if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
|
|
3137
|
+
throw new Error("Persisted session expiry does not match its signed SIWE authority.");
|
|
3138
|
+
}
|
|
3139
|
+
stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
|
|
2823
3140
|
}
|
|
2824
3141
|
const resolvedHost = await this.resolveRestoredHost(
|
|
2825
3142
|
sessionData.tinycloudHosts,
|
|
2826
3143
|
restoredAddress,
|
|
2827
3144
|
sessionData.chainId
|
|
2828
3145
|
);
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
invoke: this.invokeWithRuntimePermissions,
|
|
2834
|
-
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
2835
|
-
fetch: globalThis.fetch.bind(globalThis),
|
|
2836
|
-
hosts: [this.config.host],
|
|
2837
|
-
telemetry: this.config.telemetry
|
|
2838
|
-
});
|
|
2839
|
-
this._kv = new KVService2({});
|
|
2840
|
-
this._kv.initialize(this._serviceContext);
|
|
2841
|
-
this._serviceContext.registerService("kv", this._kv);
|
|
2842
|
-
this._sql = new SQLService2({});
|
|
2843
|
-
this._sql.initialize(this._serviceContext);
|
|
2844
|
-
this._serviceContext.registerService("sql", this._sql);
|
|
2845
|
-
this._duckdb = new DuckDbService2({});
|
|
2846
|
-
this._duckdb.initialize(this._serviceContext);
|
|
2847
|
-
this._serviceContext.registerService("duckdb", this._duckdb);
|
|
2848
|
-
this._hooks = new HooksService2({});
|
|
2849
|
-
this._hooks.initialize(this._serviceContext);
|
|
2850
|
-
this._serviceContext.registerService("hooks", this._hooks);
|
|
3146
|
+
const stagedHost = resolvedHost ?? this.config.host;
|
|
3147
|
+
const stagedAddress = restoredAddress;
|
|
3148
|
+
const stagedChainId = sessionData.chainId ?? 1;
|
|
3149
|
+
const stagedNodeDid = stagedAddress ? pkhDid2(stagedAddress, stagedChainId) : canonicalVerificationMethod;
|
|
2851
3150
|
const serviceSession = {
|
|
2852
3151
|
delegationHeader: sessionData.delegationHeader,
|
|
2853
3152
|
delegationCid: sessionData.delegationCid,
|
|
2854
3153
|
spaceId: sessionData.spaceId,
|
|
2855
|
-
verificationMethod:
|
|
2856
|
-
jwk:
|
|
3154
|
+
verificationMethod: restoredVerificationMethod,
|
|
3155
|
+
jwk: stagedJwk
|
|
2857
3156
|
};
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
3157
|
+
const stagedTcSession = hasPersistedProof ? {
|
|
3158
|
+
address: restoredAddress,
|
|
3159
|
+
chainId: sessionData.chainId,
|
|
3160
|
+
sessionKey: JSON.stringify(stagedJwk),
|
|
3161
|
+
spaceId: sessionData.spaceId,
|
|
3162
|
+
spaces: sessionData.spaces,
|
|
3163
|
+
delegationCid: sessionData.delegationCid,
|
|
3164
|
+
delegationHeader: sessionData.delegationHeader,
|
|
3165
|
+
verificationMethod: restoredVerificationMethod,
|
|
3166
|
+
jwk: stagedJwk,
|
|
3167
|
+
siwe: sessionData.siwe,
|
|
3168
|
+
signature: sessionData.signature
|
|
3169
|
+
} : void 0;
|
|
3170
|
+
const stagedPrimary = this.stagePrimarySessionState(
|
|
3171
|
+
stagedTcSession,
|
|
3172
|
+
stagedNodeDid,
|
|
3173
|
+
stagedHost,
|
|
3174
|
+
stagedSessionExpiry,
|
|
3175
|
+
stagedRecap
|
|
3176
|
+
);
|
|
3177
|
+
const stagedGraph = this.stageRestoredServiceGraph({
|
|
3178
|
+
host: stagedHost,
|
|
3179
|
+
manager: stagedManager,
|
|
3180
|
+
serviceSession,
|
|
3181
|
+
verificationMethod: canonicalVerificationMethod,
|
|
3182
|
+
nodeDid: stagedNodeDid,
|
|
3183
|
+
address: stagedAddress,
|
|
3184
|
+
chainId: stagedChainId,
|
|
3185
|
+
tinyCloudSession: stagedTcSession,
|
|
3186
|
+
sessionExpiry: stagedSessionExpiry,
|
|
3187
|
+
recap: stagedRecap
|
|
3188
|
+
});
|
|
3189
|
+
const oldGraph = this._serviceGraph;
|
|
3190
|
+
const oldCore = this.tc;
|
|
3191
|
+
this.sessionManager = stagedManager;
|
|
3192
|
+
this.sessionKeyId = stagedKeyId;
|
|
3193
|
+
this.sessionKeyJwk = stagedJwk;
|
|
3194
|
+
if (resolvedHost) this.config.host = resolvedHost;
|
|
3195
|
+
this._address = stagedAddress;
|
|
3196
|
+
this._chainId = stagedChainId;
|
|
3197
|
+
this._serviceContext = stagedGraph.serviceContext;
|
|
3198
|
+
this._kv = stagedGraph.kv;
|
|
3199
|
+
this._sql = stagedGraph.sql;
|
|
3200
|
+
this._duckdb = stagedGraph.duckdb;
|
|
3201
|
+
this._hooks = stagedGraph.hooks;
|
|
3202
|
+
this._vault = stagedGraph.vault;
|
|
3203
|
+
this._encryption = stagedGraph.encryption;
|
|
3204
|
+
this._capabilityRegistry = stagedGraph.capabilityRegistry;
|
|
3205
|
+
this._keyProvider = stagedGraph.keyProvider;
|
|
3206
|
+
this._sharingService = stagedGraph.sharingService;
|
|
3207
|
+
this._delegationManager = stagedGraph.delegationManager;
|
|
3208
|
+
this._spaceService = stagedGraph.spaceService;
|
|
3209
|
+
this._serviceGraph = stagedGraph.graph;
|
|
3210
|
+
this._baseSecrets = /* @__PURE__ */ new Map();
|
|
3211
|
+
this._secrets = /* @__PURE__ */ new Map();
|
|
3212
|
+
this._publicKV = void 0;
|
|
3213
|
+
this._account = void 0;
|
|
3214
|
+
this.tc = stagedGraph.core;
|
|
3215
|
+
this._recapOperationsCache = stagedPrimary.cache;
|
|
3216
|
+
this.runtimePermissionGrants = stagedPrimary.grants;
|
|
3217
|
+
if (this.auth) {
|
|
3218
|
+
this.auth.installRestoredSession(stagedManager, stagedTcSession, [stagedHost]);
|
|
3219
|
+
this._restoredTcSession = void 0;
|
|
3220
|
+
} else {
|
|
3221
|
+
this._restoredTcSession = stagedTcSession;
|
|
3222
|
+
}
|
|
3223
|
+
oldGraph.retire();
|
|
3224
|
+
oldCore?.retireServices?.();
|
|
3225
|
+
}
|
|
3226
|
+
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
3227
|
+
if (!session?.siwe) return { cache: void 0, grants: [] };
|
|
3228
|
+
let operations = [];
|
|
3229
|
+
operations = recap.flatMap((entry) => {
|
|
3230
|
+
const service = this.invocationServiceName(entry.service);
|
|
3231
|
+
return entry.actions.map((action) => ({
|
|
3232
|
+
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
3233
|
+
service,
|
|
3234
|
+
path: entry.path,
|
|
3235
|
+
action,
|
|
3236
|
+
caveats: cloneRecapCaveats(entry.caveats)
|
|
3237
|
+
}));
|
|
3238
|
+
});
|
|
3239
|
+
const cache = { siwe: session.siwe, operations };
|
|
3240
|
+
if (operations.length === 0) return { cache, grants: [] };
|
|
3241
|
+
const expiresAt = sessionExpiry;
|
|
3242
|
+
return {
|
|
3243
|
+
cache,
|
|
3244
|
+
grants: [{
|
|
3245
|
+
session: {
|
|
3246
|
+
delegationHeader: session.delegationHeader,
|
|
3247
|
+
delegationCid: session.delegationCid,
|
|
3248
|
+
spaceId: session.spaceId,
|
|
3249
|
+
verificationMethod: session.verificationMethod,
|
|
3250
|
+
jwk: session.jwk
|
|
3251
|
+
},
|
|
3252
|
+
delegation: {
|
|
3253
|
+
cid: session.delegationCid,
|
|
3254
|
+
delegationHeader: session.delegationHeader,
|
|
3255
|
+
delegateDID: session.verificationMethod,
|
|
3256
|
+
delegatorDID: verificationMethod,
|
|
3257
|
+
spaceId: session.spaceId,
|
|
3258
|
+
path: "",
|
|
3259
|
+
actions: [...new Set(operations.map((operation) => operation.action))],
|
|
3260
|
+
expiry: expiresAt,
|
|
3261
|
+
allowSubDelegation: true,
|
|
3262
|
+
ownerAddress: session.address,
|
|
3263
|
+
chainId: session.chainId,
|
|
3264
|
+
host
|
|
3265
|
+
},
|
|
3266
|
+
operations,
|
|
3267
|
+
expiresAt,
|
|
3268
|
+
provenance: "primary"
|
|
3269
|
+
}]
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
stageRestoredServiceGraph(input) {
|
|
3273
|
+
const graph = this.createServiceGraphLifetime();
|
|
3274
|
+
const core = this.auth ? new TinyCloud(this.auth, {
|
|
3275
|
+
invokeAny: graph.invokeAny,
|
|
3276
|
+
telemetry: this.config.telemetry
|
|
3277
|
+
}) : null;
|
|
3278
|
+
if (core) {
|
|
3279
|
+
core.initializeServices(graph.invoke, [input.host], graph.fetch);
|
|
3280
|
+
const coreContext = graph.track(core.serviceContext);
|
|
3281
|
+
coreContext.setSession(input.serviceSession);
|
|
3282
|
+
}
|
|
3283
|
+
const serviceContext = graph.track(new ServiceContext2({
|
|
3284
|
+
invoke: graph.invoke,
|
|
3285
|
+
invokeAny: graph.invokeAny,
|
|
3286
|
+
fetch: graph.fetch,
|
|
3287
|
+
hosts: [input.host],
|
|
3288
|
+
telemetry: this.config.telemetry
|
|
3289
|
+
}));
|
|
3290
|
+
const kv = new KVService2({});
|
|
3291
|
+
kv.initialize(serviceContext);
|
|
3292
|
+
serviceContext.registerService("kv", kv);
|
|
3293
|
+
const sql = new SQLService2({});
|
|
3294
|
+
sql.initialize(serviceContext);
|
|
3295
|
+
serviceContext.registerService("sql", sql);
|
|
3296
|
+
const duckdb = new DuckDbService2({});
|
|
3297
|
+
duckdb.initialize(serviceContext);
|
|
3298
|
+
serviceContext.registerService("duckdb", duckdb);
|
|
3299
|
+
const hooks = new HooksService2({});
|
|
3300
|
+
hooks.initialize(serviceContext);
|
|
3301
|
+
serviceContext.registerService("hooks", hooks);
|
|
3302
|
+
serviceContext.setSession(input.serviceSession);
|
|
3303
|
+
const encryption = this.createEncryptionService({
|
|
3304
|
+
graph,
|
|
3305
|
+
host: input.host,
|
|
3306
|
+
session: input.serviceSession,
|
|
3307
|
+
did: input.nodeDid,
|
|
3308
|
+
address: input.address,
|
|
3309
|
+
chainId: input.chainId
|
|
3310
|
+
});
|
|
3311
|
+
encryption.initialize(serviceContext);
|
|
3312
|
+
serviceContext.registerService("encryption", encryption);
|
|
3313
|
+
const vault = this.createVaultService(input.serviceSession.spaceId, kv, encryption, {
|
|
3314
|
+
host: input.host,
|
|
3315
|
+
did: input.nodeDid,
|
|
3316
|
+
address: input.address,
|
|
3317
|
+
chainId: input.chainId,
|
|
3318
|
+
serviceGraph: graph
|
|
3319
|
+
});
|
|
3320
|
+
vault.initialize(serviceContext);
|
|
3321
|
+
serviceContext.registerService("vault", vault);
|
|
3322
|
+
const capabilityRegistry = new CapabilityKeyRegistry();
|
|
3323
|
+
if (input.tinyCloudSession) {
|
|
3324
|
+
const session = input.tinyCloudSession;
|
|
3325
|
+
const delegations = input.recap.map((entry) => ({
|
|
3326
|
+
cid: session.delegationCid,
|
|
3327
|
+
delegateDID: session.verificationMethod,
|
|
3328
|
+
spaceId: entry.space,
|
|
3329
|
+
path: entry.path,
|
|
3330
|
+
actions: [...entry.actions],
|
|
3331
|
+
caveats: cloneRecapCaveats(entry.caveats),
|
|
3332
|
+
expiry: input.sessionExpiry,
|
|
3333
|
+
isRevoked: false,
|
|
3334
|
+
allowSubDelegation: true
|
|
3335
|
+
}));
|
|
3336
|
+
if (delegations.length > 0) {
|
|
3337
|
+
capabilityRegistry.registerKey({
|
|
3338
|
+
id: session.sessionKey,
|
|
3339
|
+
did: session.verificationMethod,
|
|
3340
|
+
type: "session",
|
|
3341
|
+
jwk: session.jwk,
|
|
3342
|
+
priority: 0
|
|
3343
|
+
}, delegations);
|
|
2883
3344
|
}
|
|
2884
|
-
this.registerPrimarySessionGrant(tcSession);
|
|
2885
3345
|
}
|
|
3346
|
+
const delegationManager = new DelegationManager({
|
|
3347
|
+
hosts: [input.host],
|
|
3348
|
+
session: input.serviceSession,
|
|
3349
|
+
invoke: graph.invoke,
|
|
3350
|
+
invokeAny: graph.invokeAny,
|
|
3351
|
+
fetch: graph.fetch
|
|
3352
|
+
});
|
|
3353
|
+
const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
|
|
3354
|
+
const sharingService = new SharingService({
|
|
3355
|
+
hosts: [input.host],
|
|
3356
|
+
invoke: graph.invoke,
|
|
3357
|
+
fetch: graph.fetch,
|
|
3358
|
+
assertActive: () => graph.assertActive(),
|
|
3359
|
+
keyProvider,
|
|
3360
|
+
registry: capabilityRegistry,
|
|
3361
|
+
createDelegationWasm: (params) => this.createDelegationWrapper(params),
|
|
3362
|
+
computeCid: (data, codec) => {
|
|
3363
|
+
if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
|
|
3364
|
+
return this.wasmBindings.computeCid(data, codec);
|
|
3365
|
+
},
|
|
3366
|
+
createKVService: (config) => {
|
|
3367
|
+
const service = new KVService2({ prefix: config.pathPrefix?.replace(/\/$/, "") });
|
|
3368
|
+
const context = graph.track(new ServiceContext2({
|
|
3369
|
+
invoke: config.invoke,
|
|
3370
|
+
fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
3371
|
+
hosts: config.hosts,
|
|
3372
|
+
telemetry: this.config.telemetry
|
|
3373
|
+
}));
|
|
3374
|
+
context.setSession(config.session);
|
|
3375
|
+
service.initialize(context);
|
|
3376
|
+
return service;
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3379
|
+
sharingService.updateConfig({
|
|
3380
|
+
session: input.serviceSession,
|
|
3381
|
+
delegationManager,
|
|
3382
|
+
sessionExpiry: input.sessionExpiry,
|
|
3383
|
+
createDelegationWasm: (params) => {
|
|
3384
|
+
graph.assertActive();
|
|
3385
|
+
return this.createDelegationWrapper(params);
|
|
3386
|
+
},
|
|
3387
|
+
onRootDelegationNeeded: this.signer ? async (params) => {
|
|
3388
|
+
graph.assertActive();
|
|
3389
|
+
const delegation = await this.createRootDelegationForSharing(
|
|
3390
|
+
params,
|
|
3391
|
+
() => graph.assertActive()
|
|
3392
|
+
);
|
|
3393
|
+
graph.assertActive();
|
|
3394
|
+
return delegation;
|
|
3395
|
+
} : void 0
|
|
3396
|
+
});
|
|
3397
|
+
const spaceService = new SpaceService({
|
|
3398
|
+
hosts: [input.host],
|
|
3399
|
+
session: input.serviceSession,
|
|
3400
|
+
invoke: graph.invoke,
|
|
3401
|
+
fetch: graph.fetch,
|
|
3402
|
+
capabilityRegistry,
|
|
3403
|
+
userDid: input.nodeDid,
|
|
3404
|
+
createKVService: (spaceId) => {
|
|
3405
|
+
graph.assertActive();
|
|
3406
|
+
const scopedKv = new KVService2({});
|
|
3407
|
+
const context = graph.track(new ServiceContext2({
|
|
3408
|
+
invoke: graph.invoke,
|
|
3409
|
+
invokeAny: graph.invokeAny,
|
|
3410
|
+
fetch: graph.fetch,
|
|
3411
|
+
hosts: [input.host],
|
|
3412
|
+
telemetry: this.config.telemetry
|
|
3413
|
+
}));
|
|
3414
|
+
context.setSession({ ...input.serviceSession, spaceId });
|
|
3415
|
+
scopedKv.initialize(context);
|
|
3416
|
+
return scopedKv;
|
|
3417
|
+
},
|
|
3418
|
+
createVaultService: (spaceId) => {
|
|
3419
|
+
graph.assertActive();
|
|
3420
|
+
const scopedKv = new KVService2({});
|
|
3421
|
+
const context = graph.track(new ServiceContext2({
|
|
3422
|
+
invoke: graph.invoke,
|
|
3423
|
+
invokeAny: graph.invokeAny,
|
|
3424
|
+
fetch: graph.fetch,
|
|
3425
|
+
hosts: [input.host],
|
|
3426
|
+
telemetry: this.config.telemetry
|
|
3427
|
+
}));
|
|
3428
|
+
context.setSession({ ...input.serviceSession, spaceId });
|
|
3429
|
+
scopedKv.initialize(context);
|
|
3430
|
+
const scopedVault = this.createVaultService(spaceId, scopedKv, encryption, {
|
|
3431
|
+
host: input.host,
|
|
3432
|
+
did: input.nodeDid,
|
|
3433
|
+
address: input.address,
|
|
3434
|
+
chainId: input.chainId,
|
|
3435
|
+
serviceGraph: graph
|
|
3436
|
+
});
|
|
3437
|
+
scopedVault.initialize(context);
|
|
3438
|
+
return scopedVault;
|
|
3439
|
+
},
|
|
3440
|
+
createSecretsService: (spaceId) => {
|
|
3441
|
+
graph.assertActive();
|
|
3442
|
+
return this.secretsForSpace(spaceId);
|
|
3443
|
+
},
|
|
3444
|
+
createDelegation: async (params) => {
|
|
3445
|
+
graph.assertActive();
|
|
3446
|
+
try {
|
|
3447
|
+
const portableDelegation = await this.createDelegation({
|
|
3448
|
+
delegateDID: params.delegateDID,
|
|
3449
|
+
path: params.path,
|
|
3450
|
+
actions: params.actions,
|
|
3451
|
+
disableSubDelegation: params.disableSubDelegation,
|
|
3452
|
+
expiryMs: params.expiry ? params.expiry.getTime() - Date.now() : void 0
|
|
3453
|
+
});
|
|
3454
|
+
return { ok: true, data: {
|
|
3455
|
+
cid: portableDelegation.cid,
|
|
3456
|
+
delegateDID: portableDelegation.delegateDID,
|
|
3457
|
+
delegatorDID: this.did,
|
|
3458
|
+
spaceId: portableDelegation.spaceId,
|
|
3459
|
+
path: portableDelegation.path,
|
|
3460
|
+
actions: portableDelegation.actions,
|
|
3461
|
+
expiry: portableDelegation.expiry,
|
|
3462
|
+
isRevoked: false,
|
|
3463
|
+
allowSubDelegation: !portableDelegation.disableSubDelegation,
|
|
3464
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
3465
|
+
authHeader: portableDelegation.delegationHeader.Authorization
|
|
3466
|
+
} };
|
|
3467
|
+
} catch (error) {
|
|
3468
|
+
return { ok: false, error: {
|
|
3469
|
+
code: "CREATION_FAILED",
|
|
3470
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3471
|
+
service: "delegation"
|
|
3472
|
+
} };
|
|
3473
|
+
}
|
|
3474
|
+
},
|
|
3475
|
+
onSpaceRegistered: async (space) => {
|
|
3476
|
+
graph.assertActive();
|
|
3477
|
+
await this.account.spaces.register(space);
|
|
3478
|
+
}
|
|
3479
|
+
});
|
|
3480
|
+
spaceService.updateConfig({ sharingService });
|
|
3481
|
+
return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
|
|
2886
3482
|
}
|
|
2887
3483
|
/**
|
|
2888
3484
|
* Resolve the host a restored session should target.
|
|
@@ -3044,14 +3640,19 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3044
3640
|
if (!session) {
|
|
3045
3641
|
return;
|
|
3046
3642
|
}
|
|
3047
|
-
this.tc.initializeServices(
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3643
|
+
this.tc.initializeServices(
|
|
3644
|
+
this._serviceGraph.invoke,
|
|
3645
|
+
[this.config.host],
|
|
3646
|
+
this._serviceGraph.fetch
|
|
3647
|
+
);
|
|
3648
|
+
this._serviceGraph.track(this.tc.serviceContext);
|
|
3649
|
+
this._serviceContext = this._serviceGraph.track(new ServiceContext2({
|
|
3650
|
+
invoke: this._serviceGraph.invoke,
|
|
3651
|
+
invokeAny: this._serviceGraph.invokeAny,
|
|
3652
|
+
fetch: this._serviceGraph.fetch,
|
|
3052
3653
|
hosts: [this.config.host],
|
|
3053
3654
|
telemetry: this.config.telemetry
|
|
3054
|
-
});
|
|
3655
|
+
}));
|
|
3055
3656
|
this._kv = new KVService2({});
|
|
3056
3657
|
this._kv.initialize(this._serviceContext);
|
|
3057
3658
|
this._serviceContext.registerService("kv", this._kv);
|
|
@@ -3086,12 +3687,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3086
3687
|
createSpaceScopedKVService(spaceId) {
|
|
3087
3688
|
const kvService = new KVService2({});
|
|
3088
3689
|
if (this._serviceContext) {
|
|
3089
|
-
const spaceScopedContext = new ServiceContext2({
|
|
3690
|
+
const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
|
|
3090
3691
|
invoke: this._serviceContext.invoke,
|
|
3091
3692
|
fetch: this._serviceContext.fetch,
|
|
3092
3693
|
hosts: this._serviceContext.hosts,
|
|
3093
3694
|
telemetry: this.config.telemetry
|
|
3094
|
-
});
|
|
3695
|
+
}));
|
|
3095
3696
|
const session = this._serviceContext.session;
|
|
3096
3697
|
if (session) {
|
|
3097
3698
|
spaceScopedContext.setSession({ ...session, spaceId });
|
|
@@ -3124,6 +3725,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3124
3725
|
}
|
|
3125
3726
|
return session;
|
|
3126
3727
|
}
|
|
3728
|
+
/**
|
|
3729
|
+
* Runtime-permission helpers can be used from an authenticated Node session
|
|
3730
|
+
* before its public ServiceContext is initialized. They still need the
|
|
3731
|
+
* current signed session, never an optional or stale authority object.
|
|
3732
|
+
*/
|
|
3733
|
+
requireEncryptionSession() {
|
|
3734
|
+
const session = this._serviceContext?.session ?? this.auth?.tinyCloudSession;
|
|
3735
|
+
if (!session) {
|
|
3736
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
3737
|
+
}
|
|
3738
|
+
return session;
|
|
3739
|
+
}
|
|
3127
3740
|
createEncryptionCrypto() {
|
|
3128
3741
|
const wasm = this.wasmBindings;
|
|
3129
3742
|
const columnEncrypt = (key, plaintext) => {
|
|
@@ -3179,14 +3792,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3179
3792
|
}
|
|
3180
3793
|
return info.nodeId;
|
|
3181
3794
|
}
|
|
3182
|
-
async signRawNetworkAuthorization(input) {
|
|
3795
|
+
async signRawNetworkAuthorization(input, binding) {
|
|
3796
|
+
binding?.graph.assertActive();
|
|
3183
3797
|
if (!this.wasmBindings.invokeAny) {
|
|
3184
3798
|
throw new Error("WASM binding does not support raw-resource invokeAny");
|
|
3185
3799
|
}
|
|
3186
3800
|
if (!this.wasmBindings.computeCid) {
|
|
3187
3801
|
throw new Error("WASM binding does not support invocation CID computation");
|
|
3188
3802
|
}
|
|
3189
|
-
const session = this.
|
|
3803
|
+
const session = binding?.session ?? this.requireEncryptionSession();
|
|
3190
3804
|
const headers = this.invokeAnyWithRuntimePermissions(
|
|
3191
3805
|
session,
|
|
3192
3806
|
[
|
|
@@ -3205,6 +3819,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3205
3819
|
input.targetNode,
|
|
3206
3820
|
session.jwk
|
|
3207
3821
|
);
|
|
3822
|
+
binding?.graph.assertActive();
|
|
3208
3823
|
return {
|
|
3209
3824
|
authorization: audienceBound,
|
|
3210
3825
|
invocationCid: this.wasmBindings.computeCid(
|
|
@@ -3213,25 +3828,55 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3213
3828
|
)
|
|
3214
3829
|
};
|
|
3215
3830
|
}
|
|
3216
|
-
|
|
3831
|
+
async fetchEncryptionNetworkAt(host, networkId, fetchFn) {
|
|
3832
|
+
const response = await fetchFn(
|
|
3833
|
+
`${host}/encryption/networks/${encodeURIComponent(networkId)}`
|
|
3834
|
+
);
|
|
3835
|
+
if (response.status === 404) return null;
|
|
3836
|
+
if (!response.ok) {
|
|
3837
|
+
throw new Error(
|
|
3838
|
+
`Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
|
|
3839
|
+
);
|
|
3840
|
+
}
|
|
3841
|
+
const body = await response.json();
|
|
3842
|
+
return "descriptor" in body && body.descriptor ? body.descriptor : body;
|
|
3843
|
+
}
|
|
3844
|
+
createEncryptionService(binding) {
|
|
3845
|
+
const graph = binding?.graph ?? this._serviceGraph;
|
|
3846
|
+
const host = binding?.host ?? this.config.host;
|
|
3847
|
+
const session = binding?.session ?? this.requireEncryptionSession();
|
|
3848
|
+
const did = binding?.did ?? this.did;
|
|
3849
|
+
const address = binding?.address ?? this._address;
|
|
3850
|
+
const chainId = binding?.chainId ?? this._chainId;
|
|
3217
3851
|
const crypto = this.createEncryptionCrypto();
|
|
3218
3852
|
const transport = {
|
|
3219
|
-
postDecrypt: async ({ networkId, authorization, canonicalBody }) => {
|
|
3220
|
-
|
|
3221
|
-
|
|
3853
|
+
postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
|
|
3854
|
+
graph.assertActive();
|
|
3855
|
+
const response = await graph.fetch(
|
|
3856
|
+
`${host}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
|
|
3222
3857
|
{
|
|
3223
3858
|
method: "POST",
|
|
3224
3859
|
headers: {
|
|
3225
3860
|
Authorization: authorization,
|
|
3226
3861
|
"Content-Type": "application/json"
|
|
3227
3862
|
},
|
|
3228
|
-
body: canonicalBody
|
|
3863
|
+
body: canonicalBody,
|
|
3864
|
+
signal
|
|
3229
3865
|
}
|
|
3230
3866
|
);
|
|
3867
|
+
graph.assertActive();
|
|
3231
3868
|
if (!response.ok) {
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3869
|
+
let permissionHint;
|
|
3870
|
+
if (response.status === 401 || response.status === 403) {
|
|
3871
|
+
try {
|
|
3872
|
+
const body = await response.json();
|
|
3873
|
+
const record = typeof body === "object" && body !== null ? body : void 0;
|
|
3874
|
+
const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
|
|
3875
|
+
permissionHint = parsePermissionHint(record?.permissionHint) ?? parsePermissionHint(nested?.permissionHint);
|
|
3876
|
+
} catch {
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
throw new DecryptTransportResponseError(response.status, permissionHint);
|
|
3235
3880
|
}
|
|
3236
3881
|
return await response.json();
|
|
3237
3882
|
}
|
|
@@ -3240,12 +3885,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3240
3885
|
crypto,
|
|
3241
3886
|
signer: {
|
|
3242
3887
|
signDecryptInvocation: async (input) => {
|
|
3888
|
+
graph.assertActive();
|
|
3243
3889
|
const signed = await this.signRawNetworkAuthorization({
|
|
3244
3890
|
targetNode: input.targetNode,
|
|
3245
3891
|
networkId: input.networkId,
|
|
3246
3892
|
action: DECRYPT_ACTION2,
|
|
3247
3893
|
facts: input.facts
|
|
3248
|
-
});
|
|
3894
|
+
}, { graph, session });
|
|
3895
|
+
graph.assertActive();
|
|
3249
3896
|
return {
|
|
3250
3897
|
...signed,
|
|
3251
3898
|
canonicalBody: canonicalizeEncryptionJson(
|
|
@@ -3256,25 +3903,35 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3256
3903
|
},
|
|
3257
3904
|
transport,
|
|
3258
3905
|
node: {
|
|
3259
|
-
fetchByNetworkId: (networkId) =>
|
|
3906
|
+
fetchByNetworkId: async (networkId) => {
|
|
3907
|
+
graph.assertActive();
|
|
3908
|
+
const descriptor = await this.fetchEncryptionNetworkAt(host, networkId, graph.fetch);
|
|
3909
|
+
graph.assertActive();
|
|
3910
|
+
return descriptor;
|
|
3911
|
+
}
|
|
3260
3912
|
},
|
|
3261
3913
|
wellKnown: {
|
|
3262
3914
|
fetchWellKnown: async (principal, discoveryKey) => {
|
|
3263
|
-
|
|
3915
|
+
graph.assertActive();
|
|
3916
|
+
if (!address || !didPrincipalMatches2(principal, did)) {
|
|
3264
3917
|
return null;
|
|
3265
3918
|
}
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3919
|
+
const publicSpaceId = makePublicSpaceId(address, chainId);
|
|
3920
|
+
const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
|
|
3921
|
+
const response = await graph.fetch(
|
|
3922
|
+
`${host}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
|
|
3923
|
+
{ method: "GET" }
|
|
3924
|
+
);
|
|
3925
|
+
graph.assertActive();
|
|
3926
|
+
if (!response.ok) {
|
|
3272
3927
|
return null;
|
|
3273
3928
|
}
|
|
3274
|
-
const body =
|
|
3929
|
+
const body = await response.json();
|
|
3930
|
+
graph.assertActive();
|
|
3275
3931
|
return "descriptor" in body && body.descriptor ? body.descriptor : body;
|
|
3276
3932
|
}
|
|
3277
|
-
}
|
|
3933
|
+
},
|
|
3934
|
+
assertActive: () => graph.assertActive()
|
|
3278
3935
|
});
|
|
3279
3936
|
}
|
|
3280
3937
|
getEncryptionService() {
|
|
@@ -3288,8 +3945,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3288
3945
|
}
|
|
3289
3946
|
return this._encryption;
|
|
3290
3947
|
}
|
|
3291
|
-
createVaultService(spaceId, kv) {
|
|
3948
|
+
createVaultService(spaceId, kv, encryptionService = this.getEncryptionService(), nodeContext) {
|
|
3292
3949
|
const wasm = this.wasmBindings;
|
|
3950
|
+
const serviceGraph = nodeContext?.serviceGraph ?? this._serviceGraph;
|
|
3293
3951
|
const vaultCrypto = createVaultCrypto({
|
|
3294
3952
|
vault_encrypt: wasm.vault_encrypt,
|
|
3295
3953
|
vault_decrypt: wasm.vault_decrypt,
|
|
@@ -3300,12 +3958,17 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3300
3958
|
vault_sha256: wasm.vault_sha256
|
|
3301
3959
|
});
|
|
3302
3960
|
const self = this;
|
|
3961
|
+
const did = nodeContext?.did ?? this.did;
|
|
3962
|
+
const address = nodeContext?.address ?? this._address;
|
|
3963
|
+
const chainId = nodeContext?.chainId ?? this._chainId;
|
|
3964
|
+
const host = nodeContext?.host ?? this.config.host;
|
|
3965
|
+
const ownerDid = this.ownerDidFromSpaceId(spaceId) ?? did;
|
|
3303
3966
|
return new DataVaultService({
|
|
3304
3967
|
spaceId,
|
|
3305
3968
|
crypto: vaultCrypto,
|
|
3306
3969
|
encryption: {
|
|
3307
|
-
networkId:
|
|
3308
|
-
service:
|
|
3970
|
+
networkId: `urn:tinycloud:encryption:${ownerDid}:${DEFAULT_ENCRYPTION_NETWORK_NAME}`,
|
|
3971
|
+
service: encryptionService,
|
|
3309
3972
|
decryptCapabilityProof: () => ({
|
|
3310
3973
|
proofs: [this.requireServiceSession().delegationCid]
|
|
3311
3974
|
})
|
|
@@ -3314,6 +3977,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3314
3977
|
kv,
|
|
3315
3978
|
ensurePublicSpace: async () => {
|
|
3316
3979
|
try {
|
|
3980
|
+
serviceGraph.assertActive();
|
|
3317
3981
|
await self.ensurePublicSpace();
|
|
3318
3982
|
return { ok: true, data: void 0 };
|
|
3319
3983
|
} catch (error) {
|
|
@@ -3321,14 +3985,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3321
3985
|
}
|
|
3322
3986
|
},
|
|
3323
3987
|
get publicKV() {
|
|
3988
|
+
serviceGraph.assertActive();
|
|
3324
3989
|
return self._publicKV ?? self.tc.publicKV;
|
|
3325
3990
|
},
|
|
3326
|
-
readPublicSpace: (
|
|
3991
|
+
readPublicSpace: (host2, targetSpaceId, key) => TinyCloud.readPublicSpace(host2, targetSpaceId, key),
|
|
3327
3992
|
makePublicSpaceId: TinyCloud.makePublicSpaceId,
|
|
3328
|
-
did
|
|
3329
|
-
address:
|
|
3330
|
-
chainId
|
|
3331
|
-
hosts: [
|
|
3993
|
+
did,
|
|
3994
|
+
address: address ?? "",
|
|
3995
|
+
chainId,
|
|
3996
|
+
hosts: [host]
|
|
3332
3997
|
}
|
|
3333
3998
|
});
|
|
3334
3999
|
}
|
|
@@ -3337,6 +4002,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3337
4002
|
* @internal
|
|
3338
4003
|
*/
|
|
3339
4004
|
initializeV2Services(serviceSession) {
|
|
4005
|
+
const graph = this._serviceGraph;
|
|
3340
4006
|
this._capabilityRegistry = new CapabilityKeyRegistry();
|
|
3341
4007
|
const tcSession = this.auth?.tinyCloudSession;
|
|
3342
4008
|
if (tcSession && this._address) {
|
|
@@ -3381,21 +4047,23 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3381
4047
|
hosts: [this.config.host],
|
|
3382
4048
|
accountSpaceId: this.accountSpaceId,
|
|
3383
4049
|
session: serviceSession,
|
|
3384
|
-
invoke:
|
|
3385
|
-
invokeAny:
|
|
3386
|
-
fetch:
|
|
4050
|
+
invoke: graph.invoke,
|
|
4051
|
+
invokeAny: graph.invokeAny,
|
|
4052
|
+
fetch: graph.fetch
|
|
3387
4053
|
});
|
|
3388
4054
|
this._spaceService = new SpaceService({
|
|
3389
4055
|
hosts: [this.config.host],
|
|
3390
4056
|
session: serviceSession,
|
|
3391
|
-
invoke:
|
|
3392
|
-
fetch:
|
|
4057
|
+
invoke: graph.invoke,
|
|
4058
|
+
fetch: graph.fetch,
|
|
3393
4059
|
capabilityRegistry: this._capabilityRegistry,
|
|
3394
4060
|
userDid: this.did,
|
|
3395
4061
|
createKVService: (spaceId) => {
|
|
4062
|
+
graph.assertActive();
|
|
3396
4063
|
return this.createSpaceScopedKVService(spaceId);
|
|
3397
4064
|
},
|
|
3398
4065
|
createVaultService: (spaceId) => {
|
|
4066
|
+
graph.assertActive();
|
|
3399
4067
|
const kvService = this.createSpaceScopedKVService(spaceId);
|
|
3400
4068
|
const vaultService = this.createVaultService(spaceId, kvService);
|
|
3401
4069
|
if (this._serviceContext) {
|
|
@@ -3404,11 +4072,13 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3404
4072
|
return vaultService;
|
|
3405
4073
|
},
|
|
3406
4074
|
createSecretsService: (spaceId) => {
|
|
4075
|
+
graph.assertActive();
|
|
3407
4076
|
return this.secretsForSpace(spaceId);
|
|
3408
4077
|
},
|
|
3409
4078
|
// Enable space.delegations.create() via SIWE-based delegation
|
|
3410
4079
|
createDelegation: async (params) => {
|
|
3411
4080
|
try {
|
|
4081
|
+
graph.assertActive();
|
|
3412
4082
|
const portableDelegation = await this.createDelegation({
|
|
3413
4083
|
delegateDID: params.delegateDID,
|
|
3414
4084
|
path: params.path,
|
|
@@ -3442,18 +4112,50 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3442
4112
|
}
|
|
3443
4113
|
},
|
|
3444
4114
|
onSpaceRegistered: async (space) => {
|
|
4115
|
+
graph.assertActive();
|
|
3445
4116
|
await this.account.spaces.register(space);
|
|
3446
4117
|
}
|
|
3447
4118
|
});
|
|
3448
|
-
this._sharingService
|
|
4119
|
+
this._sharingService = new SharingService({
|
|
4120
|
+
hosts: [this.config.host],
|
|
3449
4121
|
session: serviceSession,
|
|
4122
|
+
invoke: graph.invoke,
|
|
4123
|
+
fetch: graph.fetch,
|
|
4124
|
+
assertActive: () => graph.assertActive(),
|
|
4125
|
+
keyProvider: this._keyProvider,
|
|
4126
|
+
registry: this._capabilityRegistry,
|
|
3450
4127
|
delegationManager: this._delegationManager,
|
|
3451
4128
|
sessionExpiry: this.getSessionExpiry(),
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
4129
|
+
createDelegationWasm: (params) => {
|
|
4130
|
+
graph.assertActive();
|
|
4131
|
+
return this.createDelegationWrapper(params);
|
|
4132
|
+
},
|
|
4133
|
+
computeCid: (data, codec) => {
|
|
4134
|
+
if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
|
|
4135
|
+
return this.wasmBindings.computeCid(data, codec);
|
|
4136
|
+
},
|
|
4137
|
+
createKVService: (config) => {
|
|
4138
|
+
graph.assertActive();
|
|
4139
|
+
const service = new KVService2({ prefix: config.pathPrefix?.replace(/\/$/, "") });
|
|
4140
|
+
const context = graph.track(new ServiceContext2({
|
|
4141
|
+
invoke: config.invoke,
|
|
4142
|
+
fetch: config.fetch ?? graph.fetch,
|
|
4143
|
+
hosts: config.hosts,
|
|
4144
|
+
telemetry: this.config.telemetry
|
|
4145
|
+
}));
|
|
4146
|
+
context.setSession(config.session);
|
|
4147
|
+
service.initialize(context);
|
|
4148
|
+
return service;
|
|
4149
|
+
},
|
|
4150
|
+
onRootDelegationNeeded: this.signer ? async (params) => {
|
|
4151
|
+
graph.assertActive();
|
|
4152
|
+
const delegation = await this.createRootDelegationForSharing(
|
|
4153
|
+
params,
|
|
4154
|
+
() => graph.assertActive()
|
|
4155
|
+
);
|
|
4156
|
+
graph.assertActive();
|
|
4157
|
+
return delegation;
|
|
4158
|
+
} : void 0
|
|
3457
4159
|
});
|
|
3458
4160
|
this._spaceService.updateConfig({
|
|
3459
4161
|
sharingService: this._sharingService
|
|
@@ -3520,7 +4222,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3520
4222
|
* with expiry longer than the current session.
|
|
3521
4223
|
* @internal
|
|
3522
4224
|
*/
|
|
3523
|
-
async createOwnerDelegation(params) {
|
|
4225
|
+
async createOwnerDelegation(params, assertActive) {
|
|
4226
|
+
const assertOwnerGraphActive = assertActive ?? this._serviceGraph.assertActive.bind(this._serviceGraph);
|
|
4227
|
+
assertOwnerGraphActive();
|
|
3524
4228
|
if (!params.delegateDid.startsWith("did:key:") || params.actions.length === 0 || params.path.length === 0) {
|
|
3525
4229
|
throw new Error("Owner delegation requires an external did:key audience and bounded capabilities");
|
|
3526
4230
|
}
|
|
@@ -3545,8 +4249,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3545
4249
|
delegateUri: params.delegateDid
|
|
3546
4250
|
});
|
|
3547
4251
|
const signature = await this.signer.signMessage(prepared.siwe);
|
|
4252
|
+
assertOwnerGraphActive();
|
|
3548
4253
|
const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
|
|
3549
4254
|
const activation = await activateSessionWithHost2(host, delegationSession.delegationHeader);
|
|
4255
|
+
assertOwnerGraphActive();
|
|
3550
4256
|
if (!activation.success) {
|
|
3551
4257
|
throw new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim());
|
|
3552
4258
|
}
|
|
@@ -3574,7 +4280,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3574
4280
|
}
|
|
3575
4281
|
};
|
|
3576
4282
|
}
|
|
3577
|
-
async createRootDelegationForSharing(params) {
|
|
4283
|
+
async createRootDelegationForSharing(params, assertActive) {
|
|
3578
4284
|
try {
|
|
3579
4285
|
return (await this.createOwnerDelegation({
|
|
3580
4286
|
delegateDid: params.shareKeyDID,
|
|
@@ -3582,8 +4288,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3582
4288
|
path: params.path,
|
|
3583
4289
|
actions: params.actions,
|
|
3584
4290
|
expiresAt: params.requestedExpiry
|
|
3585
|
-
})).delegation;
|
|
4291
|
+
}, assertActive)).delegation;
|
|
3586
4292
|
} catch {
|
|
4293
|
+
assertActive?.();
|
|
3587
4294
|
return void 0;
|
|
3588
4295
|
}
|
|
3589
4296
|
}
|
|
@@ -3655,13 +4362,13 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3655
4362
|
throw new Error("Not signed in. Call signIn() first.");
|
|
3656
4363
|
}
|
|
3657
4364
|
const sql = new SQLService2({});
|
|
3658
|
-
const spaceScopedContext = new ServiceContext2({
|
|
4365
|
+
const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
|
|
3659
4366
|
invoke: this._serviceContext.invoke,
|
|
3660
4367
|
invokeAny: this._serviceContext.invokeAny,
|
|
3661
4368
|
fetch: this._serviceContext.fetch,
|
|
3662
4369
|
hosts: this._serviceContext.hosts,
|
|
3663
4370
|
telemetry: this.config.telemetry
|
|
3664
|
-
});
|
|
4371
|
+
}));
|
|
3665
4372
|
spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
|
|
3666
4373
|
sql.initialize(spaceScopedContext);
|
|
3667
4374
|
return sql;
|
|
@@ -3685,12 +4392,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3685
4392
|
throw new Error("Not signed in. Call signIn() first.");
|
|
3686
4393
|
}
|
|
3687
4394
|
const kv = new KVService2({});
|
|
3688
|
-
const spaceScopedContext = new ServiceContext2({
|
|
4395
|
+
const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
|
|
3689
4396
|
invoke: this._serviceContext.invoke,
|
|
3690
4397
|
invokeAny: this._serviceContext.invokeAny,
|
|
3691
4398
|
fetch: this._serviceContext.fetch,
|
|
3692
4399
|
hosts: this._serviceContext.hosts
|
|
3693
|
-
});
|
|
4400
|
+
}));
|
|
3694
4401
|
spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
|
|
3695
4402
|
kv.initialize(spaceScopedContext);
|
|
3696
4403
|
return kv;
|
|
@@ -3726,19 +4433,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3726
4433
|
}
|
|
3727
4434
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
3728
4435
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
3729
|
-
|
|
3730
|
-
|
|
4436
|
+
return this.fetchEncryptionNetworkAt(
|
|
4437
|
+
this.config.host,
|
|
4438
|
+
networkId,
|
|
4439
|
+
globalThis.fetch.bind(globalThis)
|
|
3731
4440
|
);
|
|
3732
|
-
if (response.status === 404) {
|
|
3733
|
-
return null;
|
|
3734
|
-
}
|
|
3735
|
-
if (!response.ok) {
|
|
3736
|
-
throw new Error(
|
|
3737
|
-
`Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
|
|
3738
|
-
);
|
|
3739
|
-
}
|
|
3740
|
-
const body = await response.json();
|
|
3741
|
-
return "descriptor" in body && body.descriptor ? body.descriptor : body;
|
|
3742
4441
|
}
|
|
3743
4442
|
async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
|
|
3744
4443
|
const targetNode = await this.fetchNodeId();
|
|
@@ -3832,6 +4531,38 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3832
4531
|
}
|
|
3833
4532
|
return secrets;
|
|
3834
4533
|
}
|
|
4534
|
+
/**
|
|
4535
|
+
* Read a secret from an explicit target space without requesting or
|
|
4536
|
+
* auto-signing authority. Active runtime delegations are selected by the
|
|
4537
|
+
* normal KV and encryption invocation paths.
|
|
4538
|
+
*/
|
|
4539
|
+
async readSecret(input) {
|
|
4540
|
+
const resolved = resolveSecretPath2(input.name, { scope: input.scope });
|
|
4541
|
+
const targetSpace = input.space.startsWith("tinycloud:") ? input.space : this.ownedSpaceId(input.space);
|
|
4542
|
+
const result = await this.getBaseVault(targetSpace).readNetworkEncrypted(
|
|
4543
|
+
resolved.vaultKey
|
|
4544
|
+
);
|
|
4545
|
+
if (result.status !== "ok") {
|
|
4546
|
+
if (result.status === "permission_required") {
|
|
4547
|
+
const hint = parsePermissionHint(result.hint);
|
|
4548
|
+
if (hint === void 0) return { status: "read_failed" };
|
|
4549
|
+
return {
|
|
4550
|
+
status: "permission_required",
|
|
4551
|
+
hint: {
|
|
4552
|
+
service: hint.service,
|
|
4553
|
+
...hint.space === void 0 ? {} : { space: hint.space },
|
|
4554
|
+
path: hint.path,
|
|
4555
|
+
actions: [...hint.actions]
|
|
4556
|
+
}
|
|
4557
|
+
};
|
|
4558
|
+
}
|
|
4559
|
+
return { status: result.status };
|
|
4560
|
+
}
|
|
4561
|
+
if (!isSecretPayload(result.entry.value)) {
|
|
4562
|
+
return { status: "invalid_payload" };
|
|
4563
|
+
}
|
|
4564
|
+
return { status: "ok", value: result.entry.value.value };
|
|
4565
|
+
}
|
|
3835
4566
|
getBaseSecrets(spaceId) {
|
|
3836
4567
|
if (!this._spaceService) {
|
|
3837
4568
|
throw new Error("Not signed in. Call signIn() first.");
|
|
@@ -3839,16 +4570,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3839
4570
|
const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
|
|
3840
4571
|
let secrets = this._baseSecrets.get(resolvedSpace);
|
|
3841
4572
|
if (!secrets) {
|
|
3842
|
-
|
|
3843
|
-
const vaultService = this.createVaultService(resolvedSpace, kvService);
|
|
3844
|
-
if (this._serviceContext) {
|
|
3845
|
-
vaultService.initialize(this._serviceContext);
|
|
3846
|
-
}
|
|
3847
|
-
secrets = new SecretsService(() => vaultService);
|
|
4573
|
+
secrets = new SecretsService(() => this.getBaseVault(resolvedSpace));
|
|
3848
4574
|
this._baseSecrets.set(resolvedSpace, secrets);
|
|
3849
4575
|
}
|
|
3850
4576
|
return secrets;
|
|
3851
4577
|
}
|
|
4578
|
+
getBaseVault(spaceId) {
|
|
4579
|
+
if (!this._spaceService) {
|
|
4580
|
+
throw new Error("Not signed in. Call signIn() first.");
|
|
4581
|
+
}
|
|
4582
|
+
const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
|
|
4583
|
+
let vault = this._baseVaults.get(resolvedSpace);
|
|
4584
|
+
if (!vault) {
|
|
4585
|
+
vault = this.createVaultService(
|
|
4586
|
+
resolvedSpace,
|
|
4587
|
+
this.createSpaceScopedKVService(resolvedSpace)
|
|
4588
|
+
);
|
|
4589
|
+
if (this._serviceContext) {
|
|
4590
|
+
vault.initialize(this._serviceContext);
|
|
4591
|
+
}
|
|
4592
|
+
this._baseVaults.set(resolvedSpace, vault);
|
|
4593
|
+
}
|
|
4594
|
+
return vault;
|
|
4595
|
+
}
|
|
3852
4596
|
/**
|
|
3853
4597
|
* Hooks write stream subscription API.
|
|
3854
4598
|
*/
|
|
@@ -3953,6 +4697,63 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3953
4697
|
(grant) => grant.delegation
|
|
3954
4698
|
);
|
|
3955
4699
|
}
|
|
4700
|
+
/**
|
|
4701
|
+
* Return the effective capabilities of installed runtime grants.
|
|
4702
|
+
*
|
|
4703
|
+
* The result is projected from the SDK's activated, expiry-pruned grant
|
|
4704
|
+
* operations rather than from PortableDelegation transport metadata. Base
|
|
4705
|
+
* session authority is intentionally omitted; callers that need the full
|
|
4706
|
+
* live authority must combine this with {@link getVerifiedSessionCapabilities}.
|
|
4707
|
+
*/
|
|
4708
|
+
getEffectiveRuntimePermissionEntries() {
|
|
4709
|
+
const session = this.currentTinyCloudSession();
|
|
4710
|
+
if (!session) return [];
|
|
4711
|
+
this.pruneExpiredRuntimePermissionGrants();
|
|
4712
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4713
|
+
for (const grant of this.runtimePermissionGrants) {
|
|
4714
|
+
if (grant.provenance === "primary") continue;
|
|
4715
|
+
for (const operation of grant.operations) {
|
|
4716
|
+
const service = SERVICE_SHORT_TO_LONG[operation.service];
|
|
4717
|
+
if (service === void 0 || typeof operation.path !== "string" || typeof operation.action !== "string") {
|
|
4718
|
+
continue;
|
|
4719
|
+
}
|
|
4720
|
+
let space;
|
|
4721
|
+
if (service !== "tinycloud.encryption") {
|
|
4722
|
+
if (typeof operation.spaceId !== "string") continue;
|
|
4723
|
+
try {
|
|
4724
|
+
space = this.resolvePermissionSpace(operation.spaceId, session);
|
|
4725
|
+
} catch {
|
|
4726
|
+
continue;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
const caveats = operation.caveats === void 0 || operation.caveats.length === 0 ? void 0 : cloneRecapCaveats(operation.caveats);
|
|
4730
|
+
const identity = JSON.stringify({
|
|
4731
|
+
service,
|
|
4732
|
+
...space === void 0 ? {} : { space },
|
|
4733
|
+
path: operation.path,
|
|
4734
|
+
caveats: canonicalizeRecapCaveats(caveats)
|
|
4735
|
+
});
|
|
4736
|
+
const existing = grouped.get(identity);
|
|
4737
|
+
if (existing === void 0) {
|
|
4738
|
+
grouped.set(identity, {
|
|
4739
|
+
service,
|
|
4740
|
+
...space === void 0 ? {} : { space },
|
|
4741
|
+
path: operation.path,
|
|
4742
|
+
actions: [operation.action],
|
|
4743
|
+
...caveats === void 0 ? {} : { caveats }
|
|
4744
|
+
});
|
|
4745
|
+
continue;
|
|
4746
|
+
}
|
|
4747
|
+
if (!existing.actions.includes(operation.action)) {
|
|
4748
|
+
existing.actions.push(operation.action);
|
|
4749
|
+
existing.actions.sort();
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
return [...grouped.values()].sort(
|
|
4754
|
+
(left, right) => left.service.localeCompare(right.service) || (left.space ?? "").localeCompare(right.space ?? "") || left.path.localeCompare(right.path) || left.actions.join("\0").localeCompare(right.actions.join("\0"))
|
|
4755
|
+
);
|
|
4756
|
+
}
|
|
3956
4757
|
/**
|
|
3957
4758
|
* Install a portable runtime permission delegation into this SDK instance so
|
|
3958
4759
|
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
@@ -4021,6 +4822,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4021
4822
|
"grantRuntimePermissions requires wallet mode with a signer or privateKey."
|
|
4022
4823
|
);
|
|
4023
4824
|
}
|
|
4825
|
+
this.assertDelegationCaveatsPreservable(expanded);
|
|
4024
4826
|
const rawEntries = expanded.filter(
|
|
4025
4827
|
(entry) => this.isEncryptionPermissionEntry(entry)
|
|
4026
4828
|
);
|
|
@@ -4282,12 +5084,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4282
5084
|
}
|
|
4283
5085
|
if (this._serviceContext) {
|
|
4284
5086
|
const publicKV = new KVService2({ prefix: "" });
|
|
4285
|
-
const publicContext = new ServiceContext2({
|
|
4286
|
-
invoke: this.
|
|
4287
|
-
fetch: this.
|
|
5087
|
+
const publicContext = this._serviceGraph.track(new ServiceContext2({
|
|
5088
|
+
invoke: this._serviceGraph.invoke,
|
|
5089
|
+
fetch: this._serviceGraph.fetch,
|
|
4288
5090
|
hosts: this._serviceContext.hosts,
|
|
4289
5091
|
telemetry: this.config.telemetry
|
|
4290
|
-
});
|
|
5092
|
+
}));
|
|
4291
5093
|
publicContext.setSession({
|
|
4292
5094
|
delegationHeader: delegationSession.delegationHeader,
|
|
4293
5095
|
delegationCid: delegationSession.delegationCid,
|
|
@@ -4354,13 +5156,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4354
5156
|
async getDelegationStatus(cid) {
|
|
4355
5157
|
return this.delegationManager.status(cid);
|
|
4356
5158
|
}
|
|
4357
|
-
/** Compute the canonical CID of a compact delegation authorization. */
|
|
5159
|
+
/** Compute the canonical CID of a compact UCAN or DAG-CBOR delegation authorization. */
|
|
4358
5160
|
computeDelegationCid(authorization) {
|
|
4359
5161
|
if (!authorization || !this.wasmBindings.computeCid) {
|
|
4360
5162
|
throw new Error("Delegation CID computation is unavailable");
|
|
4361
5163
|
}
|
|
5164
|
+
const compact = authorization.replace(/^Bearer /i, "");
|
|
4362
5165
|
return this.wasmBindings.computeCid(
|
|
4363
|
-
new TextEncoder().encode(authorization),
|
|
5166
|
+
compact.includes(".") ? new TextEncoder().encode(compact) : decodeAuthorizationBytes(authorization),
|
|
4364
5167
|
0x55n
|
|
4365
5168
|
);
|
|
4366
5169
|
}
|
|
@@ -4452,6 +5255,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4452
5255
|
"delegateTo with forceWalletSign=true supports at most one PermissionEntry. Multi-entry requests must go through the session-key UCAN path (drop forceWalletSign) or the legacy createDelegation method."
|
|
4453
5256
|
);
|
|
4454
5257
|
}
|
|
5258
|
+
this.assertDelegationCaveatsPreservable(expandedEntries);
|
|
4455
5259
|
const delegation2 = await this.createDelegationLegacyWalletPath(
|
|
4456
5260
|
did,
|
|
4457
5261
|
expandedEntries[0],
|
|
@@ -4460,13 +5264,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4460
5264
|
return { delegation: delegation2, prompted: true };
|
|
4461
5265
|
}
|
|
4462
5266
|
const granted = parseRecapCapabilities(
|
|
4463
|
-
(siwe) => this.
|
|
5267
|
+
(siwe) => this.parseRecapWithCaveats(siwe),
|
|
4464
5268
|
session.siwe
|
|
4465
5269
|
);
|
|
4466
5270
|
const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
|
|
4467
5271
|
if (!subset) {
|
|
5272
|
+
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
4468
5273
|
const runtimeGrant = this.findGrantForOperations(
|
|
4469
|
-
|
|
5274
|
+
runtimeOperations,
|
|
4470
5275
|
{ excludePrimary: true }
|
|
4471
5276
|
);
|
|
4472
5277
|
if (runtimeGrant) {
|
|
@@ -4479,7 +5284,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4479
5284
|
did,
|
|
4480
5285
|
expandedEntries,
|
|
4481
5286
|
runtimeExpiration,
|
|
4482
|
-
runtimeGrant
|
|
5287
|
+
runtimeGrant,
|
|
5288
|
+
runtimeOperations
|
|
4483
5289
|
);
|
|
4484
5290
|
return { delegation: delegation2, prompted: false };
|
|
4485
5291
|
}
|
|
@@ -4549,6 +5355,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4549
5355
|
"createDelegationViaWasmPath requires a non-empty entries array"
|
|
4550
5356
|
);
|
|
4551
5357
|
}
|
|
5358
|
+
this.assertDelegationCaveatsPreservable(entries);
|
|
4552
5359
|
const resolvedSpaces = /* @__PURE__ */ new Set();
|
|
4553
5360
|
for (const entry of entries) {
|
|
4554
5361
|
if (this.isEncryptionPermissionEntry(entry)) {
|
|
@@ -4629,7 +5436,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4629
5436
|
host: this.config.host
|
|
4630
5437
|
};
|
|
4631
5438
|
}
|
|
4632
|
-
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
|
|
5439
|
+
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations) {
|
|
5440
|
+
this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
|
|
5441
|
+
this.assertDelegationCaveatsPreservable(entries);
|
|
4633
5442
|
const result = this.createDelegationWrapper({
|
|
4634
5443
|
session: grant.session,
|
|
4635
5444
|
delegateDID: did,
|
|
@@ -4683,6 +5492,74 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4683
5492
|
expandPermissionEntries(permissions) {
|
|
4684
5493
|
return expandPermissionEntriesCore(permissions);
|
|
4685
5494
|
}
|
|
5495
|
+
/**
|
|
5496
|
+
* The current WASM child-delegation APIs accept action-only maps. Refuse any
|
|
5497
|
+
* constrained branch rather than signing a broader action-only child UCAN.
|
|
5498
|
+
*/
|
|
5499
|
+
assertDelegationCaveatsPreservable(entries) {
|
|
5500
|
+
const caveated = entries.filter(
|
|
5501
|
+
(entry) => !recapCaveatsEqual(entry.caveats, void 0)
|
|
5502
|
+
);
|
|
5503
|
+
if (caveated.length > 0) {
|
|
5504
|
+
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
/**
|
|
5508
|
+
* Runtime grant selection intentionally allows an uncaveated operation to
|
|
5509
|
+
* select a caveated grant so invocation can restore the signed caveat before
|
|
5510
|
+
* crossing the caveat-aware WASM boundary. Child delegation cannot do that:
|
|
5511
|
+
* it only accepts action maps, so reject the selected constrained branch.
|
|
5512
|
+
*/
|
|
5513
|
+
assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant) {
|
|
5514
|
+
let operationIndex = 0;
|
|
5515
|
+
const caveated = [];
|
|
5516
|
+
for (const entry of entries) {
|
|
5517
|
+
for (const action of entry.actions) {
|
|
5518
|
+
const requested = requestedOperations[operationIndex++];
|
|
5519
|
+
if (!requested) continue;
|
|
5520
|
+
const constrained = grant.operations.find(
|
|
5521
|
+
(granted) => this.operationCovers(granted, requested) && !recapCaveatsEqual(granted.caveats, void 0)
|
|
5522
|
+
);
|
|
5523
|
+
if (constrained) {
|
|
5524
|
+
caveated.push({
|
|
5525
|
+
...entry,
|
|
5526
|
+
actions: [action],
|
|
5527
|
+
caveats: cloneRecapCaveats(constrained.caveats)
|
|
5528
|
+
});
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
if (caveated.length > 0) {
|
|
5533
|
+
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
/** Reject caveated parent branches before action-only child signing. */
|
|
5537
|
+
assertPortableDelegationCaveatsPreservable(delegation) {
|
|
5538
|
+
const entries = [];
|
|
5539
|
+
const add = (service, space, path, actions, caveats) => {
|
|
5540
|
+
if (recapCaveatsEqual(caveats, void 0)) return;
|
|
5541
|
+
entries.push({
|
|
5542
|
+
service: service.startsWith("tinycloud.") ? service : `tinycloud.${service}`,
|
|
5543
|
+
space,
|
|
5544
|
+
path,
|
|
5545
|
+
actions: [...actions],
|
|
5546
|
+
caveats: cloneRecapCaveats(caveats)
|
|
5547
|
+
});
|
|
5548
|
+
};
|
|
5549
|
+
add(
|
|
5550
|
+
delegation.actions[0]?.split("/", 1)[0] ?? "tinycloud.unknown",
|
|
5551
|
+
delegation.spaceId,
|
|
5552
|
+
delegation.path,
|
|
5553
|
+
delegation.actions,
|
|
5554
|
+
delegation.caveats
|
|
5555
|
+
);
|
|
5556
|
+
for (const resource of delegation.resources ?? []) {
|
|
5557
|
+
add(resource.service, resource.space, resource.path, resource.actions, resource.caveats);
|
|
5558
|
+
}
|
|
5559
|
+
if (entries.length > 0) {
|
|
5560
|
+
throw new CaveatedDelegationUnsupportedError(entries);
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
4686
5563
|
shortServiceName(service) {
|
|
4687
5564
|
const short = SERVICE_LONG_TO_SHORT[service];
|
|
4688
5565
|
if (short === void 0) {
|
|
@@ -4737,14 +5614,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4737
5614
|
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
4738
5615
|
service,
|
|
4739
5616
|
path: entry.path,
|
|
4740
|
-
action
|
|
5617
|
+
action,
|
|
5618
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
4741
5619
|
}));
|
|
4742
5620
|
});
|
|
4743
5621
|
}
|
|
4744
5622
|
sessionCoversPermissionEntries(session, entries) {
|
|
4745
5623
|
try {
|
|
4746
5624
|
const granted = parseRecapCapabilities(
|
|
4747
|
-
(siwe) => this.
|
|
5625
|
+
(siwe) => this.parseRecapWithCaveats(siwe),
|
|
4748
5626
|
session.siwe
|
|
4749
5627
|
);
|
|
4750
5628
|
return isCapabilitySubset(entries, granted).subset;
|
|
@@ -4760,7 +5638,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4760
5638
|
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
4761
5639
|
service,
|
|
4762
5640
|
path: entry.path,
|
|
4763
|
-
action
|
|
5641
|
+
action,
|
|
5642
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
4764
5643
|
}));
|
|
4765
5644
|
});
|
|
4766
5645
|
}
|
|
@@ -4836,7 +5715,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4836
5715
|
service: this.shortServiceName(entry.service),
|
|
4837
5716
|
space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
|
|
4838
5717
|
path: entry.path,
|
|
4839
|
-
actions: [...entry.actions]
|
|
5718
|
+
actions: [...entry.actions],
|
|
5719
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
4840
5720
|
}));
|
|
4841
5721
|
}
|
|
4842
5722
|
operationsFromDelegation(delegation) {
|
|
@@ -4847,7 +5727,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4847
5727
|
...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
|
|
4848
5728
|
service,
|
|
4849
5729
|
path: resource.path,
|
|
4850
|
-
action
|
|
5730
|
+
action,
|
|
5731
|
+
caveats: cloneRecapCaveats(resource.caveats)
|
|
4851
5732
|
}));
|
|
4852
5733
|
});
|
|
4853
5734
|
}
|
|
@@ -4863,7 +5744,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4863
5744
|
service,
|
|
4864
5745
|
space: delegation.spaceId,
|
|
4865
5746
|
path: delegation.path,
|
|
4866
|
-
actions
|
|
5747
|
+
actions,
|
|
5748
|
+
...delegation.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(delegation.caveats) }
|
|
4867
5749
|
}));
|
|
4868
5750
|
}
|
|
4869
5751
|
/**
|
|
@@ -4964,6 +5846,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4964
5846
|
if (granted.service !== requested.service || !this.actionContains(granted.action, requested.action)) {
|
|
4965
5847
|
return false;
|
|
4966
5848
|
}
|
|
5849
|
+
if (requested.caveats !== void 0 && !recapCaveatsEqual(granted.caveats, requested.caveats)) {
|
|
5850
|
+
return false;
|
|
5851
|
+
}
|
|
4967
5852
|
if (granted.resource !== void 0 || requested.resource !== void 0) {
|
|
4968
5853
|
return granted.resource !== void 0 && requested.resource !== void 0 && granted.resource === requested.resource && this.pathContains(granted.path, requested.path);
|
|
4969
5854
|
}
|
|
@@ -4998,6 +5883,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4998
5883
|
invocationServiceName(service) {
|
|
4999
5884
|
return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
|
|
5000
5885
|
}
|
|
5886
|
+
/** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
|
|
5887
|
+
parseRecapWithCaveats(siwe) {
|
|
5888
|
+
return this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe) ?? this.wasmBindings.parseRecapFromSiwe(siwe);
|
|
5889
|
+
}
|
|
5001
5890
|
isEncryptionNetworkOperation(service, path) {
|
|
5002
5891
|
return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
|
|
5003
5892
|
}
|
|
@@ -5008,7 +5897,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5008
5897
|
resource: entry.resource,
|
|
5009
5898
|
service,
|
|
5010
5899
|
path: entry.path,
|
|
5011
|
-
action: entry.action
|
|
5900
|
+
action: entry.action,
|
|
5901
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
5012
5902
|
};
|
|
5013
5903
|
}
|
|
5014
5904
|
if (this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
@@ -5016,7 +5906,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5016
5906
|
resource: entry.path,
|
|
5017
5907
|
service,
|
|
5018
5908
|
path: entry.path,
|
|
5019
|
-
action: entry.action
|
|
5909
|
+
action: entry.action,
|
|
5910
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
5020
5911
|
};
|
|
5021
5912
|
}
|
|
5022
5913
|
if (typeof entry.spaceId === "string") {
|
|
@@ -5024,7 +5915,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5024
5915
|
spaceId: entry.spaceId,
|
|
5025
5916
|
service,
|
|
5026
5917
|
path: entry.path,
|
|
5027
|
-
action: entry.action
|
|
5918
|
+
action: entry.action,
|
|
5919
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
5028
5920
|
};
|
|
5029
5921
|
}
|
|
5030
5922
|
return void 0;
|
|
@@ -5364,6 +6256,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5364
6256
|
* @returns A portable delegation for the sub-delegate
|
|
5365
6257
|
*/
|
|
5366
6258
|
async createSubDelegation(parentDelegation, params) {
|
|
6259
|
+
this.assertPortableDelegationCaveatsPreservable(parentDelegation);
|
|
5367
6260
|
if (!this.signer) {
|
|
5368
6261
|
throw new Error("Cannot createSubDelegation() in session-only mode. Requires wallet mode.");
|
|
5369
6262
|
}
|
|
@@ -5462,6 +6355,7 @@ import {
|
|
|
5462
6355
|
DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
|
|
5463
6356
|
DEFAULT_MANIFEST_VERSION,
|
|
5464
6357
|
VAULT_PERMISSION_SERVICE,
|
|
6358
|
+
CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
|
|
5465
6359
|
PermissionNotInManifestError as PermissionNotInManifestError2,
|
|
5466
6360
|
SessionExpiredError as SessionExpiredError2,
|
|
5467
6361
|
ManifestValidationError,
|
|
@@ -5478,6 +6372,10 @@ import {
|
|
|
5478
6372
|
} from "@tinycloud/sdk-core";
|
|
5479
6373
|
|
|
5480
6374
|
// src/delegation.ts
|
|
6375
|
+
import {
|
|
6376
|
+
canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
|
|
6377
|
+
principalDidEquals as principalDidEquals3
|
|
6378
|
+
} from "@tinycloud/sdk-core";
|
|
5481
6379
|
function serializeDelegation(delegation) {
|
|
5482
6380
|
return JSON.stringify({
|
|
5483
6381
|
...delegation,
|
|
@@ -5514,7 +6412,7 @@ import {
|
|
|
5514
6412
|
SECRET_NAME_RE,
|
|
5515
6413
|
canonicalizeSecretScope,
|
|
5516
6414
|
resolveSecretListPrefix as resolveSecretListPrefix2,
|
|
5517
|
-
resolveSecretPath as
|
|
6415
|
+
resolveSecretPath as resolveSecretPath3
|
|
5518
6416
|
} from "@tinycloud/sdk-core";
|
|
5519
6417
|
import {
|
|
5520
6418
|
DelegationManager as DelegationManager2,
|
|
@@ -5550,6 +6448,7 @@ export {
|
|
|
5550
6448
|
AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
|
|
5551
6449
|
CapabilityKeyRegistry2 as CapabilityKeyRegistry,
|
|
5552
6450
|
CapabilityKeyRegistryErrorCodes,
|
|
6451
|
+
CaveatedDelegationUnsupportedError2 as CaveatedDelegationUnsupportedError,
|
|
5553
6452
|
DEFAULT_MANIFEST_SPACE2 as DEFAULT_MANIFEST_SPACE,
|
|
5554
6453
|
DEFAULT_MANIFEST_VERSION,
|
|
5555
6454
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
@@ -5585,6 +6484,7 @@ export {
|
|
|
5585
6484
|
TinyCloudDebugLogger,
|
|
5586
6485
|
TinyCloudNode,
|
|
5587
6486
|
UnsupportedFeatureError2 as UnsupportedFeatureError,
|
|
6487
|
+
UnsupportedSessionRestoreError,
|
|
5588
6488
|
VAULT_PERMISSION_SERVICE,
|
|
5589
6489
|
VaultHeaders,
|
|
5590
6490
|
VaultPublicSpaceKVActions,
|
|
@@ -5629,10 +6529,10 @@ export {
|
|
|
5629
6529
|
parseSpaceUri,
|
|
5630
6530
|
pkhDid3 as pkhDid,
|
|
5631
6531
|
principalDid,
|
|
5632
|
-
|
|
6532
|
+
principalDidEquals4 as principalDidEquals,
|
|
5633
6533
|
resolveManifest2 as resolveManifest,
|
|
5634
6534
|
resolveSecretListPrefix2 as resolveSecretListPrefix,
|
|
5635
|
-
|
|
6535
|
+
resolveSecretPath3 as resolveSecretPath,
|
|
5636
6536
|
resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
|
|
5637
6537
|
serializeDelegation,
|
|
5638
6538
|
tinyCloudDebugLogger,
|