@tinycloud/node-sdk 3.8.0-beta.1 → 3.8.0-beta.11
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/{TinyCloudNode-Ckqu_fqH.d.ts → TinyCloudNode-BVDUdEcf.d.cts} +53 -5
- package/dist/{TinyCloudNode-DDfx8Fx5.d.cts → TinyCloudNode-jMK_2Uj8.d.ts} +53 -5
- package/dist/{authority-BwM_s3Ya.d.cts → authority-DJ-PE4Xo.d.cts} +20 -4
- package/dist/{authority-DcyJpajL.d.ts → authority-i22vHbbp.d.ts} +20 -4
- package/dist/core.cjs +300 -32
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +5 -4
- package/dist/core.d.ts +5 -4
- package/dist/core.js +292 -27
- package/dist/core.js.map +1 -1
- package/dist/credentials-core.d.cts +1 -1
- package/dist/credentials-core.d.ts +1 -1
- package/dist/credentials.d.cts +1 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/{delegation-0LKZkmP2.d.cts → delegation-Ckc3UETl.d.cts} +1 -1
- package/dist/{delegation-0LKZkmP2.d.ts → delegation-Ckc3UETl.d.ts} +1 -1
- package/dist/encryption-keys.d.cts +2 -2
- package/dist/encryption-keys.d.ts +2 -2
- package/dist/index.cjs +304 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +291 -31
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.cjs +39 -0
- package/dist/replication/authority.cjs.map +1 -1
- package/dist/replication/authority.d.cts +4 -3
- package/dist/replication/authority.d.ts +4 -3
- package/dist/replication/authority.js +38 -0
- package/dist/replication/authority.js.map +1 -1
- package/dist/replication/runtime.cjs +1 -0
- package/dist/replication/runtime.cjs.map +1 -1
- package/dist/replication/runtime.d.cts +1 -1
- package/dist/replication/runtime.d.ts +1 -1
- package/dist/replication/runtime.js +1 -0
- package/dist/replication/runtime.js.map +1 -1
- package/dist/{runtime-v62FzQHz.d.ts → runtime-CO4VXnPq.d.ts} +2 -1
- package/dist/{runtime-D1o9Eybt.d.cts → runtime-mRhSu-vJ.d.cts} +2 -1
- package/package.json +4 -4
package/dist/core.cjs
CHANGED
|
@@ -58,6 +58,7 @@ __export(core_exports, {
|
|
|
58
58
|
PermissionNotInManifestError: () => import_sdk_core14.PermissionNotInManifestError,
|
|
59
59
|
PrefixedKVService: () => import_sdk_core15.PrefixedKVService,
|
|
60
60
|
ProtocolMismatchError: () => import_sdk_core22.ProtocolMismatchError,
|
|
61
|
+
RuntimeDelegationRejectedError: () => RuntimeDelegationRejectedError,
|
|
61
62
|
SECRET_NAME_RE: () => import_sdk_core18.SECRET_NAME_RE,
|
|
62
63
|
SQLAction: () => import_sdk_core16.SQLAction,
|
|
63
64
|
SQLService: () => import_sdk_core16.SQLService,
|
|
@@ -80,6 +81,7 @@ __export(core_exports, {
|
|
|
80
81
|
VersionCheckError: () => import_sdk_core22.VersionCheckError,
|
|
81
82
|
WasmKeyProvider: () => WasmKeyProvider,
|
|
82
83
|
activateCompactRuntimeDelegation: () => activateCompactRuntimeDelegation,
|
|
84
|
+
activateValidatedRuntimeDelegation: () => activateValidatedRuntimeDelegation,
|
|
83
85
|
addressStorageKey: () => import_sdk_core13.addressStorageKey,
|
|
84
86
|
buildSpaceUri: () => import_sdk_core21.buildSpaceUri,
|
|
85
87
|
canonicalizeAddress: () => import_sdk_core13.canonicalizeAddress,
|
|
@@ -130,7 +132,8 @@ __export(core_exports, {
|
|
|
130
132
|
resourceCapabilitiesToSpaceAbilitiesMap: () => import_sdk_core14.resourceCapabilitiesToSpaceAbilitiesMap,
|
|
131
133
|
serializeDelegation: () => serializeDelegation,
|
|
132
134
|
tinyCloudDebugLogger: () => import_sdk_core13.tinyCloudDebugLogger,
|
|
133
|
-
validateManifest: () => import_sdk_core14.validateManifest
|
|
135
|
+
validateManifest: () => import_sdk_core14.validateManifest,
|
|
136
|
+
wireOpenKeyAuthorize: () => wireOpenKeyAuthorize
|
|
134
137
|
});
|
|
135
138
|
module.exports = __toCommonJS(core_exports);
|
|
136
139
|
var import_sdk_core12 = require("@tinycloud/sdk-core");
|
|
@@ -345,6 +348,32 @@ var FileSessionStorage = class {
|
|
|
345
348
|
var import_node_crypto = require("crypto");
|
|
346
349
|
var import_siwe = require("siwe");
|
|
347
350
|
var import_sdk_core5 = require("@tinycloud/sdk-core");
|
|
351
|
+
var import_resource = require("@tinycloud/sdk-services/encryption/resource");
|
|
352
|
+
var import_sdk_services2 = require("@tinycloud/sdk-services");
|
|
353
|
+
|
|
354
|
+
// src/authorization/appKeyHostBinding.ts
|
|
355
|
+
var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
356
|
+
var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
|
|
357
|
+
var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
358
|
+
function appKeyHostBinding(domain, appId) {
|
|
359
|
+
if (!APP_ID.test(appId)) return false;
|
|
360
|
+
if (domain.endsWith(":")) return false;
|
|
361
|
+
let url;
|
|
362
|
+
try {
|
|
363
|
+
url = new URL(`https://${domain}`);
|
|
364
|
+
} catch {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
|
|
368
|
+
if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
|
|
369
|
+
const host = url.hostname;
|
|
370
|
+
if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
|
|
371
|
+
const labels = host.split(".");
|
|
372
|
+
if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
|
|
373
|
+
if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
|
|
374
|
+
const reversed = labels.reverse().join(".");
|
|
375
|
+
return appId === reversed || appId.startsWith(`${reversed}.`);
|
|
376
|
+
}
|
|
348
377
|
|
|
349
378
|
// src/replication/authority-sign-in.ts
|
|
350
379
|
var import_sdk_core2 = require("@tinycloud/sdk-core");
|
|
@@ -476,6 +505,7 @@ function cloneAbilitiesMap(actions) {
|
|
|
476
505
|
var NodeUserAuthorization = class {
|
|
477
506
|
constructor(config) {
|
|
478
507
|
this.activeSessionKeyId = "default";
|
|
508
|
+
this.preparedSessionCounter = 0;
|
|
479
509
|
/** Incremented by resetSession; a sign-in started earlier publishes nothing. */
|
|
480
510
|
this.sessionGeneration = 0;
|
|
481
511
|
/**
|
|
@@ -832,6 +862,23 @@ var NodeUserAuthorization = class {
|
|
|
832
862
|
);
|
|
833
863
|
return this._capabilityRequest;
|
|
834
864
|
}
|
|
865
|
+
async refreshNamedKeyFeatureForSignIn(address, chainId) {
|
|
866
|
+
const request = this.getCapabilityRequest();
|
|
867
|
+
const siweDomain = this.siweConfig?.domain ?? this.domain;
|
|
868
|
+
const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
|
|
869
|
+
if (hasNamedKeys) {
|
|
870
|
+
await this.resolveTinyCloudHostsForSignIn(address, chainId);
|
|
871
|
+
await this.refreshNodeInfo();
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
/** Manifest key paths that this sign-in cannot grant. */
|
|
875
|
+
get unavailableKeyPaths() {
|
|
876
|
+
const request = this.getCapabilityRequest();
|
|
877
|
+
const siweDomain = this.siweConfig?.domain ?? this.domain;
|
|
878
|
+
return (request?.manifests ?? []).flatMap(
|
|
879
|
+
(manifest) => this._nodeFeatures.includes("encryption-named-keys-v1") && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id) ? [] : (manifest.keys ?? []).map((entry) => "path" in entry ? entry.path : entry.prefix)
|
|
880
|
+
);
|
|
881
|
+
}
|
|
835
882
|
resolveSpaceName(space, address, chainId) {
|
|
836
883
|
if (space.startsWith("tinycloud:")) {
|
|
837
884
|
return space;
|
|
@@ -1023,15 +1070,31 @@ var NodeUserAuthorization = class {
|
|
|
1023
1070
|
}
|
|
1024
1071
|
const rawAbilities = {};
|
|
1025
1072
|
const currentDid = (0, import_sdk_core5.pkhDid)(address, chainId);
|
|
1073
|
+
if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
|
|
1074
|
+
const siweDomain = this.siweConfig?.domain ?? this.domain;
|
|
1075
|
+
for (const manifest of request.manifests) {
|
|
1076
|
+
if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
|
|
1077
|
+
for (const declaration of manifest.keys ?? []) {
|
|
1078
|
+
const path = "path" in declaration ? declaration.path : declaration.prefix;
|
|
1079
|
+
const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
|
|
1080
|
+
for (const ability of declaration.abilities) {
|
|
1081
|
+
addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1026
1086
|
const spaceResources = request.resources.filter((entry) => {
|
|
1027
1087
|
if (entry.service !== import_sdk_core5.ENCRYPTION_PERMISSION_SERVICE) {
|
|
1028
1088
|
return true;
|
|
1029
1089
|
}
|
|
1090
|
+
const parsed = entry.path.includes("/") ? (0, import_resource.parseEncryptionResource)(entry.path) : { ...(0, import_sdk_services2.parseNetworkId)(entry.path), kind: "network" };
|
|
1091
|
+
if (parsed.kind !== "network") {
|
|
1092
|
+
throw new import_sdk_core5.ManifestValidationError("named keys go in manifest.keys");
|
|
1093
|
+
}
|
|
1030
1094
|
for (const action of entry.actions) {
|
|
1031
1095
|
addRawAbility(rawAbilities, entry.path, action);
|
|
1032
1096
|
}
|
|
1033
|
-
if (entry.actions.includes(DECRYPT_ACTION)) {
|
|
1034
|
-
const parsed = (0, import_sdk_core5.parseNetworkId)(entry.path);
|
|
1097
|
+
if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
|
|
1035
1098
|
if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
|
|
1036
1099
|
addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
|
|
1037
1100
|
}
|
|
@@ -1427,6 +1490,8 @@ var NodeUserAuthorization = class {
|
|
|
1427
1490
|
this._chainId = chainId;
|
|
1428
1491
|
await this.resolveTinyCloudHostsForSignIn(address, chainId);
|
|
1429
1492
|
this.assertSignInCurrent(generation);
|
|
1493
|
+
await this.refreshNamedKeyFeatureForSignIn(address, chainId);
|
|
1494
|
+
this.assertSignInCurrent(generation);
|
|
1430
1495
|
const keyId = `session-${Date.now()}`;
|
|
1431
1496
|
this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
|
|
1432
1497
|
this.activeSessionKeyId = keyId;
|
|
@@ -1573,9 +1638,9 @@ var NodeUserAuthorization = class {
|
|
|
1573
1638
|
refuseCaveated(this.getCapabilityRequest()?.manifests);
|
|
1574
1639
|
const address = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
|
|
1575
1640
|
const chainId = await this.signer.getChainId();
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
this.
|
|
1641
|
+
await this.refreshNamedKeyFeatureForSignIn(address, chainId);
|
|
1642
|
+
const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
|
|
1643
|
+
this.sessionManager.createSessionKey(keyId);
|
|
1579
1644
|
const jwkString = this.sessionManager.jwk(keyId);
|
|
1580
1645
|
if (!jwkString) {
|
|
1581
1646
|
throw new Error("Failed to create session key");
|
|
@@ -1656,6 +1721,7 @@ var NodeUserAuthorization = class {
|
|
|
1656
1721
|
await this.sessionStorage.save(address, persistedData);
|
|
1657
1722
|
this._session = clientSession;
|
|
1658
1723
|
this._tinyCloudSession = tinyCloudSession;
|
|
1724
|
+
this.activeSessionKeyId = keyId;
|
|
1659
1725
|
this._address = address;
|
|
1660
1726
|
this._chainId = chainId;
|
|
1661
1727
|
await this.refreshNodeInfo();
|
|
@@ -1702,6 +1768,11 @@ var NodeUserAuthorization = class {
|
|
|
1702
1768
|
* @param options Optional overrides for the OpenKey request.
|
|
1703
1769
|
*/
|
|
1704
1770
|
async signInWithOpenKey(authorizeFn, options) {
|
|
1771
|
+
const pending = await this.prepareOpenKeySignIn(options);
|
|
1772
|
+
return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
|
|
1773
|
+
}
|
|
1774
|
+
/** Finish address, host and SIWE preparation before an approval click. */
|
|
1775
|
+
async prepareOpenKeySignIn(options) {
|
|
1705
1776
|
refuseCaveated(this.getCapabilityRequest()?.manifests);
|
|
1706
1777
|
if (options?.host !== void 0) {
|
|
1707
1778
|
const overrideHost = options.host.trim();
|
|
@@ -1713,7 +1784,7 @@ var NodeUserAuthorization = class {
|
|
|
1713
1784
|
const signingAddress = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
|
|
1714
1785
|
const signingChainId = await this.signer.getChainId();
|
|
1715
1786
|
await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
|
|
1716
|
-
const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
|
|
1787
|
+
const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
|
|
1717
1788
|
const presentation = this.buildPresentationEnvelope(options?.reason);
|
|
1718
1789
|
const hostHint = this.primaryTinyCloudHost;
|
|
1719
1790
|
const originalRequest = {
|
|
@@ -1725,12 +1796,32 @@ var NodeUserAuthorization = class {
|
|
|
1725
1796
|
keyId: options?.openkeyKeyId,
|
|
1726
1797
|
presentation
|
|
1727
1798
|
};
|
|
1799
|
+
return {
|
|
1800
|
+
request: originalRequest,
|
|
1801
|
+
prepared,
|
|
1802
|
+
keyId,
|
|
1803
|
+
jwk,
|
|
1804
|
+
address,
|
|
1805
|
+
chainId,
|
|
1806
|
+
generation: this.sessionGeneration,
|
|
1807
|
+
createdAt: Date.now()
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
/** Complete a prepared approval. The caller starts authorization before awaiting this method. */
|
|
1811
|
+
async completeOpenKeySignIn(pending, authorization, options) {
|
|
1812
|
+
if (Date.now() - pending.createdAt > 5 * 6e4) {
|
|
1813
|
+
throw new Error("Prepared OpenKey sign-in expired; prepare again");
|
|
1814
|
+
}
|
|
1815
|
+
if (pending.generation !== this.sessionGeneration) {
|
|
1816
|
+
throw new Error("Prepared OpenKey sign-in expired after sign-out");
|
|
1817
|
+
}
|
|
1818
|
+
const { request: originalRequest, prepared, keyId, jwk } = pending;
|
|
1728
1819
|
let result;
|
|
1729
1820
|
let signed;
|
|
1730
1821
|
let signature;
|
|
1731
1822
|
for (let attempt = 0; ; ) {
|
|
1732
1823
|
try {
|
|
1733
|
-
result ?? (result = await
|
|
1824
|
+
result ?? (result = await authorization);
|
|
1734
1825
|
signed = await this.verifyOpenKeyResult(result, prepared);
|
|
1735
1826
|
signature = result.signature;
|
|
1736
1827
|
break;
|
|
@@ -2151,8 +2242,52 @@ var NodeUserAuthorization = class {
|
|
|
2151
2242
|
}
|
|
2152
2243
|
};
|
|
2153
2244
|
|
|
2245
|
+
// src/authorization/openKeyBridge.ts
|
|
2246
|
+
function wireOpenKeyAuthorize(openkey, opts) {
|
|
2247
|
+
return async (input) => {
|
|
2248
|
+
const result = await openkey.authorizeTinyCloud({
|
|
2249
|
+
protocolVersion: 1,
|
|
2250
|
+
siwe: input.siwe,
|
|
2251
|
+
keyId: input.keyId,
|
|
2252
|
+
jwk: input.jwk,
|
|
2253
|
+
host: input.host,
|
|
2254
|
+
// Forward the presentation envelope verbatim — the OpenKey SDK
|
|
2255
|
+
// validates and size-bounds it before use.
|
|
2256
|
+
presentation: input.presentation
|
|
2257
|
+
}, opts);
|
|
2258
|
+
if (result.protocolVersion !== 1) {
|
|
2259
|
+
throw new Error(
|
|
2260
|
+
`wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
if (typeof result.signedMessage !== "string" || !result.signedMessage) {
|
|
2264
|
+
throw new Error(
|
|
2265
|
+
"wireOpenKeyAuthorize: OpenKey returned no signedMessage \u2014 cannot complete a TinyCloud session"
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
if (!Array.isArray(result.selectedActionKeys)) {
|
|
2269
|
+
throw new Error(
|
|
2270
|
+
"wireOpenKeyAuthorize: OpenKey returned no selectedActionKeys"
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
if (!Array.isArray(result.permissions)) {
|
|
2274
|
+
throw new Error(
|
|
2275
|
+
"wireOpenKeyAuthorize: OpenKey returned no permissions"
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
return {
|
|
2279
|
+
protocolVersion: 1,
|
|
2280
|
+
address: result.address,
|
|
2281
|
+
signature: result.signature,
|
|
2282
|
+
signedMessage: result.signedMessage,
|
|
2283
|
+
selectedActionKeys: result.selectedActionKeys,
|
|
2284
|
+
permissions: result.permissions
|
|
2285
|
+
};
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2154
2289
|
// src/recordingServiceContext.ts
|
|
2155
|
-
var
|
|
2290
|
+
var import_sdk_services3 = require("@tinycloud/sdk-services");
|
|
2156
2291
|
var import_requestLog = require("@tinycloud/sdk-services/telemetry/requestLog");
|
|
2157
2292
|
|
|
2158
2293
|
// src/recordingRegistry.ts
|
|
@@ -2197,9 +2332,9 @@ function replicationEvent(sample, op) {
|
|
|
2197
2332
|
}
|
|
2198
2333
|
function createServiceContext(config) {
|
|
2199
2334
|
const { requestSink, ...base } = config;
|
|
2200
|
-
return requestSink === void 0 ? new
|
|
2335
|
+
return requestSink === void 0 ? new import_sdk_services3.ServiceContext(base) : new RecordingServiceContext(base, requestSink);
|
|
2201
2336
|
}
|
|
2202
|
-
var RecordingServiceContext = class extends
|
|
2337
|
+
var RecordingServiceContext = class extends import_sdk_services3.ServiceContext {
|
|
2203
2338
|
constructor(config, sink) {
|
|
2204
2339
|
const pending = {};
|
|
2205
2340
|
super({
|
|
@@ -2793,8 +2928,9 @@ function deserializeDelegation(data) {
|
|
|
2793
2928
|
// src/TinyCloudNode.ts
|
|
2794
2929
|
var import_siwe2 = require("siwe");
|
|
2795
2930
|
var import_sdk_core11 = require("@tinycloud/sdk-core");
|
|
2796
|
-
var
|
|
2931
|
+
var import_sdk_services4 = require("@tinycloud/sdk-services");
|
|
2797
2932
|
var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
|
|
2933
|
+
var import_resource2 = require("@tinycloud/sdk-services/encryption/resource");
|
|
2798
2934
|
|
|
2799
2935
|
// src/account/AccountService.ts
|
|
2800
2936
|
var import_sdk_core7 = require("@tinycloud/sdk-core");
|
|
@@ -3271,6 +3407,7 @@ var ROOT_DELEGATION_ACTIONS = [
|
|
|
3271
3407
|
import_sdk_core11.DUCKDB.ALL
|
|
3272
3408
|
];
|
|
3273
3409
|
var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core11.EXPIRY.SESSION_MS;
|
|
3410
|
+
var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
|
|
3274
3411
|
function unifiedRootAttenuation(capabilities) {
|
|
3275
3412
|
const attenuation = {};
|
|
3276
3413
|
for (const capability of capabilities) {
|
|
@@ -3735,6 +3872,7 @@ function replicaRestoreRefusal(error) {
|
|
|
3735
3872
|
case "purge_pending":
|
|
3736
3873
|
case "logout_pending":
|
|
3737
3874
|
case "gate_unavailable":
|
|
3875
|
+
case "wallet_provider_required":
|
|
3738
3876
|
return error.gate;
|
|
3739
3877
|
}
|
|
3740
3878
|
}
|
|
@@ -3767,8 +3905,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3767
3905
|
constructor(config = {}) {
|
|
3768
3906
|
this.signer = null;
|
|
3769
3907
|
this.auth = null;
|
|
3908
|
+
this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
|
|
3770
3909
|
this.tc = null;
|
|
3771
3910
|
this._chainId = 1;
|
|
3911
|
+
this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
|
|
3772
3912
|
/** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
|
|
3773
3913
|
this.establishReplicaStamp = async (session) => {
|
|
3774
3914
|
const replication = this.config.replication;
|
|
@@ -3800,6 +3940,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3800
3940
|
return void 0;
|
|
3801
3941
|
}
|
|
3802
3942
|
};
|
|
3943
|
+
this._keysUnavailable = [];
|
|
3803
3944
|
this._baseVaults = /* @__PURE__ */ new Map();
|
|
3804
3945
|
this._baseSecrets = /* @__PURE__ */ new Map();
|
|
3805
3946
|
this._secrets = /* @__PURE__ */ new Map();
|
|
@@ -3975,6 +4116,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3975
4116
|
return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
|
|
3976
4117
|
},
|
|
3977
4118
|
clearPending: async () => (await runtime).control.clearPending(),
|
|
4119
|
+
purgeLocalCopy: async () => (await this.eraseReplicaLocalCopy("purge_local_copy"))?.purge ?? { ok: false, retryNow: false },
|
|
3978
4120
|
close: async () => (await runtime).control.close()
|
|
3979
4121
|
};
|
|
3980
4122
|
}
|
|
@@ -4029,18 +4171,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4029
4171
|
get freshEncryptionNetworkCreated() {
|
|
4030
4172
|
return this._freshEncryptionNetworkCreated;
|
|
4031
4173
|
}
|
|
4032
|
-
/**
|
|
4033
|
-
* Subscribe to `storage.full`. It fires on the first write any service had
|
|
4034
|
-
* rejected because the owner's TinyCloud storage is full
|
|
4035
|
-
* (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`), so an app can show
|
|
4036
|
-
* one read-only banner. Reads keep working. For the signed-in account's own
|
|
4037
|
-
* storage it fires once per episode: the episode ends only when a write to
|
|
4038
|
-
* that storage succeeds, or on sign-out. A `storage.status()` read does not
|
|
4039
|
-
* end it. A rejection on another owner's delegated space fires once per
|
|
4040
|
-
* owner, until that owner's `storage.status()` reports space again. Returns
|
|
4041
|
-
* the unsubscribe function.
|
|
4042
|
-
*/
|
|
4043
4174
|
on(event, handler) {
|
|
4175
|
+
if (event === "replica.purge.failed") {
|
|
4176
|
+
const listener = handler;
|
|
4177
|
+
this.replicaPurgeFailedHandlers.add(listener);
|
|
4178
|
+
return () => {
|
|
4179
|
+
this.replicaPurgeFailedHandlers.delete(listener);
|
|
4180
|
+
};
|
|
4181
|
+
}
|
|
4044
4182
|
if (event !== "storage.full") {
|
|
4045
4183
|
throw new Error(`Unknown TinyCloud event: ${String(event)}`);
|
|
4046
4184
|
}
|
|
@@ -4521,6 +4659,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4521
4659
|
const leavingAuth = this.auth;
|
|
4522
4660
|
const leavingContext = this._serviceContext;
|
|
4523
4661
|
let denied;
|
|
4662
|
+
let erased;
|
|
4524
4663
|
try {
|
|
4525
4664
|
this.teardownSession();
|
|
4526
4665
|
} finally {
|
|
@@ -4531,6 +4670,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4531
4670
|
try {
|
|
4532
4671
|
await this.accountRegistryTail;
|
|
4533
4672
|
await this.quiesceReplicaRuntime();
|
|
4673
|
+
if (denied) erased = await this.eraseReplicaLocalCopy("sign_out", denied.status);
|
|
4534
4674
|
} finally {
|
|
4535
4675
|
if (leavingAddress) {
|
|
4536
4676
|
await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
|
|
@@ -4538,8 +4678,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4538
4678
|
}
|
|
4539
4679
|
}
|
|
4540
4680
|
}
|
|
4541
|
-
if (denied?.status === "unguarded") {
|
|
4542
|
-
throw
|
|
4681
|
+
if (denied?.status === "unguarded" && erased?.outcome.status !== "erased") {
|
|
4682
|
+
throw Object.assign(
|
|
4683
|
+
new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`),
|
|
4684
|
+
{
|
|
4685
|
+
code: "REPLICA_PURGE_FAILED",
|
|
4686
|
+
reason: "gate_unavailable",
|
|
4687
|
+
retryNow: erased?.outcome.status === "failed"
|
|
4688
|
+
}
|
|
4689
|
+
);
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
/**
|
|
4693
|
+
* Erases the browser local copy after a deny (retry, event and next-load marker live in the lazy
|
|
4694
|
+
* replication authority). Never rejects; `undefined` when replication is off or the storage keeps
|
|
4695
|
+
* no browser database.
|
|
4696
|
+
*/
|
|
4697
|
+
async eraseReplicaLocalCopy(trigger, denyStatus) {
|
|
4698
|
+
const replication = this.config.replication;
|
|
4699
|
+
if (!replication?.enabled) return void 0;
|
|
4700
|
+
try {
|
|
4701
|
+
const { eraseReplicaLocalCopy } = await getNodeReplicationLoaders().authority();
|
|
4702
|
+
const erased = await eraseReplicaLocalCopy(
|
|
4703
|
+
replication.storage.lifecycle,
|
|
4704
|
+
trigger,
|
|
4705
|
+
denyStatus,
|
|
4706
|
+
this.replicaPurgeFailedHandlers
|
|
4707
|
+
);
|
|
4708
|
+
if (erased?.logoutPending) this.replicaRestoreGate = "logout_pending";
|
|
4709
|
+
return erased;
|
|
4710
|
+
} catch {
|
|
4711
|
+
return void 0;
|
|
4543
4712
|
}
|
|
4544
4713
|
}
|
|
4545
4714
|
/**
|
|
@@ -4599,6 +4768,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4599
4768
|
this._hooks = void 0;
|
|
4600
4769
|
this._vault = void 0;
|
|
4601
4770
|
this._encryption = void 0;
|
|
4771
|
+
this._namedKeyClient = void 0;
|
|
4772
|
+
this._keysUnavailable = [];
|
|
4602
4773
|
this._baseVaults.clear();
|
|
4603
4774
|
this._baseSecrets.clear();
|
|
4604
4775
|
this._secrets.clear();
|
|
@@ -4675,6 +4846,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4675
4846
|
}
|
|
4676
4847
|
await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
|
|
4677
4848
|
}
|
|
4849
|
+
/** Prepare the OpenKey request before the approval click. */
|
|
4850
|
+
async prepareOpenKeySignIn() {
|
|
4851
|
+
if (!this.signer || !this.tc || !this.auth) {
|
|
4852
|
+
throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
|
|
4853
|
+
}
|
|
4854
|
+
await this.signOutInFlight;
|
|
4855
|
+
await this.accountRegistryTail;
|
|
4856
|
+
await this.wasmBindings.ensureInitialized?.();
|
|
4857
|
+
return this.auth.prepareOpenKeySignIn();
|
|
4858
|
+
}
|
|
4859
|
+
/** Start OpenKey authorization synchronously, then complete normal session setup. */
|
|
4860
|
+
signInWithPreparedOpenKey(pending, authorizeFn) {
|
|
4861
|
+
if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
|
|
4862
|
+
throw new Error("Prepared OpenKey sign-in is unavailable");
|
|
4863
|
+
}
|
|
4864
|
+
if (this.consumedOpenKeyPendings.has(pending)) {
|
|
4865
|
+
throw new Error("Prepared OpenKey sign-in was already used");
|
|
4866
|
+
}
|
|
4867
|
+
if (Date.now() - pending.createdAt > 5 * 6e4) {
|
|
4868
|
+
throw new Error("Prepared OpenKey sign-in expired; prepare again");
|
|
4869
|
+
}
|
|
4870
|
+
this.consumedOpenKeyPendings.add(pending);
|
|
4871
|
+
const authorization = authorizeFn(pending.request);
|
|
4872
|
+
void authorization.catch(() => void 0);
|
|
4873
|
+
return this.signInWith(() => {
|
|
4874
|
+
if (this._address !== pending.address || this._chainId !== pending.chainId) {
|
|
4875
|
+
throw new Error("OpenKey account changed after sign-in preparation");
|
|
4876
|
+
}
|
|
4877
|
+
return this.auth.completeOpenKeySignIn(pending, authorization);
|
|
4878
|
+
});
|
|
4879
|
+
}
|
|
4678
4880
|
async signInWith(performSignIn) {
|
|
4679
4881
|
refuseCaveated(this.auth?.capabilityRequest?.manifests);
|
|
4680
4882
|
const epoch = this.sessionEpoch;
|
|
@@ -4703,6 +4905,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4703
4905
|
const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? (0, import_sdk_core11.pkhDid)(this.address, this._chainId) : void 0);
|
|
4704
4906
|
if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
|
|
4705
4907
|
const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
|
|
4908
|
+
if (denial) {
|
|
4909
|
+
await this.quiesceReplicaRuntime();
|
|
4910
|
+
await this.eraseReplicaLocalCopy("account_switch", denial.status);
|
|
4911
|
+
}
|
|
4706
4912
|
this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
|
|
4707
4913
|
}
|
|
4708
4914
|
if (address !== this._address || chainId !== this._chainId) {
|
|
@@ -4717,6 +4923,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4717
4923
|
this._hooks = void 0;
|
|
4718
4924
|
this._vault = void 0;
|
|
4719
4925
|
this._encryption = void 0;
|
|
4926
|
+
this._namedKeyClient = void 0;
|
|
4927
|
+
this._keysUnavailable = [];
|
|
4720
4928
|
this._baseVaults.clear();
|
|
4721
4929
|
this._baseSecrets.clear();
|
|
4722
4930
|
this._secrets.clear();
|
|
@@ -4769,6 +4977,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4769
4977
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4770
4978
|
await this.ensureRequestedEncryptionNetworks();
|
|
4771
4979
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4980
|
+
this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
|
|
4981
|
+
if (this._keysUnavailable.length > 0) {
|
|
4982
|
+
this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
|
|
4983
|
+
}
|
|
4984
|
+
if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
|
|
4985
|
+
for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
|
|
4986
|
+
for (const declaration of manifest.keys ?? []) {
|
|
4987
|
+
if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
|
|
4988
|
+
try {
|
|
4989
|
+
await this.keys.ensureKey(declaration.path);
|
|
4990
|
+
} catch (error) {
|
|
4991
|
+
this._keysUnavailable.push(declaration.path);
|
|
4992
|
+
const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
|
|
4993
|
+
this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
|
|
4994
|
+
}
|
|
4995
|
+
this.assertSessionEpoch(epoch, "Sign-in");
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
4772
5000
|
if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
|
|
4773
5001
|
await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(import_sdk_core11.SECRETS_SPACE));
|
|
4774
5002
|
}
|
|
@@ -5345,7 +5573,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5345
5573
|
if (!this.currentSessionCoversAccountRegistry()) {
|
|
5346
5574
|
if (!_TinyCloudNode.accountRegistrySkipLogged) {
|
|
5347
5575
|
_TinyCloudNode.accountRegistrySkipLogged = true;
|
|
5348
|
-
|
|
5576
|
+
import_sdk_services4.tinyCloudDebugLogger.log("registry.skip");
|
|
5349
5577
|
}
|
|
5350
5578
|
return;
|
|
5351
5579
|
}
|
|
@@ -5556,7 +5784,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5556
5784
|
const networkIds = /* @__PURE__ */ new Set();
|
|
5557
5785
|
for (const resource of request.resources) {
|
|
5558
5786
|
if (resource.service === import_sdk_core11.ENCRYPTION_PERMISSION_SERVICE && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
|
|
5559
|
-
|
|
5787
|
+
const parsed = resource.path.includes("/") ? (0, import_resource2.parseEncryptionResource)(resource.path) : { ...(0, import_sdk_core11.parseNetworkId)(resource.path), kind: "network" };
|
|
5788
|
+
if (parsed.kind === "network") networkIds.add(resource.path);
|
|
5560
5789
|
}
|
|
5561
5790
|
}
|
|
5562
5791
|
return [...networkIds];
|
|
@@ -6106,6 +6335,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6106
6335
|
this._hooks = stagedGraph.hooks;
|
|
6107
6336
|
this._vault = stagedGraph.vault;
|
|
6108
6337
|
this._encryption = stagedGraph.encryption;
|
|
6338
|
+
this._namedKeyClient = void 0;
|
|
6339
|
+
this._keysUnavailable = [];
|
|
6109
6340
|
this._capabilityRegistry = stagedGraph.capabilityRegistry;
|
|
6110
6341
|
this._keyProvider = stagedGraph.keyProvider;
|
|
6111
6342
|
this._sharingService = stagedGraph.sharingService;
|
|
@@ -7015,7 +7246,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7015
7246
|
);
|
|
7016
7247
|
graph.assertActive();
|
|
7017
7248
|
if (!response.ok) {
|
|
7018
|
-
const permissionHint = response.status === 401 || response.status === 403 ? (0,
|
|
7249
|
+
const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services4.parsePermissionHint)({
|
|
7019
7250
|
service: "tinycloud.encryption",
|
|
7020
7251
|
path: networkId,
|
|
7021
7252
|
actions: ["tinycloud.encryption/decrypt"]
|
|
@@ -7459,7 +7690,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7459
7690
|
* Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
|
|
7460
7691
|
* `POST /revoke`, and return its Authorization header value. The node
|
|
7461
7692
|
* accepts it when the owner delegated `cid` or owns a space its abilities
|
|
7462
|
-
* target, whichever key issued it. Valid for
|
|
7693
|
+
* target, whichever key issued it. Valid for 10 minutes. Requires the
|
|
7463
7694
|
* owner wallet signer.
|
|
7464
7695
|
*/
|
|
7465
7696
|
async signOwnerRevocation(cid) {
|
|
@@ -7475,7 +7706,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7475
7706
|
chainId: session.chainId,
|
|
7476
7707
|
nonce: (0, import_siwe2.generateNonce)(),
|
|
7477
7708
|
issuedAt: issuedAt.toISOString(),
|
|
7478
|
-
expirationTime: new Date(issuedAt.getTime() +
|
|
7709
|
+
expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
|
|
7479
7710
|
}).prepareMessage();
|
|
7480
7711
|
const signature = await this.signer.signMessage(siwe);
|
|
7481
7712
|
return this.wasmBindings.siweToDelegationHeaders({ siwe, signature }).Authorization;
|
|
@@ -7702,6 +7933,40 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7702
7933
|
get encryption() {
|
|
7703
7934
|
return this.getEncryptionService();
|
|
7704
7935
|
}
|
|
7936
|
+
/** Named keys bound to the current session. */
|
|
7937
|
+
get keys() {
|
|
7938
|
+
return new Proxy({}, {
|
|
7939
|
+
get: (_target, property) => {
|
|
7940
|
+
if (property === "refreshNodeInfo") {
|
|
7941
|
+
return () => {
|
|
7942
|
+
void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
|
|
7943
|
+
};
|
|
7944
|
+
}
|
|
7945
|
+
if (typeof property !== "string" || ![
|
|
7946
|
+
"getPublicKey",
|
|
7947
|
+
"describeKey",
|
|
7948
|
+
"createKey",
|
|
7949
|
+
"blockKey",
|
|
7950
|
+
"unblockKey",
|
|
7951
|
+
"ensureKey",
|
|
7952
|
+
"encrypt",
|
|
7953
|
+
"decrypt",
|
|
7954
|
+
"decryptMany"
|
|
7955
|
+
].includes(property)) return void 0;
|
|
7956
|
+
return (...args) => {
|
|
7957
|
+
this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.cjs").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
|
|
7958
|
+
return this._namedKeyClient.then((client) => {
|
|
7959
|
+
const method = client[property];
|
|
7960
|
+
return method.apply(client, args);
|
|
7961
|
+
});
|
|
7962
|
+
};
|
|
7963
|
+
}
|
|
7964
|
+
});
|
|
7965
|
+
}
|
|
7966
|
+
/** Manifest key paths unavailable during the latest `signIn()` attempt. */
|
|
7967
|
+
get keysUnavailable() {
|
|
7968
|
+
return this._keysUnavailable;
|
|
7969
|
+
}
|
|
7705
7970
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
7706
7971
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
7707
7972
|
return this.fetchEncryptionNetworkAt(
|
|
@@ -7829,7 +8094,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7829
8094
|
);
|
|
7830
8095
|
if (result.status !== "ok") {
|
|
7831
8096
|
if (result.status === "permission_required") {
|
|
7832
|
-
const hint = (0,
|
|
8097
|
+
const hint = (0, import_sdk_services4.parsePermissionHint)(result.hint);
|
|
7833
8098
|
if (hint === void 0) return { status: "read_failed" };
|
|
7834
8099
|
return {
|
|
7835
8100
|
status: "permission_required",
|
|
@@ -10007,6 +10272,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
|
10007
10272
|
PermissionNotInManifestError,
|
|
10008
10273
|
PrefixedKVService,
|
|
10009
10274
|
ProtocolMismatchError,
|
|
10275
|
+
RuntimeDelegationRejectedError,
|
|
10010
10276
|
SECRET_NAME_RE,
|
|
10011
10277
|
SQLAction,
|
|
10012
10278
|
SQLService,
|
|
@@ -10029,6 +10295,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
|
10029
10295
|
VersionCheckError,
|
|
10030
10296
|
WasmKeyProvider,
|
|
10031
10297
|
activateCompactRuntimeDelegation,
|
|
10298
|
+
activateValidatedRuntimeDelegation,
|
|
10032
10299
|
addressStorageKey,
|
|
10033
10300
|
buildSpaceUri,
|
|
10034
10301
|
canonicalizeAddress,
|
|
@@ -10079,6 +10346,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
|
10079
10346
|
resourceCapabilitiesToSpaceAbilitiesMap,
|
|
10080
10347
|
serializeDelegation,
|
|
10081
10348
|
tinyCloudDebugLogger,
|
|
10082
|
-
validateManifest
|
|
10349
|
+
validateManifest,
|
|
10350
|
+
wireOpenKeyAuthorize
|
|
10083
10351
|
});
|
|
10084
10352
|
//# sourceMappingURL=core.cjs.map
|