@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/index.js
CHANGED
|
@@ -17099,6 +17099,9 @@ function assertValidReplicationConfig(replication) {
|
|
|
17099
17099
|
if (replication?.syncScope !== void 0 && replication.syncScope !== "prefixes") {
|
|
17100
17100
|
throw new TypeError('replication.syncScope must be "prefixes"');
|
|
17101
17101
|
}
|
|
17102
|
+
if (replication?.contentFetch !== void 0 && replication.contentFetch !== "eager" && replication.contentFetch !== "lazy") {
|
|
17103
|
+
throw new TypeError('replication.contentFetch must be "eager" or "lazy"');
|
|
17104
|
+
}
|
|
17102
17105
|
if (replication === void 0 || !replication.enabled) return;
|
|
17103
17106
|
if (replication.storage === void 0 || replication.storage === null) {
|
|
17104
17107
|
throw new TypeError("replication.enabled requires replication.storage");
|
|
@@ -17151,6 +17154,7 @@ function resolvedOptions(options) {
|
|
|
17151
17154
|
maxStalenessMs: options.maxStalenessMs ?? 12e4,
|
|
17152
17155
|
staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5e3,
|
|
17153
17156
|
allowSecrets: options.allowSecrets === true,
|
|
17157
|
+
contentFetch: options.contentFetch ?? "eager",
|
|
17154
17158
|
verify: options.verify === true
|
|
17155
17159
|
};
|
|
17156
17160
|
}
|
|
@@ -17421,6 +17425,7 @@ __export(authority_exports, {
|
|
|
17421
17425
|
assertValidReplicationConfig: () => assertValidReplicationConfig,
|
|
17422
17426
|
augmentSignInEntriesWithReplication: () => augmentSignInEntriesWithReplication,
|
|
17423
17427
|
createReplicationAuthority: () => createReplicationAuthority,
|
|
17428
|
+
eraseReplicaLocalCopy: () => eraseReplicaLocalCopy,
|
|
17424
17429
|
establishReplicaSessionStamp: () => establishReplicaSessionStamp,
|
|
17425
17430
|
hasUnrestrictedGetCoverage: () => hasUnrestrictedGetCoverage,
|
|
17426
17431
|
mintNodeReplicationGrant: () => mintNodeReplicationGrant,
|
|
@@ -17461,6 +17466,38 @@ function reportReplicaDenial(result, reason, onEvent, context) {
|
|
|
17461
17466
|
} catch {
|
|
17462
17467
|
}
|
|
17463
17468
|
}
|
|
17469
|
+
async function eraseReplicaLocalCopy(lifecycle, trigger, denyStatus, handlers) {
|
|
17470
|
+
if (lifecycle?.kind !== "indexeddb" || lifecycle.eraseLocalCopy === void 0) return void 0;
|
|
17471
|
+
let outcome = { status: "failed", code: "STORAGE_ERROR", marker: false };
|
|
17472
|
+
for (let attempt = 0; attempt <= ERASE_RETRY_DELAYS_MS.length; attempt++) {
|
|
17473
|
+
if (attempt > 0) await new Promise((resolve2) => setTimeout(resolve2, ERASE_RETRY_DELAYS_MS[attempt - 1]));
|
|
17474
|
+
try {
|
|
17475
|
+
outcome = await lifecycle.eraseLocalCopy({ signal: AbortSignal.timeout(ERASE_ATTEMPT_MS) });
|
|
17476
|
+
} catch {
|
|
17477
|
+
outcome = { status: "failed", code: "STORAGE_ERROR", marker: false };
|
|
17478
|
+
}
|
|
17479
|
+
if (outcome.status !== "failed") break;
|
|
17480
|
+
}
|
|
17481
|
+
const keysKept = denyStatus === "marker" || denyStatus === "unguarded";
|
|
17482
|
+
const purge = outcome.status === "erased" ? { ok: true } : { ok: false, retryNow: outcome.status === "failed" };
|
|
17483
|
+
if (outcome.status === "erased" || outcome.status === "refused" && !keysKept) return { outcome, purge };
|
|
17484
|
+
const logoutPending = outcome.status === "failed" && outcome.marker;
|
|
17485
|
+
const event = {
|
|
17486
|
+
type: "replica.purge.failed",
|
|
17487
|
+
trigger,
|
|
17488
|
+
reason: logoutPending ? "logout_pending" : "gate_unavailable",
|
|
17489
|
+
code: outcome.code,
|
|
17490
|
+
retryNow: outcome.status === "failed"
|
|
17491
|
+
};
|
|
17492
|
+
for (const handler of handlers) {
|
|
17493
|
+
try {
|
|
17494
|
+
handler(event);
|
|
17495
|
+
} catch (error) {
|
|
17496
|
+
console.error('Error in "replica.purge.failed" handler:', error);
|
|
17497
|
+
}
|
|
17498
|
+
}
|
|
17499
|
+
return logoutPending ? { outcome, purge, logoutPending } : { outcome, purge };
|
|
17500
|
+
}
|
|
17464
17501
|
function verifiedOwnerConsentAnchor(session, wasmBindings, exactVerifiedRecap2) {
|
|
17465
17502
|
if (typeof session.siwe !== "string" || typeof session.signature !== "string" || typeof session.address !== "string" || typeof session.chainId !== "number") return null;
|
|
17466
17503
|
try {
|
|
@@ -17719,13 +17756,15 @@ function createReplicationAuthority(host) {
|
|
|
17719
17756
|
};
|
|
17720
17757
|
return authority;
|
|
17721
17758
|
}
|
|
17722
|
-
var parseUcanGrant, KV_GET2, KV_SYNC2, KV_SERVICE2, AUTHORITY_EXPIRY_MARGIN_MS;
|
|
17759
|
+
var parseUcanGrant, ERASE_RETRY_DELAYS_MS, ERASE_ATTEMPT_MS, KV_GET2, KV_SYNC2, KV_SERVICE2, AUTHORITY_EXPIRY_MARGIN_MS;
|
|
17723
17760
|
var init_authority = __esm({
|
|
17724
17761
|
"src/replication/authority.ts"() {
|
|
17725
17762
|
"use strict";
|
|
17726
17763
|
init_config();
|
|
17727
17764
|
init_authority_sign_in();
|
|
17728
17765
|
parseUcanGrant = replicaParseUcanGrant;
|
|
17766
|
+
ERASE_RETRY_DELAYS_MS = [150, 450];
|
|
17767
|
+
ERASE_ATTEMPT_MS = 3e3;
|
|
17729
17768
|
KV_GET2 = KV4.GET;
|
|
17730
17769
|
KV_SYNC2 = KV4.SYNC;
|
|
17731
17770
|
KV_SERVICE2 = "tinycloud.kv";
|
|
@@ -18748,9 +18787,9 @@ import {
|
|
|
18748
18787
|
tinyCloudDebugLogger
|
|
18749
18788
|
} from "@tinycloud/sdk-services";
|
|
18750
18789
|
import { RequestLog, observeReplicaReads, readOnlyRequestLog } from "@tinycloud/sdk-services/telemetry/requestLog";
|
|
18790
|
+
import { parseEncryptionResource as parseEncryptionResource2 } from "@tinycloud/sdk-services/encryption/resource";
|
|
18751
18791
|
|
|
18752
18792
|
// src/authorization/NodeUserAuthorization.ts
|
|
18753
|
-
init_authority_sign_in();
|
|
18754
18793
|
import { createHash } from "crypto";
|
|
18755
18794
|
import { SiweMessage } from "siwe";
|
|
18756
18795
|
import {
|
|
@@ -18759,10 +18798,10 @@ import {
|
|
|
18759
18798
|
activateSessionWithHost,
|
|
18760
18799
|
checkNodeInfo,
|
|
18761
18800
|
AutoApproveSpaceCreationHandler,
|
|
18801
|
+
ManifestValidationError,
|
|
18762
18802
|
DEFAULT_MANIFEST_SPACE,
|
|
18763
18803
|
ENCRYPTION_PERMISSION_SERVICE,
|
|
18764
18804
|
composeManifestRequest,
|
|
18765
|
-
parseNetworkId,
|
|
18766
18805
|
principalDidEquals as principalDidEquals2,
|
|
18767
18806
|
resourceCapabilitiesToAbilitiesMap,
|
|
18768
18807
|
resourceCapabilitiesToSpaceAbilitiesMap,
|
|
@@ -18790,6 +18829,35 @@ import {
|
|
|
18790
18829
|
HOOKS,
|
|
18791
18830
|
ENCRYPTION
|
|
18792
18831
|
} from "@tinycloud/sdk-core";
|
|
18832
|
+
import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
|
|
18833
|
+
import { parseNetworkId } from "@tinycloud/sdk-services";
|
|
18834
|
+
|
|
18835
|
+
// src/authorization/appKeyHostBinding.ts
|
|
18836
|
+
var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
18837
|
+
var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
|
|
18838
|
+
var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
18839
|
+
function appKeyHostBinding(domain, appId) {
|
|
18840
|
+
if (!APP_ID.test(appId)) return false;
|
|
18841
|
+
if (domain.endsWith(":")) return false;
|
|
18842
|
+
let url;
|
|
18843
|
+
try {
|
|
18844
|
+
url = new URL(`https://${domain}`);
|
|
18845
|
+
} catch {
|
|
18846
|
+
return false;
|
|
18847
|
+
}
|
|
18848
|
+
if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
|
|
18849
|
+
if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
|
|
18850
|
+
const host = url.hostname;
|
|
18851
|
+
if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
|
|
18852
|
+
const labels = host.split(".");
|
|
18853
|
+
if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
|
|
18854
|
+
if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
|
|
18855
|
+
const reversed = labels.reverse().join(".");
|
|
18856
|
+
return appId === reversed || appId.startsWith(`${reversed}.`);
|
|
18857
|
+
}
|
|
18858
|
+
|
|
18859
|
+
// src/authorization/NodeUserAuthorization.ts
|
|
18860
|
+
init_authority_sign_in();
|
|
18793
18861
|
|
|
18794
18862
|
// src/authorization/caveatRefusal.ts
|
|
18795
18863
|
import {
|
|
@@ -18947,6 +19015,7 @@ function cloneAbilitiesMap(actions) {
|
|
|
18947
19015
|
var NodeUserAuthorization = class {
|
|
18948
19016
|
constructor(config) {
|
|
18949
19017
|
this.activeSessionKeyId = "default";
|
|
19018
|
+
this.preparedSessionCounter = 0;
|
|
18950
19019
|
/** Incremented by resetSession; a sign-in started earlier publishes nothing. */
|
|
18951
19020
|
this.sessionGeneration = 0;
|
|
18952
19021
|
/**
|
|
@@ -19303,6 +19372,23 @@ var NodeUserAuthorization = class {
|
|
|
19303
19372
|
);
|
|
19304
19373
|
return this._capabilityRequest;
|
|
19305
19374
|
}
|
|
19375
|
+
async refreshNamedKeyFeatureForSignIn(address, chainId) {
|
|
19376
|
+
const request = this.getCapabilityRequest();
|
|
19377
|
+
const siweDomain = this.siweConfig?.domain ?? this.domain;
|
|
19378
|
+
const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
|
|
19379
|
+
if (hasNamedKeys) {
|
|
19380
|
+
await this.resolveTinyCloudHostsForSignIn(address, chainId);
|
|
19381
|
+
await this.refreshNodeInfo();
|
|
19382
|
+
}
|
|
19383
|
+
}
|
|
19384
|
+
/** Manifest key paths that this sign-in cannot grant. */
|
|
19385
|
+
get unavailableKeyPaths() {
|
|
19386
|
+
const request = this.getCapabilityRequest();
|
|
19387
|
+
const siweDomain = this.siweConfig?.domain ?? this.domain;
|
|
19388
|
+
return (request?.manifests ?? []).flatMap(
|
|
19389
|
+
(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)
|
|
19390
|
+
);
|
|
19391
|
+
}
|
|
19306
19392
|
resolveSpaceName(space, address, chainId) {
|
|
19307
19393
|
if (space.startsWith("tinycloud:")) {
|
|
19308
19394
|
return space;
|
|
@@ -19494,15 +19580,31 @@ var NodeUserAuthorization = class {
|
|
|
19494
19580
|
}
|
|
19495
19581
|
const rawAbilities = {};
|
|
19496
19582
|
const currentDid = pkhDid(address, chainId);
|
|
19583
|
+
if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
|
|
19584
|
+
const siweDomain = this.siweConfig?.domain ?? this.domain;
|
|
19585
|
+
for (const manifest of request.manifests) {
|
|
19586
|
+
if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
|
|
19587
|
+
for (const declaration of manifest.keys ?? []) {
|
|
19588
|
+
const path = "path" in declaration ? declaration.path : declaration.prefix;
|
|
19589
|
+
const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
|
|
19590
|
+
for (const ability of declaration.abilities) {
|
|
19591
|
+
addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
|
|
19592
|
+
}
|
|
19593
|
+
}
|
|
19594
|
+
}
|
|
19595
|
+
}
|
|
19497
19596
|
const spaceResources = request.resources.filter((entry) => {
|
|
19498
19597
|
if (entry.service !== ENCRYPTION_PERMISSION_SERVICE) {
|
|
19499
19598
|
return true;
|
|
19500
19599
|
}
|
|
19600
|
+
const parsed = entry.path.includes("/") ? parseEncryptionResource(entry.path) : { ...parseNetworkId(entry.path), kind: "network" };
|
|
19601
|
+
if (parsed.kind !== "network") {
|
|
19602
|
+
throw new ManifestValidationError("named keys go in manifest.keys");
|
|
19603
|
+
}
|
|
19501
19604
|
for (const action of entry.actions) {
|
|
19502
19605
|
addRawAbility(rawAbilities, entry.path, action);
|
|
19503
19606
|
}
|
|
19504
|
-
if (entry.actions.includes(DECRYPT_ACTION)) {
|
|
19505
|
-
const parsed = parseNetworkId(entry.path);
|
|
19607
|
+
if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
|
|
19506
19608
|
if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
|
|
19507
19609
|
addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
|
|
19508
19610
|
}
|
|
@@ -19898,6 +20000,8 @@ var NodeUserAuthorization = class {
|
|
|
19898
20000
|
this._chainId = chainId;
|
|
19899
20001
|
await this.resolveTinyCloudHostsForSignIn(address, chainId);
|
|
19900
20002
|
this.assertSignInCurrent(generation);
|
|
20003
|
+
await this.refreshNamedKeyFeatureForSignIn(address, chainId);
|
|
20004
|
+
this.assertSignInCurrent(generation);
|
|
19901
20005
|
const keyId = `session-${Date.now()}`;
|
|
19902
20006
|
this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
|
|
19903
20007
|
this.activeSessionKeyId = keyId;
|
|
@@ -20044,9 +20148,9 @@ var NodeUserAuthorization = class {
|
|
|
20044
20148
|
refuseCaveated(this.getCapabilityRequest()?.manifests);
|
|
20045
20149
|
const address = canonicalizeAddress(await this.signer.getAddress());
|
|
20046
20150
|
const chainId = await this.signer.getChainId();
|
|
20047
|
-
|
|
20048
|
-
|
|
20049
|
-
this.
|
|
20151
|
+
await this.refreshNamedKeyFeatureForSignIn(address, chainId);
|
|
20152
|
+
const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
|
|
20153
|
+
this.sessionManager.createSessionKey(keyId);
|
|
20050
20154
|
const jwkString = this.sessionManager.jwk(keyId);
|
|
20051
20155
|
if (!jwkString) {
|
|
20052
20156
|
throw new Error("Failed to create session key");
|
|
@@ -20127,6 +20231,7 @@ var NodeUserAuthorization = class {
|
|
|
20127
20231
|
await this.sessionStorage.save(address, persistedData);
|
|
20128
20232
|
this._session = clientSession;
|
|
20129
20233
|
this._tinyCloudSession = tinyCloudSession;
|
|
20234
|
+
this.activeSessionKeyId = keyId;
|
|
20130
20235
|
this._address = address;
|
|
20131
20236
|
this._chainId = chainId;
|
|
20132
20237
|
await this.refreshNodeInfo();
|
|
@@ -20173,6 +20278,11 @@ var NodeUserAuthorization = class {
|
|
|
20173
20278
|
* @param options Optional overrides for the OpenKey request.
|
|
20174
20279
|
*/
|
|
20175
20280
|
async signInWithOpenKey(authorizeFn, options) {
|
|
20281
|
+
const pending = await this.prepareOpenKeySignIn(options);
|
|
20282
|
+
return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
|
|
20283
|
+
}
|
|
20284
|
+
/** Finish address, host and SIWE preparation before an approval click. */
|
|
20285
|
+
async prepareOpenKeySignIn(options) {
|
|
20176
20286
|
refuseCaveated(this.getCapabilityRequest()?.manifests);
|
|
20177
20287
|
if (options?.host !== void 0) {
|
|
20178
20288
|
const overrideHost = options.host.trim();
|
|
@@ -20184,7 +20294,7 @@ var NodeUserAuthorization = class {
|
|
|
20184
20294
|
const signingAddress = canonicalizeAddress(await this.signer.getAddress());
|
|
20185
20295
|
const signingChainId = await this.signer.getChainId();
|
|
20186
20296
|
await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
|
|
20187
|
-
const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
|
|
20297
|
+
const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
|
|
20188
20298
|
const presentation = this.buildPresentationEnvelope(options?.reason);
|
|
20189
20299
|
const hostHint = this.primaryTinyCloudHost;
|
|
20190
20300
|
const originalRequest = {
|
|
@@ -20196,12 +20306,32 @@ var NodeUserAuthorization = class {
|
|
|
20196
20306
|
keyId: options?.openkeyKeyId,
|
|
20197
20307
|
presentation
|
|
20198
20308
|
};
|
|
20309
|
+
return {
|
|
20310
|
+
request: originalRequest,
|
|
20311
|
+
prepared,
|
|
20312
|
+
keyId,
|
|
20313
|
+
jwk,
|
|
20314
|
+
address,
|
|
20315
|
+
chainId,
|
|
20316
|
+
generation: this.sessionGeneration,
|
|
20317
|
+
createdAt: Date.now()
|
|
20318
|
+
};
|
|
20319
|
+
}
|
|
20320
|
+
/** Complete a prepared approval. The caller starts authorization before awaiting this method. */
|
|
20321
|
+
async completeOpenKeySignIn(pending, authorization, options) {
|
|
20322
|
+
if (Date.now() - pending.createdAt > 5 * 6e4) {
|
|
20323
|
+
throw new Error("Prepared OpenKey sign-in expired; prepare again");
|
|
20324
|
+
}
|
|
20325
|
+
if (pending.generation !== this.sessionGeneration) {
|
|
20326
|
+
throw new Error("Prepared OpenKey sign-in expired after sign-out");
|
|
20327
|
+
}
|
|
20328
|
+
const { request: originalRequest, prepared, keyId, jwk } = pending;
|
|
20199
20329
|
let result;
|
|
20200
20330
|
let signed2;
|
|
20201
20331
|
let signature2;
|
|
20202
20332
|
for (let attempt = 0; ; ) {
|
|
20203
20333
|
try {
|
|
20204
|
-
result ?? (result = await
|
|
20334
|
+
result ?? (result = await authorization);
|
|
20205
20335
|
signed2 = await this.verifyOpenKeyResult(result, prepared);
|
|
20206
20336
|
signature2 = result.signature;
|
|
20207
20337
|
break;
|
|
@@ -21112,6 +21242,7 @@ var ROOT_DELEGATION_ACTIONS = [
|
|
|
21112
21242
|
DUCKDB2.ALL
|
|
21113
21243
|
];
|
|
21114
21244
|
var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
|
|
21245
|
+
var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
|
|
21115
21246
|
function unifiedRootAttenuation(capabilities) {
|
|
21116
21247
|
const attenuation = {};
|
|
21117
21248
|
for (const capability of capabilities) {
|
|
@@ -21576,6 +21707,7 @@ function replicaRestoreRefusal(error) {
|
|
|
21576
21707
|
case "purge_pending":
|
|
21577
21708
|
case "logout_pending":
|
|
21578
21709
|
case "gate_unavailable":
|
|
21710
|
+
case "wallet_provider_required":
|
|
21579
21711
|
return error.gate;
|
|
21580
21712
|
}
|
|
21581
21713
|
}
|
|
@@ -21608,8 +21740,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21608
21740
|
constructor(config = {}) {
|
|
21609
21741
|
this.signer = null;
|
|
21610
21742
|
this.auth = null;
|
|
21743
|
+
this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
|
|
21611
21744
|
this.tc = null;
|
|
21612
21745
|
this._chainId = 1;
|
|
21746
|
+
this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
|
|
21613
21747
|
/** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
|
|
21614
21748
|
this.establishReplicaStamp = async (session) => {
|
|
21615
21749
|
const replication = this.config.replication;
|
|
@@ -21641,6 +21775,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21641
21775
|
return void 0;
|
|
21642
21776
|
}
|
|
21643
21777
|
};
|
|
21778
|
+
this._keysUnavailable = [];
|
|
21644
21779
|
this._baseVaults = /* @__PURE__ */ new Map();
|
|
21645
21780
|
this._baseSecrets = /* @__PURE__ */ new Map();
|
|
21646
21781
|
this._secrets = /* @__PURE__ */ new Map();
|
|
@@ -21816,6 +21951,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21816
21951
|
return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
|
|
21817
21952
|
},
|
|
21818
21953
|
clearPending: async () => (await runtime).control.clearPending(),
|
|
21954
|
+
purgeLocalCopy: async () => (await this.eraseReplicaLocalCopy("purge_local_copy"))?.purge ?? { ok: false, retryNow: false },
|
|
21819
21955
|
close: async () => (await runtime).control.close()
|
|
21820
21956
|
};
|
|
21821
21957
|
}
|
|
@@ -21870,18 +22006,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21870
22006
|
get freshEncryptionNetworkCreated() {
|
|
21871
22007
|
return this._freshEncryptionNetworkCreated;
|
|
21872
22008
|
}
|
|
21873
|
-
/**
|
|
21874
|
-
* Subscribe to `storage.full`. It fires on the first write any service had
|
|
21875
|
-
* rejected because the owner's TinyCloud storage is full
|
|
21876
|
-
* (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`), so an app can show
|
|
21877
|
-
* one read-only banner. Reads keep working. For the signed-in account's own
|
|
21878
|
-
* storage it fires once per episode: the episode ends only when a write to
|
|
21879
|
-
* that storage succeeds, or on sign-out. A `storage.status()` read does not
|
|
21880
|
-
* end it. A rejection on another owner's delegated space fires once per
|
|
21881
|
-
* owner, until that owner's `storage.status()` reports space again. Returns
|
|
21882
|
-
* the unsubscribe function.
|
|
21883
|
-
*/
|
|
21884
22009
|
on(event, handler) {
|
|
22010
|
+
if (event === "replica.purge.failed") {
|
|
22011
|
+
const listener = handler;
|
|
22012
|
+
this.replicaPurgeFailedHandlers.add(listener);
|
|
22013
|
+
return () => {
|
|
22014
|
+
this.replicaPurgeFailedHandlers.delete(listener);
|
|
22015
|
+
};
|
|
22016
|
+
}
|
|
21885
22017
|
if (event !== "storage.full") {
|
|
21886
22018
|
throw new Error(`Unknown TinyCloud event: ${String(event)}`);
|
|
21887
22019
|
}
|
|
@@ -22362,6 +22494,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22362
22494
|
const leavingAuth = this.auth;
|
|
22363
22495
|
const leavingContext = this._serviceContext;
|
|
22364
22496
|
let denied;
|
|
22497
|
+
let erased;
|
|
22365
22498
|
try {
|
|
22366
22499
|
this.teardownSession();
|
|
22367
22500
|
} finally {
|
|
@@ -22372,6 +22505,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22372
22505
|
try {
|
|
22373
22506
|
await this.accountRegistryTail;
|
|
22374
22507
|
await this.quiesceReplicaRuntime();
|
|
22508
|
+
if (denied) erased = await this.eraseReplicaLocalCopy("sign_out", denied.status);
|
|
22375
22509
|
} finally {
|
|
22376
22510
|
if (leavingAddress) {
|
|
22377
22511
|
await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
|
|
@@ -22379,8 +22513,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22379
22513
|
}
|
|
22380
22514
|
}
|
|
22381
22515
|
}
|
|
22382
|
-
if (denied?.status === "unguarded") {
|
|
22383
|
-
throw
|
|
22516
|
+
if (denied?.status === "unguarded" && erased?.outcome.status !== "erased") {
|
|
22517
|
+
throw Object.assign(
|
|
22518
|
+
new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`),
|
|
22519
|
+
{
|
|
22520
|
+
code: "REPLICA_PURGE_FAILED",
|
|
22521
|
+
reason: "gate_unavailable",
|
|
22522
|
+
retryNow: erased?.outcome.status === "failed"
|
|
22523
|
+
}
|
|
22524
|
+
);
|
|
22525
|
+
}
|
|
22526
|
+
}
|
|
22527
|
+
/**
|
|
22528
|
+
* Erases the browser local copy after a deny (retry, event and next-load marker live in the lazy
|
|
22529
|
+
* replication authority). Never rejects; `undefined` when replication is off or the storage keeps
|
|
22530
|
+
* no browser database.
|
|
22531
|
+
*/
|
|
22532
|
+
async eraseReplicaLocalCopy(trigger, denyStatus) {
|
|
22533
|
+
const replication = this.config.replication;
|
|
22534
|
+
if (!replication?.enabled) return void 0;
|
|
22535
|
+
try {
|
|
22536
|
+
const { eraseReplicaLocalCopy: eraseReplicaLocalCopy2 } = await getNodeReplicationLoaders().authority();
|
|
22537
|
+
const erased = await eraseReplicaLocalCopy2(
|
|
22538
|
+
replication.storage.lifecycle,
|
|
22539
|
+
trigger,
|
|
22540
|
+
denyStatus,
|
|
22541
|
+
this.replicaPurgeFailedHandlers
|
|
22542
|
+
);
|
|
22543
|
+
if (erased?.logoutPending) this.replicaRestoreGate = "logout_pending";
|
|
22544
|
+
return erased;
|
|
22545
|
+
} catch {
|
|
22546
|
+
return void 0;
|
|
22384
22547
|
}
|
|
22385
22548
|
}
|
|
22386
22549
|
/**
|
|
@@ -22440,6 +22603,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22440
22603
|
this._hooks = void 0;
|
|
22441
22604
|
this._vault = void 0;
|
|
22442
22605
|
this._encryption = void 0;
|
|
22606
|
+
this._namedKeyClient = void 0;
|
|
22607
|
+
this._keysUnavailable = [];
|
|
22443
22608
|
this._baseVaults.clear();
|
|
22444
22609
|
this._baseSecrets.clear();
|
|
22445
22610
|
this._secrets.clear();
|
|
@@ -22516,6 +22681,37 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22516
22681
|
}
|
|
22517
22682
|
await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
|
|
22518
22683
|
}
|
|
22684
|
+
/** Prepare the OpenKey request before the approval click. */
|
|
22685
|
+
async prepareOpenKeySignIn() {
|
|
22686
|
+
if (!this.signer || !this.tc || !this.auth) {
|
|
22687
|
+
throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
|
|
22688
|
+
}
|
|
22689
|
+
await this.signOutInFlight;
|
|
22690
|
+
await this.accountRegistryTail;
|
|
22691
|
+
await this.wasmBindings.ensureInitialized?.();
|
|
22692
|
+
return this.auth.prepareOpenKeySignIn();
|
|
22693
|
+
}
|
|
22694
|
+
/** Start OpenKey authorization synchronously, then complete normal session setup. */
|
|
22695
|
+
signInWithPreparedOpenKey(pending, authorizeFn) {
|
|
22696
|
+
if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
|
|
22697
|
+
throw new Error("Prepared OpenKey sign-in is unavailable");
|
|
22698
|
+
}
|
|
22699
|
+
if (this.consumedOpenKeyPendings.has(pending)) {
|
|
22700
|
+
throw new Error("Prepared OpenKey sign-in was already used");
|
|
22701
|
+
}
|
|
22702
|
+
if (Date.now() - pending.createdAt > 5 * 6e4) {
|
|
22703
|
+
throw new Error("Prepared OpenKey sign-in expired; prepare again");
|
|
22704
|
+
}
|
|
22705
|
+
this.consumedOpenKeyPendings.add(pending);
|
|
22706
|
+
const authorization = authorizeFn(pending.request);
|
|
22707
|
+
void authorization.catch(() => void 0);
|
|
22708
|
+
return this.signInWith(() => {
|
|
22709
|
+
if (this._address !== pending.address || this._chainId !== pending.chainId) {
|
|
22710
|
+
throw new Error("OpenKey account changed after sign-in preparation");
|
|
22711
|
+
}
|
|
22712
|
+
return this.auth.completeOpenKeySignIn(pending, authorization);
|
|
22713
|
+
});
|
|
22714
|
+
}
|
|
22519
22715
|
async signInWith(performSignIn) {
|
|
22520
22716
|
refuseCaveated(this.auth?.capabilityRequest?.manifests);
|
|
22521
22717
|
const epoch = this.sessionEpoch;
|
|
@@ -22544,6 +22740,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22544
22740
|
const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? pkhDid2(this.address, this._chainId) : void 0);
|
|
22545
22741
|
if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
|
|
22546
22742
|
const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
|
|
22743
|
+
if (denial) {
|
|
22744
|
+
await this.quiesceReplicaRuntime();
|
|
22745
|
+
await this.eraseReplicaLocalCopy("account_switch", denial.status);
|
|
22746
|
+
}
|
|
22547
22747
|
this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
|
|
22548
22748
|
}
|
|
22549
22749
|
if (address !== this._address || chainId !== this._chainId) {
|
|
@@ -22558,6 +22758,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22558
22758
|
this._hooks = void 0;
|
|
22559
22759
|
this._vault = void 0;
|
|
22560
22760
|
this._encryption = void 0;
|
|
22761
|
+
this._namedKeyClient = void 0;
|
|
22762
|
+
this._keysUnavailable = [];
|
|
22561
22763
|
this._baseVaults.clear();
|
|
22562
22764
|
this._baseSecrets.clear();
|
|
22563
22765
|
this._secrets.clear();
|
|
@@ -22610,6 +22812,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22610
22812
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
22611
22813
|
await this.ensureRequestedEncryptionNetworks();
|
|
22612
22814
|
this.assertSessionEpoch(epoch, "Sign-in");
|
|
22815
|
+
this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
|
|
22816
|
+
if (this._keysUnavailable.length > 0) {
|
|
22817
|
+
this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
|
|
22818
|
+
}
|
|
22819
|
+
if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
|
|
22820
|
+
for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
|
|
22821
|
+
for (const declaration of manifest.keys ?? []) {
|
|
22822
|
+
if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
|
|
22823
|
+
try {
|
|
22824
|
+
await this.keys.ensureKey(declaration.path);
|
|
22825
|
+
} catch (error) {
|
|
22826
|
+
this._keysUnavailable.push(declaration.path);
|
|
22827
|
+
const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
|
|
22828
|
+
this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
|
|
22829
|
+
}
|
|
22830
|
+
this.assertSessionEpoch(epoch, "Sign-in");
|
|
22831
|
+
}
|
|
22832
|
+
}
|
|
22833
|
+
}
|
|
22834
|
+
}
|
|
22613
22835
|
if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
|
|
22614
22836
|
await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
|
|
22615
22837
|
}
|
|
@@ -23397,7 +23619,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23397
23619
|
const networkIds = /* @__PURE__ */ new Set();
|
|
23398
23620
|
for (const resource of request.resources) {
|
|
23399
23621
|
if (resource.service === ENCRYPTION_PERMISSION_SERVICE2 && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
|
|
23400
|
-
|
|
23622
|
+
const parsed = resource.path.includes("/") ? parseEncryptionResource2(resource.path) : { ...parseNetworkId2(resource.path), kind: "network" };
|
|
23623
|
+
if (parsed.kind === "network") networkIds.add(resource.path);
|
|
23401
23624
|
}
|
|
23402
23625
|
}
|
|
23403
23626
|
return [...networkIds];
|
|
@@ -23947,6 +24170,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23947
24170
|
this._hooks = stagedGraph.hooks;
|
|
23948
24171
|
this._vault = stagedGraph.vault;
|
|
23949
24172
|
this._encryption = stagedGraph.encryption;
|
|
24173
|
+
this._namedKeyClient = void 0;
|
|
24174
|
+
this._keysUnavailable = [];
|
|
23950
24175
|
this._capabilityRegistry = stagedGraph.capabilityRegistry;
|
|
23951
24176
|
this._keyProvider = stagedGraph.keyProvider;
|
|
23952
24177
|
this._sharingService = stagedGraph.sharingService;
|
|
@@ -25300,7 +25525,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
25300
25525
|
* Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
|
|
25301
25526
|
* `POST /revoke`, and return its Authorization header value. The node
|
|
25302
25527
|
* accepts it when the owner delegated `cid` or owns a space its abilities
|
|
25303
|
-
* target, whichever key issued it. Valid for
|
|
25528
|
+
* target, whichever key issued it. Valid for 10 minutes. Requires the
|
|
25304
25529
|
* owner wallet signer.
|
|
25305
25530
|
*/
|
|
25306
25531
|
async signOwnerRevocation(cid) {
|
|
@@ -25316,7 +25541,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
25316
25541
|
chainId: session.chainId,
|
|
25317
25542
|
nonce: generateNonce(),
|
|
25318
25543
|
issuedAt: issuedAt.toISOString(),
|
|
25319
|
-
expirationTime: new Date(issuedAt.getTime() +
|
|
25544
|
+
expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
|
|
25320
25545
|
}).prepareMessage();
|
|
25321
25546
|
const signature2 = await this.signer.signMessage(siwe);
|
|
25322
25547
|
return this.wasmBindings.siweToDelegationHeaders({ siwe, signature: signature2 }).Authorization;
|
|
@@ -25543,6 +25768,40 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
25543
25768
|
get encryption() {
|
|
25544
25769
|
return this.getEncryptionService();
|
|
25545
25770
|
}
|
|
25771
|
+
/** Named keys bound to the current session. */
|
|
25772
|
+
get keys() {
|
|
25773
|
+
return new Proxy({}, {
|
|
25774
|
+
get: (_target, property) => {
|
|
25775
|
+
if (property === "refreshNodeInfo") {
|
|
25776
|
+
return () => {
|
|
25777
|
+
void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
|
|
25778
|
+
};
|
|
25779
|
+
}
|
|
25780
|
+
if (typeof property !== "string" || ![
|
|
25781
|
+
"getPublicKey",
|
|
25782
|
+
"describeKey",
|
|
25783
|
+
"createKey",
|
|
25784
|
+
"blockKey",
|
|
25785
|
+
"unblockKey",
|
|
25786
|
+
"ensureKey",
|
|
25787
|
+
"encrypt",
|
|
25788
|
+
"decrypt",
|
|
25789
|
+
"decryptMany"
|
|
25790
|
+
].includes(property)) return void 0;
|
|
25791
|
+
return (...args) => {
|
|
25792
|
+
this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.js").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
|
|
25793
|
+
return this._namedKeyClient.then((client) => {
|
|
25794
|
+
const method = client[property];
|
|
25795
|
+
return method.apply(client, args);
|
|
25796
|
+
});
|
|
25797
|
+
};
|
|
25798
|
+
}
|
|
25799
|
+
});
|
|
25800
|
+
}
|
|
25801
|
+
/** Manifest key paths unavailable during the latest `signIn()` attempt. */
|
|
25802
|
+
get keysUnavailable() {
|
|
25803
|
+
return this._keysUnavailable;
|
|
25804
|
+
}
|
|
25546
25805
|
async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
|
|
25547
25806
|
const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
|
|
25548
25807
|
return this.fetchEncryptionNetworkAt(
|
|
@@ -27982,7 +28241,7 @@ var FileSessionStorage = class {
|
|
|
27982
28241
|
};
|
|
27983
28242
|
|
|
27984
28243
|
// src/authorization/openKeyBridge.ts
|
|
27985
|
-
function wireOpenKeyAuthorize(openkey) {
|
|
28244
|
+
function wireOpenKeyAuthorize(openkey, opts) {
|
|
27986
28245
|
return async (input) => {
|
|
27987
28246
|
const result = await openkey.authorizeTinyCloud({
|
|
27988
28247
|
protocolVersion: 1,
|
|
@@ -27993,7 +28252,7 @@ function wireOpenKeyAuthorize(openkey) {
|
|
|
27993
28252
|
// Forward the presentation envelope verbatim — the OpenKey SDK
|
|
27994
28253
|
// validates and size-bounds it before use.
|
|
27995
28254
|
presentation: input.presentation
|
|
27996
|
-
});
|
|
28255
|
+
}, opts);
|
|
27997
28256
|
if (result.protocolVersion !== 1) {
|
|
27998
28257
|
throw new Error(
|
|
27999
28258
|
`wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
|
|
@@ -28035,7 +28294,7 @@ import {
|
|
|
28035
28294
|
CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError3,
|
|
28036
28295
|
PermissionNotInManifestError as PermissionNotInManifestError2,
|
|
28037
28296
|
SessionExpiredError as SessionExpiredError2,
|
|
28038
|
-
ManifestValidationError,
|
|
28297
|
+
ManifestValidationError as ManifestValidationError2,
|
|
28039
28298
|
composeManifestRequest as composeManifestRequest2,
|
|
28040
28299
|
resolveManifest as resolveManifest2,
|
|
28041
28300
|
validateManifest,
|
|
@@ -29180,7 +29439,8 @@ var SqliteReplicaHandle = class {
|
|
|
29180
29439
|
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
29181
29440
|
const engine = new (__privateGet(this, _replica)).Replica({
|
|
29182
29441
|
store: __privateGet(this, _store),
|
|
29183
|
-
transportFor
|
|
29442
|
+
transportFor,
|
|
29443
|
+
...this.spec.contentFetch === void 0 ? {} : { contentFetch: this.spec.contentFetch }
|
|
29184
29444
|
});
|
|
29185
29445
|
const running = engine.sync({ signal: abort.signal });
|
|
29186
29446
|
const registration = { promise: running, abort: () => abort.abort() };
|
|
@@ -30699,7 +30959,7 @@ export {
|
|
|
30699
30959
|
InvalidRestoredSessionError,
|
|
30700
30960
|
KVService4 as KVService,
|
|
30701
30961
|
MAX_SECRET_SCOPE_LENGTH,
|
|
30702
|
-
ManifestValidationError,
|
|
30962
|
+
ManifestValidationError2 as ManifestValidationError,
|
|
30703
30963
|
MemorySessionStorage,
|
|
30704
30964
|
NETWORK_NAME_PATTERN,
|
|
30705
30965
|
NetworkIdError,
|