@tinycloud/node-sdk 2.7.0-beta.2 → 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-BfqzTbfY.d.cts → core-D_KNrE_y.d.cts} +153 -8
- package/dist/{core-BfqzTbfY.d.ts → core-D_KNrE_y.d.ts} +153 -8
- package/dist/core.cjs +1152 -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 +1083 -181
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1470 -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 +1361 -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
|
*/
|
|
@@ -2104,6 +2339,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2104
2339
|
getPrimarySpaceId: () => this.spaceId,
|
|
2105
2340
|
getAccountSpaceId: () => this.accountSpaceId,
|
|
2106
2341
|
getSpaces: () => this.spaces,
|
|
2342
|
+
getDelegationManager: () => this.delegationManager,
|
|
2107
2343
|
getAccountDb: () => this.accountSpaceId ? this.sqlForSpace(this.accountSpaceId).db("account") : void 0,
|
|
2108
2344
|
ensureAccountSpaceHosted: async () => {
|
|
2109
2345
|
if (this.accountSpaceId && this.auth) {
|
|
@@ -2150,6 +2386,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2150
2386
|
this._hooks = void 0;
|
|
2151
2387
|
this._vault = void 0;
|
|
2152
2388
|
this._encryption = void 0;
|
|
2389
|
+
this._baseVaults.clear();
|
|
2153
2390
|
this._baseSecrets.clear();
|
|
2154
2391
|
this._secrets.clear();
|
|
2155
2392
|
this._spaceService = void 0;
|
|
@@ -2157,6 +2394,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2157
2394
|
this.runtimePermissionGrants = [];
|
|
2158
2395
|
await this.tc.signIn(options);
|
|
2159
2396
|
this.syncResolvedHostFromAuth();
|
|
2397
|
+
const oldGraph = this._serviceGraph;
|
|
2398
|
+
this._serviceGraph = this.createServiceGraphLifetime();
|
|
2399
|
+
oldGraph.retire();
|
|
2400
|
+
this.tc.retireServices();
|
|
2160
2401
|
const signedInSession = this.currentTinyCloudSession();
|
|
2161
2402
|
if (signedInSession) {
|
|
2162
2403
|
this.sessionKeyId = signedInSession.sessionKey;
|
|
@@ -2441,7 +2682,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2441
2682
|
}
|
|
2442
2683
|
let operations = [];
|
|
2443
2684
|
try {
|
|
2444
|
-
const entries = this.
|
|
2685
|
+
const entries = this.parseRecapWithCaveats(siwe);
|
|
2445
2686
|
if (Array.isArray(entries)) {
|
|
2446
2687
|
operations = entries.flatMap((entry) => {
|
|
2447
2688
|
const service = this.invocationServiceName(entry.service);
|
|
@@ -2449,7 +2690,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2449
2690
|
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
2450
2691
|
service,
|
|
2451
2692
|
path: entry.path,
|
|
2452
|
-
action
|
|
2693
|
+
action,
|
|
2694
|
+
caveats: cloneRecapCaveats(entry.caveats)
|
|
2453
2695
|
}));
|
|
2454
2696
|
});
|
|
2455
2697
|
}
|
|
@@ -2802,86 +3044,441 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2802
3044
|
*/
|
|
2803
3045
|
async restoreSession(sessionData) {
|
|
2804
3046
|
await this.wasmBindings.ensureInitialized?.();
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
this.
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
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.");
|
|
2819
3080
|
}
|
|
2820
|
-
|
|
2821
|
-
|
|
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);
|
|
2822
3140
|
}
|
|
2823
3141
|
const resolvedHost = await this.resolveRestoredHost(
|
|
2824
3142
|
sessionData.tinycloudHosts,
|
|
2825
3143
|
restoredAddress,
|
|
2826
3144
|
sessionData.chainId
|
|
2827
3145
|
);
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
invoke: this.invokeWithRuntimePermissions,
|
|
2833
|
-
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
2834
|
-
fetch: globalThis.fetch.bind(globalThis),
|
|
2835
|
-
hosts: [this.config.host],
|
|
2836
|
-
telemetry: this.config.telemetry
|
|
2837
|
-
});
|
|
2838
|
-
this._kv = new KVService2({});
|
|
2839
|
-
this._kv.initialize(this._serviceContext);
|
|
2840
|
-
this._serviceContext.registerService("kv", this._kv);
|
|
2841
|
-
this._sql = new SQLService2({});
|
|
2842
|
-
this._sql.initialize(this._serviceContext);
|
|
2843
|
-
this._serviceContext.registerService("sql", this._sql);
|
|
2844
|
-
this._duckdb = new DuckDbService2({});
|
|
2845
|
-
this._duckdb.initialize(this._serviceContext);
|
|
2846
|
-
this._serviceContext.registerService("duckdb", this._duckdb);
|
|
2847
|
-
this._hooks = new HooksService2({});
|
|
2848
|
-
this._hooks.initialize(this._serviceContext);
|
|
2849
|
-
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;
|
|
2850
3150
|
const serviceSession = {
|
|
2851
3151
|
delegationHeader: sessionData.delegationHeader,
|
|
2852
3152
|
delegationCid: sessionData.delegationCid,
|
|
2853
3153
|
spaceId: sessionData.spaceId,
|
|
2854
|
-
verificationMethod:
|
|
2855
|
-
jwk:
|
|
3154
|
+
verificationMethod: restoredVerificationMethod,
|
|
3155
|
+
jwk: stagedJwk
|
|
2856
3156
|
};
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
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);
|
|
2882
3344
|
}
|
|
2883
|
-
this.registerPrimarySessionGrant(tcSession);
|
|
2884
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 };
|
|
2885
3482
|
}
|
|
2886
3483
|
/**
|
|
2887
3484
|
* Resolve the host a restored session should target.
|
|
@@ -3043,14 +3640,19 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3043
3640
|
if (!session) {
|
|
3044
3641
|
return;
|
|
3045
3642
|
}
|
|
3046
|
-
this.tc.initializeServices(
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
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,
|
|
3051
3653
|
hosts: [this.config.host],
|
|
3052
3654
|
telemetry: this.config.telemetry
|
|
3053
|
-
});
|
|
3655
|
+
}));
|
|
3054
3656
|
this._kv = new KVService2({});
|
|
3055
3657
|
this._kv.initialize(this._serviceContext);
|
|
3056
3658
|
this._serviceContext.registerService("kv", this._kv);
|
|
@@ -3085,12 +3687,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3085
3687
|
createSpaceScopedKVService(spaceId) {
|
|
3086
3688
|
const kvService = new KVService2({});
|
|
3087
3689
|
if (this._serviceContext) {
|
|
3088
|
-
const spaceScopedContext = new ServiceContext2({
|
|
3690
|
+
const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
|
|
3089
3691
|
invoke: this._serviceContext.invoke,
|
|
3090
3692
|
fetch: this._serviceContext.fetch,
|
|
3091
3693
|
hosts: this._serviceContext.hosts,
|
|
3092
3694
|
telemetry: this.config.telemetry
|
|
3093
|
-
});
|
|
3695
|
+
}));
|
|
3094
3696
|
const session = this._serviceContext.session;
|
|
3095
3697
|
if (session) {
|
|
3096
3698
|
spaceScopedContext.setSession({ ...session, spaceId });
|
|
@@ -3123,6 +3725,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3123
3725
|
}
|
|
3124
3726
|
return session;
|
|
3125
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
|
+
}
|
|
3126
3740
|
createEncryptionCrypto() {
|
|
3127
3741
|
const wasm = this.wasmBindings;
|
|
3128
3742
|
const columnEncrypt = (key, plaintext) => {
|
|
@@ -3178,14 +3792,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3178
3792
|
}
|
|
3179
3793
|
return info.nodeId;
|
|
3180
3794
|
}
|
|
3181
|
-
async signRawNetworkAuthorization(input) {
|
|
3795
|
+
async signRawNetworkAuthorization(input, binding) {
|
|
3796
|
+
binding?.graph.assertActive();
|
|
3182
3797
|
if (!this.wasmBindings.invokeAny) {
|
|
3183
3798
|
throw new Error("WASM binding does not support raw-resource invokeAny");
|
|
3184
3799
|
}
|
|
3185
3800
|
if (!this.wasmBindings.computeCid) {
|
|
3186
3801
|
throw new Error("WASM binding does not support invocation CID computation");
|
|
3187
3802
|
}
|
|
3188
|
-
const session = this.
|
|
3803
|
+
const session = binding?.session ?? this.requireEncryptionSession();
|
|
3189
3804
|
const headers = this.invokeAnyWithRuntimePermissions(
|
|
3190
3805
|
session,
|
|
3191
3806
|
[
|
|
@@ -3204,6 +3819,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3204
3819
|
input.targetNode,
|
|
3205
3820
|
session.jwk
|
|
3206
3821
|
);
|
|
3822
|
+
binding?.graph.assertActive();
|
|
3207
3823
|
return {
|
|
3208
3824
|
authorization: audienceBound,
|
|
3209
3825
|
invocationCid: this.wasmBindings.computeCid(
|
|
@@ -3212,25 +3828,55 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3212
3828
|
)
|
|
3213
3829
|
};
|
|
3214
3830
|
}
|
|
3215
|
-
|
|
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;
|
|
3216
3851
|
const crypto = this.createEncryptionCrypto();
|
|
3217
3852
|
const transport = {
|
|
3218
|
-
postDecrypt: async ({ networkId, authorization, canonicalBody }) => {
|
|
3219
|
-
|
|
3220
|
-
|
|
3853
|
+
postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
|
|
3854
|
+
graph.assertActive();
|
|
3855
|
+
const response = await graph.fetch(
|
|
3856
|
+
`${host}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
|
|
3221
3857
|
{
|
|
3222
3858
|
method: "POST",
|
|
3223
3859
|
headers: {
|
|
3224
3860
|
Authorization: authorization,
|
|
3225
3861
|
"Content-Type": "application/json"
|
|
3226
3862
|
},
|
|
3227
|
-
body: canonicalBody
|
|
3863
|
+
body: canonicalBody,
|
|
3864
|
+
signal
|
|
3228
3865
|
}
|
|
3229
3866
|
);
|
|
3867
|
+
graph.assertActive();
|
|
3230
3868
|
if (!response.ok) {
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
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);
|
|
3234
3880
|
}
|
|
3235
3881
|
return await response.json();
|
|
3236
3882
|
}
|
|
@@ -3239,12 +3885,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3239
3885
|
crypto,
|
|
3240
3886
|
signer: {
|
|
3241
3887
|
signDecryptInvocation: async (input) => {
|
|
3888
|
+
graph.assertActive();
|
|
3242
3889
|
const signed = await this.signRawNetworkAuthorization({
|
|
3243
3890
|
targetNode: input.targetNode,
|
|
3244
3891
|
networkId: input.networkId,
|
|
3245
3892
|
action: DECRYPT_ACTION2,
|
|
3246
3893
|
facts: input.facts
|
|
3247
|
-
});
|
|
3894
|
+
}, { graph, session });
|
|
3895
|
+
graph.assertActive();
|
|
3248
3896
|
return {
|
|
3249
3897
|
...signed,
|
|
3250
3898
|
canonicalBody: canonicalizeEncryptionJson(
|
|
@@ -3255,25 +3903,35 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3255
3903
|
},
|
|
3256
3904
|
transport,
|
|
3257
3905
|
node: {
|
|
3258
|
-
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
|
+
}
|
|
3259
3912
|
},
|
|
3260
3913
|
wellKnown: {
|
|
3261
3914
|
fetchWellKnown: async (principal, discoveryKey) => {
|
|
3262
|
-
|
|
3915
|
+
graph.assertActive();
|
|
3916
|
+
if (!address || !didPrincipalMatches2(principal, did)) {
|
|
3263
3917
|
return null;
|
|
3264
3918
|
}
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
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) {
|
|
3271
3927
|
return null;
|
|
3272
3928
|
}
|
|
3273
|
-
const body =
|
|
3929
|
+
const body = await response.json();
|
|
3930
|
+
graph.assertActive();
|
|
3274
3931
|
return "descriptor" in body && body.descriptor ? body.descriptor : body;
|
|
3275
3932
|
}
|
|
3276
|
-
}
|
|
3933
|
+
},
|
|
3934
|
+
assertActive: () => graph.assertActive()
|
|
3277
3935
|
});
|
|
3278
3936
|
}
|
|
3279
3937
|
getEncryptionService() {
|
|
@@ -3287,8 +3945,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3287
3945
|
}
|
|
3288
3946
|
return this._encryption;
|
|
3289
3947
|
}
|
|
3290
|
-
createVaultService(spaceId, kv) {
|
|
3948
|
+
createVaultService(spaceId, kv, encryptionService = this.getEncryptionService(), nodeContext) {
|
|
3291
3949
|
const wasm = this.wasmBindings;
|
|
3950
|
+
const serviceGraph = nodeContext?.serviceGraph ?? this._serviceGraph;
|
|
3292
3951
|
const vaultCrypto = createVaultCrypto({
|
|
3293
3952
|
vault_encrypt: wasm.vault_encrypt,
|
|
3294
3953
|
vault_decrypt: wasm.vault_decrypt,
|
|
@@ -3299,12 +3958,17 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3299
3958
|
vault_sha256: wasm.vault_sha256
|
|
3300
3959
|
});
|
|
3301
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;
|
|
3302
3966
|
return new DataVaultService({
|
|
3303
3967
|
spaceId,
|
|
3304
3968
|
crypto: vaultCrypto,
|
|
3305
3969
|
encryption: {
|
|
3306
|
-
networkId:
|
|
3307
|
-
service:
|
|
3970
|
+
networkId: `urn:tinycloud:encryption:${ownerDid}:${DEFAULT_ENCRYPTION_NETWORK_NAME}`,
|
|
3971
|
+
service: encryptionService,
|
|
3308
3972
|
decryptCapabilityProof: () => ({
|
|
3309
3973
|
proofs: [this.requireServiceSession().delegationCid]
|
|
3310
3974
|
})
|
|
@@ -3313,6 +3977,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3313
3977
|
kv,
|
|
3314
3978
|
ensurePublicSpace: async () => {
|
|
3315
3979
|
try {
|
|
3980
|
+
serviceGraph.assertActive();
|
|
3316
3981
|
await self.ensurePublicSpace();
|
|
3317
3982
|
return { ok: true, data: void 0 };
|
|
3318
3983
|
} catch (error) {
|
|
@@ -3320,14 +3985,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3320
3985
|
}
|
|
3321
3986
|
},
|
|
3322
3987
|
get publicKV() {
|
|
3988
|
+
serviceGraph.assertActive();
|
|
3323
3989
|
return self._publicKV ?? self.tc.publicKV;
|
|
3324
3990
|
},
|
|
3325
|
-
readPublicSpace: (
|
|
3991
|
+
readPublicSpace: (host2, targetSpaceId, key) => TinyCloud.readPublicSpace(host2, targetSpaceId, key),
|
|
3326
3992
|
makePublicSpaceId: TinyCloud.makePublicSpaceId,
|
|
3327
|
-
did
|
|
3328
|
-
address:
|
|
3329
|
-
chainId
|
|
3330
|
-
hosts: [
|
|
3993
|
+
did,
|
|
3994
|
+
address: address ?? "",
|
|
3995
|
+
chainId,
|
|
3996
|
+
hosts: [host]
|
|
3331
3997
|
}
|
|
3332
3998
|
});
|
|
3333
3999
|
}
|
|
@@ -3336,6 +4002,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3336
4002
|
* @internal
|
|
3337
4003
|
*/
|
|
3338
4004
|
initializeV2Services(serviceSession) {
|
|
4005
|
+
const graph = this._serviceGraph;
|
|
3339
4006
|
this._capabilityRegistry = new CapabilityKeyRegistry();
|
|
3340
4007
|
const tcSession = this.auth?.tinyCloudSession;
|
|
3341
4008
|
if (tcSession && this._address) {
|
|
@@ -3378,22 +4045,25 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3378
4045
|
}
|
|
3379
4046
|
this._delegationManager = new DelegationManager({
|
|
3380
4047
|
hosts: [this.config.host],
|
|
4048
|
+
accountSpaceId: this.accountSpaceId,
|
|
3381
4049
|
session: serviceSession,
|
|
3382
|
-
invoke:
|
|
3383
|
-
invokeAny:
|
|
3384
|
-
fetch:
|
|
4050
|
+
invoke: graph.invoke,
|
|
4051
|
+
invokeAny: graph.invokeAny,
|
|
4052
|
+
fetch: graph.fetch
|
|
3385
4053
|
});
|
|
3386
4054
|
this._spaceService = new SpaceService({
|
|
3387
4055
|
hosts: [this.config.host],
|
|
3388
4056
|
session: serviceSession,
|
|
3389
|
-
invoke:
|
|
3390
|
-
fetch:
|
|
4057
|
+
invoke: graph.invoke,
|
|
4058
|
+
fetch: graph.fetch,
|
|
3391
4059
|
capabilityRegistry: this._capabilityRegistry,
|
|
3392
4060
|
userDid: this.did,
|
|
3393
4061
|
createKVService: (spaceId) => {
|
|
4062
|
+
graph.assertActive();
|
|
3394
4063
|
return this.createSpaceScopedKVService(spaceId);
|
|
3395
4064
|
},
|
|
3396
4065
|
createVaultService: (spaceId) => {
|
|
4066
|
+
graph.assertActive();
|
|
3397
4067
|
const kvService = this.createSpaceScopedKVService(spaceId);
|
|
3398
4068
|
const vaultService = this.createVaultService(spaceId, kvService);
|
|
3399
4069
|
if (this._serviceContext) {
|
|
@@ -3402,11 +4072,13 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3402
4072
|
return vaultService;
|
|
3403
4073
|
},
|
|
3404
4074
|
createSecretsService: (spaceId) => {
|
|
4075
|
+
graph.assertActive();
|
|
3405
4076
|
return this.secretsForSpace(spaceId);
|
|
3406
4077
|
},
|
|
3407
4078
|
// Enable space.delegations.create() via SIWE-based delegation
|
|
3408
4079
|
createDelegation: async (params) => {
|
|
3409
4080
|
try {
|
|
4081
|
+
graph.assertActive();
|
|
3410
4082
|
const portableDelegation = await this.createDelegation({
|
|
3411
4083
|
delegateDID: params.delegateDID,
|
|
3412
4084
|
path: params.path,
|
|
@@ -3440,18 +4112,50 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3440
4112
|
}
|
|
3441
4113
|
},
|
|
3442
4114
|
onSpaceRegistered: async (space) => {
|
|
4115
|
+
graph.assertActive();
|
|
3443
4116
|
await this.account.spaces.register(space);
|
|
3444
4117
|
}
|
|
3445
4118
|
});
|
|
3446
|
-
this._sharingService
|
|
4119
|
+
this._sharingService = new SharingService({
|
|
4120
|
+
hosts: [this.config.host],
|
|
3447
4121
|
session: serviceSession,
|
|
4122
|
+
invoke: graph.invoke,
|
|
4123
|
+
fetch: graph.fetch,
|
|
4124
|
+
assertActive: () => graph.assertActive(),
|
|
4125
|
+
keyProvider: this._keyProvider,
|
|
4126
|
+
registry: this._capabilityRegistry,
|
|
3448
4127
|
delegationManager: this._delegationManager,
|
|
3449
4128
|
sessionExpiry: this.getSessionExpiry(),
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
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
|
|
3455
4159
|
});
|
|
3456
4160
|
this._spaceService.updateConfig({
|
|
3457
4161
|
sharingService: this._sharingService
|
|
@@ -3518,7 +4222,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3518
4222
|
* with expiry longer than the current session.
|
|
3519
4223
|
* @internal
|
|
3520
4224
|
*/
|
|
3521
|
-
async createOwnerDelegation(params) {
|
|
4225
|
+
async createOwnerDelegation(params, assertActive) {
|
|
4226
|
+
const assertOwnerGraphActive = assertActive ?? this._serviceGraph.assertActive.bind(this._serviceGraph);
|
|
4227
|
+
assertOwnerGraphActive();
|
|
3522
4228
|
if (!params.delegateDid.startsWith("did:key:") || params.actions.length === 0 || params.path.length === 0) {
|
|
3523
4229
|
throw new Error("Owner delegation requires an external did:key audience and bounded capabilities");
|
|
3524
4230
|
}
|
|
@@ -3543,8 +4249,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3543
4249
|
delegateUri: params.delegateDid
|
|
3544
4250
|
});
|
|
3545
4251
|
const signature = await this.signer.signMessage(prepared.siwe);
|
|
4252
|
+
assertOwnerGraphActive();
|
|
3546
4253
|
const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
|
|
3547
4254
|
const activation = await activateSessionWithHost2(host, delegationSession.delegationHeader);
|
|
4255
|
+
assertOwnerGraphActive();
|
|
3548
4256
|
if (!activation.success) {
|
|
3549
4257
|
throw new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim());
|
|
3550
4258
|
}
|
|
@@ -3572,7 +4280,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3572
4280
|
}
|
|
3573
4281
|
};
|
|
3574
4282
|
}
|
|
3575
|
-
async createRootDelegationForSharing(params) {
|
|
4283
|
+
async createRootDelegationForSharing(params, assertActive) {
|
|
3576
4284
|
try {
|
|
3577
4285
|
return (await this.createOwnerDelegation({
|
|
3578
4286
|
delegateDid: params.shareKeyDID,
|
|
@@ -3580,8 +4288,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3580
4288
|
path: params.path,
|
|
3581
4289
|
actions: params.actions,
|
|
3582
4290
|
expiresAt: params.requestedExpiry
|
|
3583
|
-
})).delegation;
|
|
4291
|
+
}, assertActive)).delegation;
|
|
3584
4292
|
} catch {
|
|
4293
|
+
assertActive?.();
|
|
3585
4294
|
return void 0;
|
|
3586
4295
|
}
|
|
3587
4296
|
}
|
|
@@ -3653,13 +4362,13 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3653
4362
|
throw new Error("Not signed in. Call signIn() first.");
|
|
3654
4363
|
}
|
|
3655
4364
|
const sql = new SQLService2({});
|
|
3656
|
-
const spaceScopedContext = new ServiceContext2({
|
|
4365
|
+
const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
|
|
3657
4366
|
invoke: this._serviceContext.invoke,
|
|
3658
4367
|
invokeAny: this._serviceContext.invokeAny,
|
|
3659
4368
|
fetch: this._serviceContext.fetch,
|
|
3660
4369
|
hosts: this._serviceContext.hosts,
|
|
3661
4370
|
telemetry: this.config.telemetry
|
|
3662
|
-
});
|
|
4371
|
+
}));
|
|
3663
4372
|
spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
|
|
3664
4373
|
sql.initialize(spaceScopedContext);
|
|
3665
4374
|
return sql;
|
|
@@ -3683,12 +4392,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3683
4392
|
throw new Error("Not signed in. Call signIn() first.");
|
|
3684
4393
|
}
|
|
3685
4394
|
const kv = new KVService2({});
|
|
3686
|
-
const spaceScopedContext = new ServiceContext2({
|
|
4395
|
+
const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
|
|
3687
4396
|
invoke: this._serviceContext.invoke,
|
|
3688
4397
|
invokeAny: this._serviceContext.invokeAny,
|
|
3689
4398
|
fetch: this._serviceContext.fetch,
|
|
3690
4399
|
hosts: this._serviceContext.hosts
|
|
3691
|
-
});
|
|
4400
|
+
}));
|
|
3692
4401
|
spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
|
|
3693
4402
|
kv.initialize(spaceScopedContext);
|
|
3694
4403
|
return kv;
|
|
@@ -3724,19 +4433,11 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3724
4433
|
}
|
|
3725
4434
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
3726
4435
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
3727
|
-
|
|
3728
|
-
|
|
4436
|
+
return this.fetchEncryptionNetworkAt(
|
|
4437
|
+
this.config.host,
|
|
4438
|
+
networkId,
|
|
4439
|
+
globalThis.fetch.bind(globalThis)
|
|
3729
4440
|
);
|
|
3730
|
-
if (response.status === 404) {
|
|
3731
|
-
return null;
|
|
3732
|
-
}
|
|
3733
|
-
if (!response.ok) {
|
|
3734
|
-
throw new Error(
|
|
3735
|
-
`Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
|
|
3736
|
-
);
|
|
3737
|
-
}
|
|
3738
|
-
const body = await response.json();
|
|
3739
|
-
return "descriptor" in body && body.descriptor ? body.descriptor : body;
|
|
3740
4441
|
}
|
|
3741
4442
|
async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
|
|
3742
4443
|
const targetNode = await this.fetchNodeId();
|
|
@@ -3830,6 +4531,38 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3830
4531
|
}
|
|
3831
4532
|
return secrets;
|
|
3832
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
|
+
}
|
|
3833
4566
|
getBaseSecrets(spaceId) {
|
|
3834
4567
|
if (!this._spaceService) {
|
|
3835
4568
|
throw new Error("Not signed in. Call signIn() first.");
|
|
@@ -3837,16 +4570,29 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3837
4570
|
const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
|
|
3838
4571
|
let secrets = this._baseSecrets.get(resolvedSpace);
|
|
3839
4572
|
if (!secrets) {
|
|
3840
|
-
|
|
3841
|
-
const vaultService = this.createVaultService(resolvedSpace, kvService);
|
|
3842
|
-
if (this._serviceContext) {
|
|
3843
|
-
vaultService.initialize(this._serviceContext);
|
|
3844
|
-
}
|
|
3845
|
-
secrets = new SecretsService(() => vaultService);
|
|
4573
|
+
secrets = new SecretsService(() => this.getBaseVault(resolvedSpace));
|
|
3846
4574
|
this._baseSecrets.set(resolvedSpace, secrets);
|
|
3847
4575
|
}
|
|
3848
4576
|
return secrets;
|
|
3849
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
|
+
}
|
|
3850
4596
|
/**
|
|
3851
4597
|
* Hooks write stream subscription API.
|
|
3852
4598
|
*/
|
|
@@ -3951,6 +4697,63 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3951
4697
|
(grant) => grant.delegation
|
|
3952
4698
|
);
|
|
3953
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
|
+
}
|
|
3954
4757
|
/**
|
|
3955
4758
|
* Install a portable runtime permission delegation into this SDK instance so
|
|
3956
4759
|
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
@@ -4019,6 +4822,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4019
4822
|
"grantRuntimePermissions requires wallet mode with a signer or privateKey."
|
|
4020
4823
|
);
|
|
4021
4824
|
}
|
|
4825
|
+
this.assertDelegationCaveatsPreservable(expanded);
|
|
4022
4826
|
const rawEntries = expanded.filter(
|
|
4023
4827
|
(entry) => this.isEncryptionPermissionEntry(entry)
|
|
4024
4828
|
);
|
|
@@ -4280,12 +5084,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4280
5084
|
}
|
|
4281
5085
|
if (this._serviceContext) {
|
|
4282
5086
|
const publicKV = new KVService2({ prefix: "" });
|
|
4283
|
-
const publicContext = new ServiceContext2({
|
|
4284
|
-
invoke: this.
|
|
4285
|
-
fetch: this.
|
|
5087
|
+
const publicContext = this._serviceGraph.track(new ServiceContext2({
|
|
5088
|
+
invoke: this._serviceGraph.invoke,
|
|
5089
|
+
fetch: this._serviceGraph.fetch,
|
|
4286
5090
|
hosts: this._serviceContext.hosts,
|
|
4287
5091
|
telemetry: this.config.telemetry
|
|
4288
|
-
});
|
|
5092
|
+
}));
|
|
4289
5093
|
publicContext.setSession({
|
|
4290
5094
|
delegationHeader: delegationSession.delegationHeader,
|
|
4291
5095
|
delegationCid: delegationSession.delegationCid,
|
|
@@ -4352,13 +5156,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4352
5156
|
async getDelegationStatus(cid) {
|
|
4353
5157
|
return this.delegationManager.status(cid);
|
|
4354
5158
|
}
|
|
4355
|
-
/** Compute the canonical CID of a compact delegation authorization. */
|
|
5159
|
+
/** Compute the canonical CID of a compact UCAN or DAG-CBOR delegation authorization. */
|
|
4356
5160
|
computeDelegationCid(authorization) {
|
|
4357
5161
|
if (!authorization || !this.wasmBindings.computeCid) {
|
|
4358
5162
|
throw new Error("Delegation CID computation is unavailable");
|
|
4359
5163
|
}
|
|
5164
|
+
const compact = authorization.replace(/^Bearer /i, "");
|
|
4360
5165
|
return this.wasmBindings.computeCid(
|
|
4361
|
-
new TextEncoder().encode(authorization),
|
|
5166
|
+
compact.includes(".") ? new TextEncoder().encode(compact) : decodeAuthorizationBytes(authorization),
|
|
4362
5167
|
0x55n
|
|
4363
5168
|
);
|
|
4364
5169
|
}
|
|
@@ -4450,6 +5255,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4450
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."
|
|
4451
5256
|
);
|
|
4452
5257
|
}
|
|
5258
|
+
this.assertDelegationCaveatsPreservable(expandedEntries);
|
|
4453
5259
|
const delegation2 = await this.createDelegationLegacyWalletPath(
|
|
4454
5260
|
did,
|
|
4455
5261
|
expandedEntries[0],
|
|
@@ -4458,13 +5264,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4458
5264
|
return { delegation: delegation2, prompted: true };
|
|
4459
5265
|
}
|
|
4460
5266
|
const granted = parseRecapCapabilities(
|
|
4461
|
-
(siwe) => this.
|
|
5267
|
+
(siwe) => this.parseRecapWithCaveats(siwe),
|
|
4462
5268
|
session.siwe
|
|
4463
5269
|
);
|
|
4464
5270
|
const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
|
|
4465
5271
|
if (!subset) {
|
|
5272
|
+
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
4466
5273
|
const runtimeGrant = this.findGrantForOperations(
|
|
4467
|
-
|
|
5274
|
+
runtimeOperations,
|
|
4468
5275
|
{ excludePrimary: true }
|
|
4469
5276
|
);
|
|
4470
5277
|
if (runtimeGrant) {
|
|
@@ -4477,7 +5284,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4477
5284
|
did,
|
|
4478
5285
|
expandedEntries,
|
|
4479
5286
|
runtimeExpiration,
|
|
4480
|
-
runtimeGrant
|
|
5287
|
+
runtimeGrant,
|
|
5288
|
+
runtimeOperations
|
|
4481
5289
|
);
|
|
4482
5290
|
return { delegation: delegation2, prompted: false };
|
|
4483
5291
|
}
|
|
@@ -4547,6 +5355,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4547
5355
|
"createDelegationViaWasmPath requires a non-empty entries array"
|
|
4548
5356
|
);
|
|
4549
5357
|
}
|
|
5358
|
+
this.assertDelegationCaveatsPreservable(entries);
|
|
4550
5359
|
const resolvedSpaces = /* @__PURE__ */ new Set();
|
|
4551
5360
|
for (const entry of entries) {
|
|
4552
5361
|
if (this.isEncryptionPermissionEntry(entry)) {
|
|
@@ -4627,7 +5436,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4627
5436
|
host: this.config.host
|
|
4628
5437
|
};
|
|
4629
5438
|
}
|
|
4630
|
-
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
|
|
5439
|
+
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations) {
|
|
5440
|
+
this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
|
|
5441
|
+
this.assertDelegationCaveatsPreservable(entries);
|
|
4631
5442
|
const result = this.createDelegationWrapper({
|
|
4632
5443
|
session: grant.session,
|
|
4633
5444
|
delegateDID: did,
|
|
@@ -4681,6 +5492,74 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4681
5492
|
expandPermissionEntries(permissions) {
|
|
4682
5493
|
return expandPermissionEntriesCore(permissions);
|
|
4683
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
|
+
}
|
|
4684
5563
|
shortServiceName(service) {
|
|
4685
5564
|
const short = SERVICE_LONG_TO_SHORT[service];
|
|
4686
5565
|
if (short === void 0) {
|
|
@@ -4735,14 +5614,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4735
5614
|
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
4736
5615
|
service,
|
|
4737
5616
|
path: entry.path,
|
|
4738
|
-
action
|
|
5617
|
+
action,
|
|
5618
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
4739
5619
|
}));
|
|
4740
5620
|
});
|
|
4741
5621
|
}
|
|
4742
5622
|
sessionCoversPermissionEntries(session, entries) {
|
|
4743
5623
|
try {
|
|
4744
5624
|
const granted = parseRecapCapabilities(
|
|
4745
|
-
(siwe) => this.
|
|
5625
|
+
(siwe) => this.parseRecapWithCaveats(siwe),
|
|
4746
5626
|
session.siwe
|
|
4747
5627
|
);
|
|
4748
5628
|
return isCapabilitySubset(entries, granted).subset;
|
|
@@ -4758,7 +5638,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4758
5638
|
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
|
|
4759
5639
|
service,
|
|
4760
5640
|
path: entry.path,
|
|
4761
|
-
action
|
|
5641
|
+
action,
|
|
5642
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
4762
5643
|
}));
|
|
4763
5644
|
});
|
|
4764
5645
|
}
|
|
@@ -4834,7 +5715,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4834
5715
|
service: this.shortServiceName(entry.service),
|
|
4835
5716
|
space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
|
|
4836
5717
|
path: entry.path,
|
|
4837
|
-
actions: [...entry.actions]
|
|
5718
|
+
actions: [...entry.actions],
|
|
5719
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
4838
5720
|
}));
|
|
4839
5721
|
}
|
|
4840
5722
|
operationsFromDelegation(delegation) {
|
|
@@ -4845,7 +5727,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4845
5727
|
...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
|
|
4846
5728
|
service,
|
|
4847
5729
|
path: resource.path,
|
|
4848
|
-
action
|
|
5730
|
+
action,
|
|
5731
|
+
caveats: cloneRecapCaveats(resource.caveats)
|
|
4849
5732
|
}));
|
|
4850
5733
|
});
|
|
4851
5734
|
}
|
|
@@ -4861,7 +5744,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4861
5744
|
service,
|
|
4862
5745
|
space: delegation.spaceId,
|
|
4863
5746
|
path: delegation.path,
|
|
4864
|
-
actions
|
|
5747
|
+
actions,
|
|
5748
|
+
...delegation.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(delegation.caveats) }
|
|
4865
5749
|
}));
|
|
4866
5750
|
}
|
|
4867
5751
|
/**
|
|
@@ -4962,6 +5846,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4962
5846
|
if (granted.service !== requested.service || !this.actionContains(granted.action, requested.action)) {
|
|
4963
5847
|
return false;
|
|
4964
5848
|
}
|
|
5849
|
+
if (requested.caveats !== void 0 && !recapCaveatsEqual(granted.caveats, requested.caveats)) {
|
|
5850
|
+
return false;
|
|
5851
|
+
}
|
|
4965
5852
|
if (granted.resource !== void 0 || requested.resource !== void 0) {
|
|
4966
5853
|
return granted.resource !== void 0 && requested.resource !== void 0 && granted.resource === requested.resource && this.pathContains(granted.path, requested.path);
|
|
4967
5854
|
}
|
|
@@ -4996,6 +5883,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4996
5883
|
invocationServiceName(service) {
|
|
4997
5884
|
return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
|
|
4998
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
|
+
}
|
|
4999
5890
|
isEncryptionNetworkOperation(service, path) {
|
|
5000
5891
|
return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
|
|
5001
5892
|
}
|
|
@@ -5006,7 +5897,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5006
5897
|
resource: entry.resource,
|
|
5007
5898
|
service,
|
|
5008
5899
|
path: entry.path,
|
|
5009
|
-
action: entry.action
|
|
5900
|
+
action: entry.action,
|
|
5901
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
5010
5902
|
};
|
|
5011
5903
|
}
|
|
5012
5904
|
if (this.isEncryptionNetworkOperation(service, entry.path)) {
|
|
@@ -5014,7 +5906,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5014
5906
|
resource: entry.path,
|
|
5015
5907
|
service,
|
|
5016
5908
|
path: entry.path,
|
|
5017
|
-
action: entry.action
|
|
5909
|
+
action: entry.action,
|
|
5910
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
5018
5911
|
};
|
|
5019
5912
|
}
|
|
5020
5913
|
if (typeof entry.spaceId === "string") {
|
|
@@ -5022,7 +5915,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5022
5915
|
spaceId: entry.spaceId,
|
|
5023
5916
|
service,
|
|
5024
5917
|
path: entry.path,
|
|
5025
|
-
action: entry.action
|
|
5918
|
+
action: entry.action,
|
|
5919
|
+
...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
|
|
5026
5920
|
};
|
|
5027
5921
|
}
|
|
5028
5922
|
return void 0;
|
|
@@ -5362,6 +6256,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5362
6256
|
* @returns A portable delegation for the sub-delegate
|
|
5363
6257
|
*/
|
|
5364
6258
|
async createSubDelegation(parentDelegation, params) {
|
|
6259
|
+
this.assertPortableDelegationCaveatsPreservable(parentDelegation);
|
|
5365
6260
|
if (!this.signer) {
|
|
5366
6261
|
throw new Error("Cannot createSubDelegation() in session-only mode. Requires wallet mode.");
|
|
5367
6262
|
}
|
|
@@ -5460,6 +6355,7 @@ import {
|
|
|
5460
6355
|
DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
|
|
5461
6356
|
DEFAULT_MANIFEST_VERSION,
|
|
5462
6357
|
VAULT_PERMISSION_SERVICE,
|
|
6358
|
+
CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
|
|
5463
6359
|
PermissionNotInManifestError as PermissionNotInManifestError2,
|
|
5464
6360
|
SessionExpiredError as SessionExpiredError2,
|
|
5465
6361
|
ManifestValidationError,
|
|
@@ -5476,6 +6372,10 @@ import {
|
|
|
5476
6372
|
} from "@tinycloud/sdk-core";
|
|
5477
6373
|
|
|
5478
6374
|
// src/delegation.ts
|
|
6375
|
+
import {
|
|
6376
|
+
canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
|
|
6377
|
+
principalDidEquals as principalDidEquals3
|
|
6378
|
+
} from "@tinycloud/sdk-core";
|
|
5479
6379
|
function serializeDelegation(delegation) {
|
|
5480
6380
|
return JSON.stringify({
|
|
5481
6381
|
...delegation,
|
|
@@ -5512,7 +6412,7 @@ import {
|
|
|
5512
6412
|
SECRET_NAME_RE,
|
|
5513
6413
|
canonicalizeSecretScope,
|
|
5514
6414
|
resolveSecretListPrefix as resolveSecretListPrefix2,
|
|
5515
|
-
resolveSecretPath as
|
|
6415
|
+
resolveSecretPath as resolveSecretPath3
|
|
5516
6416
|
} from "@tinycloud/sdk-core";
|
|
5517
6417
|
import {
|
|
5518
6418
|
DelegationManager as DelegationManager2,
|
|
@@ -5548,6 +6448,7 @@ export {
|
|
|
5548
6448
|
AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
|
|
5549
6449
|
CapabilityKeyRegistry2 as CapabilityKeyRegistry,
|
|
5550
6450
|
CapabilityKeyRegistryErrorCodes,
|
|
6451
|
+
CaveatedDelegationUnsupportedError2 as CaveatedDelegationUnsupportedError,
|
|
5551
6452
|
DEFAULT_MANIFEST_SPACE2 as DEFAULT_MANIFEST_SPACE,
|
|
5552
6453
|
DEFAULT_MANIFEST_VERSION,
|
|
5553
6454
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
@@ -5583,6 +6484,7 @@ export {
|
|
|
5583
6484
|
TinyCloudDebugLogger,
|
|
5584
6485
|
TinyCloudNode,
|
|
5585
6486
|
UnsupportedFeatureError2 as UnsupportedFeatureError,
|
|
6487
|
+
UnsupportedSessionRestoreError,
|
|
5586
6488
|
VAULT_PERMISSION_SERVICE,
|
|
5587
6489
|
VaultHeaders,
|
|
5588
6490
|
VaultPublicSpaceKVActions,
|
|
@@ -5627,10 +6529,10 @@ export {
|
|
|
5627
6529
|
parseSpaceUri,
|
|
5628
6530
|
pkhDid3 as pkhDid,
|
|
5629
6531
|
principalDid,
|
|
5630
|
-
|
|
6532
|
+
principalDidEquals4 as principalDidEquals,
|
|
5631
6533
|
resolveManifest2 as resolveManifest,
|
|
5632
6534
|
resolveSecretListPrefix2 as resolveSecretListPrefix,
|
|
5633
|
-
|
|
6535
|
+
resolveSecretPath3 as resolveSecretPath,
|
|
5634
6536
|
resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
|
|
5635
6537
|
serializeDelegation,
|
|
5636
6538
|
tinyCloudDebugLogger,
|