@tinycloud/node-sdk 2.11.0 → 2.11.1-beta.0
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-Bq8TMY7Y.d.cts → core-C6Czyr52.d.cts} +47 -2
- package/dist/{core-Bq8TMY7Y.d.ts → core-C6Czyr52.d.ts} +47 -2
- package/dist/core.cjs +446 -31
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +448 -32
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +152 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +154 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/core.cjs
CHANGED
|
@@ -78,6 +78,7 @@ __export(core_exports, {
|
|
|
78
78
|
VaultPublicSpaceKVActions: () => import_sdk_core16.VaultPublicSpaceKVActions,
|
|
79
79
|
VersionCheckError: () => import_sdk_core20.VersionCheckError,
|
|
80
80
|
WasmKeyProvider: () => WasmKeyProvider,
|
|
81
|
+
activateCompactRuntimeDelegation: () => activateCompactRuntimeDelegation,
|
|
81
82
|
addressStorageKey: () => import_sdk_core11.addressStorageKey,
|
|
82
83
|
buildSpaceUri: () => import_sdk_core19.buildSpaceUri,
|
|
83
84
|
canonicalizeAddress: () => import_sdk_core11.canonicalizeAddress,
|
|
@@ -2179,6 +2180,18 @@ var ROOT_DELEGATION_ACTIONS = [
|
|
|
2179
2180
|
import_sdk_core8.DUCKDB.ALL
|
|
2180
2181
|
];
|
|
2181
2182
|
var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core8.EXPIRY.SESSION_MS;
|
|
2183
|
+
function unifiedRootAttenuation(capabilities) {
|
|
2184
|
+
const attenuation = {};
|
|
2185
|
+
for (const capability of capabilities) {
|
|
2186
|
+
if (attenuation[capability.resource] !== void 0) throw new Error("unified owner root contains a duplicate resource");
|
|
2187
|
+
attenuation[capability.resource] = capability.kind === "encryption" ? { [capability.action]: [{}] } : Object.fromEntries(capability.actions.map((action) => [action, [{
|
|
2188
|
+
type: "xyz.tinycloud.resource/selector",
|
|
2189
|
+
kind: capability.selector,
|
|
2190
|
+
value: capability.resource
|
|
2191
|
+
}]]));
|
|
2192
|
+
}
|
|
2193
|
+
return attenuation;
|
|
2194
|
+
}
|
|
2182
2195
|
function decodeAuthorizationBytes(authorization) {
|
|
2183
2196
|
const encoded = authorization.replace(/^Bearer /i, "");
|
|
2184
2197
|
const match = /^([A-Za-z0-9_-]+)(={1,2})?$/.exec(encoded);
|
|
@@ -2558,6 +2571,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2558
2571
|
* hosting is confirmed against the session that was active at the time.
|
|
2559
2572
|
*/
|
|
2560
2573
|
this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
|
|
2574
|
+
/** Serializes account-registry writes for the active node instance. */
|
|
2575
|
+
this.accountRegistryTail = Promise.resolve();
|
|
2561
2576
|
/**
|
|
2562
2577
|
* True when the last signIn() detected an interactive signer and skipped
|
|
2563
2578
|
* client-side bootstrap. Apps can read this to know whether bootstrap was
|
|
@@ -2954,6 +2969,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2954
2969
|
"Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
|
|
2955
2970
|
);
|
|
2956
2971
|
}
|
|
2972
|
+
await this.accountRegistryTail;
|
|
2957
2973
|
await this.wasmBindings.ensureInitialized?.();
|
|
2958
2974
|
this._address = (0, import_sdk_core8.canonicalizeAddress)(await this.signer.getAddress());
|
|
2959
2975
|
this._chainId = await this.signer.getChainId();
|
|
@@ -3435,16 +3451,48 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3435
3451
|
}
|
|
3436
3452
|
}
|
|
3437
3453
|
scheduleAccountRegistrySync() {
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3454
|
+
const session = this.currentTinyCloudSession();
|
|
3455
|
+
if (!session || this.pendingAccountRegistrySync?.session === session) {
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
const promise = this.enqueueAccountRegistryOperation(session, async () => {
|
|
3459
|
+
await this.withAccountRegistryRetry(async () => {
|
|
3460
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
await this.account.index.ensure();
|
|
3464
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
await this.writeManifestRegistryRecords();
|
|
3468
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
if (this.currentSessionCanListSpaces()) {
|
|
3472
|
+
const spaces = await this.account.spaces.syncAccessible();
|
|
3473
|
+
if (!spaces.ok) {
|
|
3474
|
+
throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
|
|
3475
|
+
}
|
|
3445
3476
|
}
|
|
3477
|
+
});
|
|
3478
|
+
});
|
|
3479
|
+
this.pendingAccountRegistrySync = { session, promise };
|
|
3480
|
+
const clearPendingSync = () => {
|
|
3481
|
+
if (this.pendingAccountRegistrySync?.promise === promise) {
|
|
3482
|
+
this.pendingAccountRegistrySync = void 0;
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
void promise.then(clearPendingSync, clearPendingSync);
|
|
3486
|
+
}
|
|
3487
|
+
enqueueAccountRegistryOperation(session, task) {
|
|
3488
|
+
const operation = this.accountRegistryTail.then(async () => {
|
|
3489
|
+
if (this.currentTinyCloudSession() === session) {
|
|
3490
|
+
await task();
|
|
3446
3491
|
}
|
|
3447
3492
|
});
|
|
3493
|
+
this.accountRegistryTail = operation.catch(() => {
|
|
3494
|
+
});
|
|
3495
|
+
return operation;
|
|
3448
3496
|
}
|
|
3449
3497
|
/**
|
|
3450
3498
|
* Whether the current primary session may invoke `tinycloud.space/list`.
|
|
@@ -3597,7 +3645,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3597
3645
|
* @returns The hosted space URI.
|
|
3598
3646
|
*/
|
|
3599
3647
|
async hostOwnedSpace(name) {
|
|
3600
|
-
|
|
3648
|
+
const session = this.auth?.tinyCloudSession;
|
|
3649
|
+
if (!this.auth || !session) {
|
|
3601
3650
|
throw new Error("Not signed in. Call signIn() first.");
|
|
3602
3651
|
}
|
|
3603
3652
|
const spaceId = this.ownedSpaceId(name);
|
|
@@ -3613,22 +3662,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3613
3662
|
}
|
|
3614
3663
|
const activation = await (0, import_sdk_core8.activateSessionWithHost)(
|
|
3615
3664
|
host,
|
|
3616
|
-
|
|
3665
|
+
session.delegationHeader
|
|
3617
3666
|
);
|
|
3618
3667
|
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
3619
3668
|
throw new Error(
|
|
3620
3669
|
`Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
|
|
3621
3670
|
);
|
|
3622
3671
|
}
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3672
|
+
try {
|
|
3673
|
+
await this.enqueueAccountRegistryOperation(session, async () => {
|
|
3674
|
+
await this.account.spaces.register({
|
|
3675
|
+
spaceId,
|
|
3676
|
+
name,
|
|
3677
|
+
ownerDid: this.did,
|
|
3678
|
+
type: "owned",
|
|
3679
|
+
permissions: ["*"],
|
|
3680
|
+
status: "active"
|
|
3681
|
+
});
|
|
3682
|
+
});
|
|
3683
|
+
} catch {
|
|
3684
|
+
}
|
|
3632
3685
|
return spaceId;
|
|
3633
3686
|
}
|
|
3634
3687
|
/**
|
|
@@ -3663,23 +3716,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3663
3716
|
if (!this.auth || !this.auth.tinyCloudSession) {
|
|
3664
3717
|
throw new Error("Not signed in. Call signIn() first.");
|
|
3665
3718
|
}
|
|
3719
|
+
await this.accountRegistryTail;
|
|
3666
3720
|
const spaceId = this.ownedSpaceId(name);
|
|
3667
3721
|
if (await this.isOwnedSpaceRegistered(spaceId)) {
|
|
3668
3722
|
return spaceId;
|
|
3669
3723
|
}
|
|
3670
|
-
|
|
3671
|
-
try {
|
|
3672
|
-
await this.account.spaces.register({
|
|
3673
|
-
spaceId,
|
|
3674
|
-
name,
|
|
3675
|
-
ownerDid: this.did,
|
|
3676
|
-
type: "owned",
|
|
3677
|
-
permissions: ["*"],
|
|
3678
|
-
status: "active"
|
|
3679
|
-
});
|
|
3680
|
-
} catch {
|
|
3681
|
-
}
|
|
3682
|
-
return hosted;
|
|
3724
|
+
return this.hostOwnedSpace(name);
|
|
3683
3725
|
}
|
|
3684
3726
|
/** Resolve and authenticate the owner encoded by an owned-space URI. */
|
|
3685
3727
|
credentialSpaceOwnerDid(spaceId) {
|
|
@@ -4232,6 +4274,46 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4232
4274
|
if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
|
|
4233
4275
|
return signBytesWithJwk(bytes, session.jwk);
|
|
4234
4276
|
}
|
|
4277
|
+
/** Author a proofless Policy/v3 owner root with the authenticated holder key. */
|
|
4278
|
+
async createUnifiedOwnerRoot(input) {
|
|
4279
|
+
const ownerDid = this.credentialHolderDid;
|
|
4280
|
+
if (input.ownerDid !== ownerDid) throw new Error("unified owner root signer does not match owner DID");
|
|
4281
|
+
const facts = {
|
|
4282
|
+
role: input.role,
|
|
4283
|
+
mode: input.role === "policy-authority" ? "policy-source" : "conditional-mint",
|
|
4284
|
+
ownerDid,
|
|
4285
|
+
policyId: input.policyId,
|
|
4286
|
+
policyDigestHex: input.policyDigestHex,
|
|
4287
|
+
policyCid: input.policyCid,
|
|
4288
|
+
contentSourceDigestHex: input.contentSourceDigestHex,
|
|
4289
|
+
capabilityCeilingHashHex: input.capabilityCeilingHashHex,
|
|
4290
|
+
nativeProjectionHashHex: input.nativeProjectionHashHex,
|
|
4291
|
+
nodeAudience: input.nodeAudience,
|
|
4292
|
+
...input.role === "policy-enforcement" ? { enforcerDid: input.audienceDid } : {}
|
|
4293
|
+
};
|
|
4294
|
+
const root = await (0, import_sdk_core8.signCompactUcanRootAuthorization)({
|
|
4295
|
+
issuerDid: ownerDid,
|
|
4296
|
+
audienceDid: input.audienceDid,
|
|
4297
|
+
attenuation: unifiedRootAttenuation(input.capabilities),
|
|
4298
|
+
facts: [facts],
|
|
4299
|
+
notBefore: Math.floor(input.notBefore.getTime() / 1e3),
|
|
4300
|
+
expiresAt: Math.floor(input.expiresAt.getTime() / 1e3),
|
|
4301
|
+
nonce: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
|
|
4302
|
+
sign: (bytes) => this.signSessionBytes(bytes)
|
|
4303
|
+
});
|
|
4304
|
+
const kv = input.capabilities.find((capability) => capability.kind === "kv");
|
|
4305
|
+
const resource = kv?.resource ?? "";
|
|
4306
|
+
const marker = resource.indexOf("/kv/");
|
|
4307
|
+
return {
|
|
4308
|
+
cid: root.cid,
|
|
4309
|
+
delegationHeader: { Authorization: root.authorization },
|
|
4310
|
+
delegateDID: input.audienceDid,
|
|
4311
|
+
spaceId: marker === -1 ? "" : resource.slice("tinycloud://".length, marker),
|
|
4312
|
+
path: marker === -1 ? resource : resource.slice(marker + 4),
|
|
4313
|
+
actions: kv?.actions ?? [],
|
|
4314
|
+
expiry: input.expiresAt
|
|
4315
|
+
};
|
|
4316
|
+
}
|
|
4235
4317
|
/** Apply only an already-enabled signing policy to the exact credential request. */
|
|
4236
4318
|
async autoSignCredentialBytes(bytes) {
|
|
4237
4319
|
const strategy = this.config.signStrategy;
|
|
@@ -7264,6 +7346,338 @@ var import_sdk_core12 = require("@tinycloud/sdk-core");
|
|
|
7264
7346
|
|
|
7265
7347
|
// src/delegation.ts
|
|
7266
7348
|
var import_sdk_core9 = require("@tinycloud/sdk-core");
|
|
7349
|
+
var SERVICE_BY_SHORT_NAME = {
|
|
7350
|
+
kv: "tinycloud.kv",
|
|
7351
|
+
sql: "tinycloud.sql",
|
|
7352
|
+
duckdb: "tinycloud.duckdb",
|
|
7353
|
+
hooks: "tinycloud.hooks"
|
|
7354
|
+
};
|
|
7355
|
+
function authorizationWithoutBearer(authorization) {
|
|
7356
|
+
return authorization.replace(/^Bearer /i, "");
|
|
7357
|
+
}
|
|
7358
|
+
function normalizedHost(host) {
|
|
7359
|
+
if (typeof host !== "string" || host.length === 0) {
|
|
7360
|
+
throw new Error("Validated runtime delegation requires a non-empty host.");
|
|
7361
|
+
}
|
|
7362
|
+
return host.replace(/\/+$/, "");
|
|
7363
|
+
}
|
|
7364
|
+
function didPrincipalsMatch(left, right) {
|
|
7365
|
+
try {
|
|
7366
|
+
return (0, import_sdk_core9.principalDidEquals)(left, right);
|
|
7367
|
+
} catch {
|
|
7368
|
+
return left === right;
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
function compactUcanPayload(authorization) {
|
|
7372
|
+
const compact = authorizationWithoutBearer(authorization);
|
|
7373
|
+
const parts = compact.split(".");
|
|
7374
|
+
if (parts.length !== 3 || parts.some((part) => part.length === 0)) {
|
|
7375
|
+
throw new Error(
|
|
7376
|
+
"Validated runtime delegation requires a compact UCAN authorization so its signed authority can be derived."
|
|
7377
|
+
);
|
|
7378
|
+
}
|
|
7379
|
+
try {
|
|
7380
|
+
const payload = JSON.parse(
|
|
7381
|
+
new TextDecoder("utf-8", { fatal: true }).decode((0, import_sdk_core9.decodeBase64Url)(parts[1]))
|
|
7382
|
+
);
|
|
7383
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
7384
|
+
throw new Error("payload is not an object");
|
|
7385
|
+
}
|
|
7386
|
+
return payload;
|
|
7387
|
+
} catch {
|
|
7388
|
+
throw new Error(
|
|
7389
|
+
"Validated runtime delegation authorization has an invalid compact UCAN payload."
|
|
7390
|
+
);
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
function canonicalPermissionEntries(permissions) {
|
|
7394
|
+
return permissions.map((permission) => ({
|
|
7395
|
+
...permission,
|
|
7396
|
+
actions: [...permission.actions].sort()
|
|
7397
|
+
})).sort(
|
|
7398
|
+
(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")) || (0, import_sdk_core9.canonicalizeRecapCaveats)(left.caveats).localeCompare((0, import_sdk_core9.canonicalizeRecapCaveats)(right.caveats))
|
|
7399
|
+
);
|
|
7400
|
+
}
|
|
7401
|
+
function signedCaveats(value) {
|
|
7402
|
+
if (!Array.isArray(value) || value.some(
|
|
7403
|
+
(caveat) => caveat === null || typeof caveat !== "object" || Array.isArray(caveat)
|
|
7404
|
+
)) {
|
|
7405
|
+
throw new Error("Validated runtime delegation has malformed signed ReCap caveats.");
|
|
7406
|
+
}
|
|
7407
|
+
const cloned = JSON.parse(JSON.stringify(value));
|
|
7408
|
+
return (0, import_sdk_core9.canonicalizeRecapCaveats)(cloned) === (0, import_sdk_core9.canonicalizeRecapCaveats)(void 0) ? void 0 : cloned;
|
|
7409
|
+
}
|
|
7410
|
+
function groupedSignedEntries(service, space, path, abilities) {
|
|
7411
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7412
|
+
for (const [action, rawCaveats] of Object.entries(abilities)) {
|
|
7413
|
+
const caveats = signedCaveats(rawCaveats);
|
|
7414
|
+
const identity = (0, import_sdk_core9.canonicalizeRecapCaveats)(caveats);
|
|
7415
|
+
const group = groups.get(identity) ?? { actions: [], caveats };
|
|
7416
|
+
group.actions.push(action);
|
|
7417
|
+
groups.set(identity, group);
|
|
7418
|
+
}
|
|
7419
|
+
return [...groups.values()].map((group) => ({
|
|
7420
|
+
service,
|
|
7421
|
+
...space === void 0 ? {} : { space },
|
|
7422
|
+
path,
|
|
7423
|
+
actions: group.actions.sort(),
|
|
7424
|
+
...group.caveats === void 0 ? {} : { caveats: group.caveats }
|
|
7425
|
+
}));
|
|
7426
|
+
}
|
|
7427
|
+
function signedAuthorityFromCompactUcan(authorization) {
|
|
7428
|
+
const payload = compactUcanPayload(authorization);
|
|
7429
|
+
if (typeof payload.aud !== "string" || payload.aud.length === 0) {
|
|
7430
|
+
throw new Error("Validated runtime delegation is missing a signed audience.");
|
|
7431
|
+
}
|
|
7432
|
+
if (typeof payload.iss !== "string" || payload.iss.length === 0) {
|
|
7433
|
+
throw new Error("Validated runtime delegation is missing a signed issuer.");
|
|
7434
|
+
}
|
|
7435
|
+
if (!Array.isArray(payload.prf) || payload.prf.some(
|
|
7436
|
+
(proof) => typeof proof !== "string" || proof.length === 0
|
|
7437
|
+
)) {
|
|
7438
|
+
throw new Error("Validated runtime delegation has malformed signed proofs.");
|
|
7439
|
+
}
|
|
7440
|
+
if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
|
|
7441
|
+
throw new Error("Validated runtime delegation is missing a signed expiry.");
|
|
7442
|
+
}
|
|
7443
|
+
const expiry = new Date(payload.exp * 1e3);
|
|
7444
|
+
if (Number.isNaN(expiry.getTime())) {
|
|
7445
|
+
throw new Error("Validated runtime delegation has an invalid signed expiry.");
|
|
7446
|
+
}
|
|
7447
|
+
if (payload.att === null || typeof payload.att !== "object" || Array.isArray(payload.att)) {
|
|
7448
|
+
throw new Error("Validated runtime delegation is missing a signed attenuation.");
|
|
7449
|
+
}
|
|
7450
|
+
const fact = Array.isArray(payload.fct) && payload.fct.length > 0 && payload.fct[0] !== null && typeof payload.fct[0] === "object" && !Array.isArray(payload.fct[0]) ? payload.fct[0] : void 0;
|
|
7451
|
+
const remainingRedelegationDepth = fact?.remainingRedelegationDepth;
|
|
7452
|
+
if (remainingRedelegationDepth !== void 0 && (!Number.isInteger(remainingRedelegationDepth) || remainingRedelegationDepth < 0 || remainingRedelegationDepth > 8)) {
|
|
7453
|
+
throw new Error("Validated runtime delegation has invalid signed redelegation depth.");
|
|
7454
|
+
}
|
|
7455
|
+
const permissions = [];
|
|
7456
|
+
for (const [resource, abilities] of Object.entries(payload.att)) {
|
|
7457
|
+
if (abilities === null || typeof abilities !== "object" || Array.isArray(abilities)) {
|
|
7458
|
+
throw new Error("Validated runtime delegation has an invalid signed attenuation.");
|
|
7459
|
+
}
|
|
7460
|
+
const abilityEntries = Object.entries(abilities);
|
|
7461
|
+
if (abilityEntries.length === 0) {
|
|
7462
|
+
throw new Error("Validated runtime delegation has an empty signed capability.");
|
|
7463
|
+
}
|
|
7464
|
+
if (resource.startsWith("urn:tinycloud:encryption:")) {
|
|
7465
|
+
permissions.push(...groupedSignedEntries(
|
|
7466
|
+
"tinycloud.encryption",
|
|
7467
|
+
void 0,
|
|
7468
|
+
resource,
|
|
7469
|
+
abilities
|
|
7470
|
+
));
|
|
7471
|
+
continue;
|
|
7472
|
+
}
|
|
7473
|
+
const nativeMatch = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
|
|
7474
|
+
const policyMatch = /^tinycloud:\/\/([^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
|
|
7475
|
+
const match = nativeMatch ?? policyMatch;
|
|
7476
|
+
if (!match) {
|
|
7477
|
+
throw new Error(
|
|
7478
|
+
`Validated runtime delegation has an unsupported signed resource '${resource}'.`
|
|
7479
|
+
);
|
|
7480
|
+
}
|
|
7481
|
+
const [, space, shortService, path] = match;
|
|
7482
|
+
const service = SERVICE_BY_SHORT_NAME[shortService];
|
|
7483
|
+
if (service === void 0) {
|
|
7484
|
+
throw new Error(
|
|
7485
|
+
`Validated runtime delegation has an unsupported signed service '${shortService}'.`
|
|
7486
|
+
);
|
|
7487
|
+
}
|
|
7488
|
+
permissions.push(...groupedSignedEntries(
|
|
7489
|
+
service,
|
|
7490
|
+
space,
|
|
7491
|
+
path,
|
|
7492
|
+
abilities
|
|
7493
|
+
));
|
|
7494
|
+
}
|
|
7495
|
+
if (permissions.length === 0) {
|
|
7496
|
+
throw new Error("Validated runtime delegation has no signed capabilities.");
|
|
7497
|
+
}
|
|
7498
|
+
const canonicalPermissions = canonicalPermissionEntries(permissions);
|
|
7499
|
+
const canonicalResources = canonicalPermissions.map((permission) => ({
|
|
7500
|
+
service: permission.service === "tinycloud.encryption" ? "encryption" : permission.service.slice("tinycloud.".length),
|
|
7501
|
+
space: permission.service === "tinycloud.encryption" ? "encryption" : permission.space,
|
|
7502
|
+
path: permission.path,
|
|
7503
|
+
actions: [...permission.actions],
|
|
7504
|
+
...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
|
|
7505
|
+
}));
|
|
7506
|
+
return {
|
|
7507
|
+
issuer: payload.iss,
|
|
7508
|
+
audience: payload.aud,
|
|
7509
|
+
expiry,
|
|
7510
|
+
proofs: [...payload.prf],
|
|
7511
|
+
...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
|
|
7512
|
+
permissions: canonicalPermissions,
|
|
7513
|
+
resources: canonicalResources
|
|
7514
|
+
};
|
|
7515
|
+
}
|
|
7516
|
+
function canonicalResourcesFromPortableDelegation(delegation) {
|
|
7517
|
+
if (delegation.resources !== void 0 && delegation.resources.length > 0) {
|
|
7518
|
+
return delegation.resources.map((resource) => ({ ...resource, actions: [...resource.actions].sort() })).sort(
|
|
7519
|
+
(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")) || (0, import_sdk_core9.canonicalizeRecapCaveats)(left.caveats).localeCompare((0, import_sdk_core9.canonicalizeRecapCaveats)(right.caveats))
|
|
7520
|
+
);
|
|
7521
|
+
}
|
|
7522
|
+
const byService = /* @__PURE__ */ new Map();
|
|
7523
|
+
for (const action of delegation.actions) {
|
|
7524
|
+
const [service] = action.split("/", 1);
|
|
7525
|
+
const shortService = service.replace(/^tinycloud\./, "");
|
|
7526
|
+
const values = byService.get(shortService) ?? [];
|
|
7527
|
+
values.push(action);
|
|
7528
|
+
byService.set(shortService, values);
|
|
7529
|
+
}
|
|
7530
|
+
return [...byService.entries()].map(([service, actions]) => ({
|
|
7531
|
+
service,
|
|
7532
|
+
space: service === "encryption" ? "encryption" : delegation.spaceId,
|
|
7533
|
+
path: delegation.path,
|
|
7534
|
+
actions: [...new Set(actions)].sort(),
|
|
7535
|
+
...delegation.caveats === void 0 ? {} : { caveats: delegation.caveats }
|
|
7536
|
+
})).sort(
|
|
7537
|
+
(left, right) => left.service.localeCompare(right.service) || left.space.localeCompare(right.space) || left.path.localeCompare(right.path)
|
|
7538
|
+
);
|
|
7539
|
+
}
|
|
7540
|
+
function resourcesMatch(actual, expected) {
|
|
7541
|
+
const comparable = (resource) => ({
|
|
7542
|
+
...resource,
|
|
7543
|
+
caveats: (0, import_sdk_core9.canonicalizeRecapCaveats)(resource.caveats)
|
|
7544
|
+
});
|
|
7545
|
+
return JSON.stringify(actual.map(comparable)) === JSON.stringify(expected.map(comparable));
|
|
7546
|
+
}
|
|
7547
|
+
async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
7548
|
+
const host = normalizedHost(options.host);
|
|
7549
|
+
if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
|
|
7550
|
+
throw new Error(
|
|
7551
|
+
`Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
|
|
7552
|
+
);
|
|
7553
|
+
}
|
|
7554
|
+
if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
|
|
7555
|
+
throw new Error("Runtime delegation has an invalid expiry.");
|
|
7556
|
+
}
|
|
7557
|
+
if (delegation.expiry.getTime() <= Date.now()) {
|
|
7558
|
+
throw new Error("Runtime delegation is expired.");
|
|
7559
|
+
}
|
|
7560
|
+
const authorization = delegation.delegationHeader?.Authorization;
|
|
7561
|
+
if (typeof authorization !== "string" || authorization.length === 0) {
|
|
7562
|
+
throw new Error("Runtime delegation is missing authorization bytes.");
|
|
7563
|
+
}
|
|
7564
|
+
const cid = node.computeDelegationCid(authorization);
|
|
7565
|
+
if (cid !== delegation.cid) {
|
|
7566
|
+
throw new Error("Runtime delegation CID does not match authorization bytes.");
|
|
7567
|
+
}
|
|
7568
|
+
const signed = signedAuthorityFromCompactUcan(authorization);
|
|
7569
|
+
if (signed.expiry.getTime() <= Date.now()) {
|
|
7570
|
+
throw new Error("Runtime delegation is expired.");
|
|
7571
|
+
}
|
|
7572
|
+
if (delegation.expiry.getTime() !== signed.expiry.getTime()) {
|
|
7573
|
+
throw new Error("Runtime delegation expiry does not match signed authority.");
|
|
7574
|
+
}
|
|
7575
|
+
if (!didPrincipalsMatch(signed.audience, node.sessionDid)) {
|
|
7576
|
+
throw new Error(
|
|
7577
|
+
`Runtime delegation targets ${signed.audience} but this session is ${node.sessionDid}.`
|
|
7578
|
+
);
|
|
7579
|
+
}
|
|
7580
|
+
if (!didPrincipalsMatch(delegation.delegateDID, signed.audience)) {
|
|
7581
|
+
throw new Error("Runtime delegation audience does not match signed authority.");
|
|
7582
|
+
}
|
|
7583
|
+
const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
|
|
7584
|
+
if (!resourcesMatch(declaredResources, signed.resources)) {
|
|
7585
|
+
throw new Error("Runtime delegation resources do not match signed authority.");
|
|
7586
|
+
}
|
|
7587
|
+
const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
|
|
7588
|
+
if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
|
|
7589
|
+
throw new Error(
|
|
7590
|
+
"A different authorization is already installed for this runtime delegation CID."
|
|
7591
|
+
);
|
|
7592
|
+
}
|
|
7593
|
+
const primary = signed.resources[0];
|
|
7594
|
+
const signedSpace = signed.permissions.find(
|
|
7595
|
+
(permission) => permission.service !== "tinycloud.encryption"
|
|
7596
|
+
)?.space;
|
|
7597
|
+
const installedCandidate = {
|
|
7598
|
+
cid,
|
|
7599
|
+
delegationHeader: { Authorization: authorization },
|
|
7600
|
+
ownerAddress: delegation.ownerAddress,
|
|
7601
|
+
chainId: delegation.chainId,
|
|
7602
|
+
delegatorDID: signed.issuer,
|
|
7603
|
+
...signed.proofs.length === 1 ? { parentCid: signed.proofs[0] } : {},
|
|
7604
|
+
...signed.remainingRedelegationDepth === void 0 ? {} : {
|
|
7605
|
+
allowSubDelegation: signed.remainingRedelegationDepth > 0,
|
|
7606
|
+
disableSubDelegation: signed.remainingRedelegationDepth === 0
|
|
7607
|
+
},
|
|
7608
|
+
authHeader: authorization,
|
|
7609
|
+
spaceId: signedSpace ?? delegation.spaceId,
|
|
7610
|
+
path: primary.path,
|
|
7611
|
+
actions: [...primary.actions],
|
|
7612
|
+
...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
|
|
7613
|
+
resources: signed.resources.map((resource) => ({
|
|
7614
|
+
...resource,
|
|
7615
|
+
actions: [...resource.actions]
|
|
7616
|
+
})),
|
|
7617
|
+
expiry: new Date(signed.expiry),
|
|
7618
|
+
delegateDID: signed.audience,
|
|
7619
|
+
host
|
|
7620
|
+
};
|
|
7621
|
+
if (node.isSessionOnly === true && typeof node.did === "string" && (0, import_sdk_core9.principalDidEquals)(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
|
|
7622
|
+
await node.useDelegation(installedCandidate);
|
|
7623
|
+
} else {
|
|
7624
|
+
await node.useRuntimeDelegation(installedCandidate);
|
|
7625
|
+
}
|
|
7626
|
+
const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
|
|
7627
|
+
if (!installed) {
|
|
7628
|
+
throw new Error("Runtime delegation activation did not install the validated authority.");
|
|
7629
|
+
}
|
|
7630
|
+
if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed.audience) || installed.expiry.getTime() !== signed.expiry.getTime() || !resourcesMatch(
|
|
7631
|
+
canonicalResourcesFromPortableDelegation(installed),
|
|
7632
|
+
signed.resources
|
|
7633
|
+
) || normalizedHost(installed.host ?? "") !== host) {
|
|
7634
|
+
throw new Error(
|
|
7635
|
+
"Runtime delegation activation installed authority that differs from the validated authority."
|
|
7636
|
+
);
|
|
7637
|
+
}
|
|
7638
|
+
return {
|
|
7639
|
+
cid,
|
|
7640
|
+
delegation: installed,
|
|
7641
|
+
effectivePermissions: signed.permissions,
|
|
7642
|
+
expiry: new Date(signed.expiry),
|
|
7643
|
+
audience: signed.audience,
|
|
7644
|
+
host: normalizedHost(installed.host ?? host)
|
|
7645
|
+
};
|
|
7646
|
+
}
|
|
7647
|
+
async function activateCompactRuntimeDelegation(node, input) {
|
|
7648
|
+
const authorization = input.authorization;
|
|
7649
|
+
const cid = node.computeDelegationCid(authorization);
|
|
7650
|
+
if (cid !== input.cid) {
|
|
7651
|
+
throw new Error("Compact runtime delegation CID does not match authorization bytes.");
|
|
7652
|
+
}
|
|
7653
|
+
const signed = signedAuthorityFromCompactUcan(authorization);
|
|
7654
|
+
const primary = signed.resources[0];
|
|
7655
|
+
const signedSpace = signed.permissions.find(
|
|
7656
|
+
(permission) => permission.service !== "tinycloud.encryption"
|
|
7657
|
+
)?.space;
|
|
7658
|
+
const delegation = {
|
|
7659
|
+
cid,
|
|
7660
|
+
delegationHeader: { Authorization: authorization },
|
|
7661
|
+
ownerAddress: input.ownerAddress,
|
|
7662
|
+
chainId: input.chainId,
|
|
7663
|
+
delegatorDID: signed.issuer,
|
|
7664
|
+
authHeader: authorization,
|
|
7665
|
+
spaceId: signedSpace ?? primary.space,
|
|
7666
|
+
path: primary.path,
|
|
7667
|
+
actions: [...primary.actions],
|
|
7668
|
+
...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
|
|
7669
|
+
resources: signed.resources.map((resource) => ({
|
|
7670
|
+
...resource,
|
|
7671
|
+
actions: [...resource.actions]
|
|
7672
|
+
})),
|
|
7673
|
+
expiry: new Date(signed.expiry),
|
|
7674
|
+
delegateDID: signed.audience,
|
|
7675
|
+
host: normalizedHost(input.host)
|
|
7676
|
+
};
|
|
7677
|
+
return activateValidatedRuntimeDelegation(node, delegation, {
|
|
7678
|
+
host: input.host
|
|
7679
|
+
});
|
|
7680
|
+
}
|
|
7267
7681
|
function serializeDelegation(delegation) {
|
|
7268
7682
|
return JSON.stringify({
|
|
7269
7683
|
...delegation,
|
|
@@ -7339,6 +7753,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
|
|
|
7339
7753
|
VaultPublicSpaceKVActions,
|
|
7340
7754
|
VersionCheckError,
|
|
7341
7755
|
WasmKeyProvider,
|
|
7756
|
+
activateCompactRuntimeDelegation,
|
|
7342
7757
|
addressStorageKey,
|
|
7343
7758
|
buildSpaceUri,
|
|
7344
7759
|
canonicalizeAddress,
|