@tinycloud/node-sdk 2.6.0-beta.0 → 2.6.0-beta.1
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-ojeY-wet.d.cts → core-Cr-zD1lk.d.cts} +42 -0
- package/dist/{core-ojeY-wet.d.ts → core-Cr-zD1lk.d.ts} +42 -0
- package/dist/core.cjs +125 -10
- 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 +125 -10
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +125 -10
- 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 +125 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -910,6 +910,12 @@ declare class TinyCloudNode {
|
|
|
910
910
|
private _delegationManager?;
|
|
911
911
|
private _spaceService?;
|
|
912
912
|
private runtimePermissionGrants;
|
|
913
|
+
/**
|
|
914
|
+
* Memoized `recapOperationsFromSession` result, keyed by the exact SIWE it
|
|
915
|
+
* was parsed from. The primary session is stable for the life of a sign-in,
|
|
916
|
+
* so this avoids re-parsing the recap on every registration.
|
|
917
|
+
*/
|
|
918
|
+
private _recapOperationsCache?;
|
|
913
919
|
/**
|
|
914
920
|
* TinyCloudSession captured by {@link restoreSession} when there's no
|
|
915
921
|
* auth-layer signer available (session-only mode used by OpenKey-backed
|
|
@@ -1052,6 +1058,42 @@ declare class TinyCloudNode {
|
|
|
1052
1058
|
private isFreshBootstrapAccount;
|
|
1053
1059
|
private runAccountBootstrap;
|
|
1054
1060
|
private registerBootstrapRuntimeGrant;
|
|
1061
|
+
/**
|
|
1062
|
+
* Map the base session's OWN recap into runtime permission operations.
|
|
1063
|
+
*
|
|
1064
|
+
* Uses the RAW `parseRecapFromSiwe` binding — NOT `parseRecapCapabilities`,
|
|
1065
|
+
* whose `normalizeSpace` collapses `tinycloud:pkh:...:<owner>:<space>` to a
|
|
1066
|
+
* bare short name and would conflate two owners' identically-named spaces.
|
|
1067
|
+
* We must keep the full owner-scoped URI so a synthetic primary grant can
|
|
1068
|
+
* never cover an operation on a different owner's space.
|
|
1069
|
+
*
|
|
1070
|
+
* Mirrors {@link operationsFromDelegation}'s op shape: encryption network
|
|
1071
|
+
* entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
|
|
1072
|
+
* other entry becomes a `spaceId` op carrying the raw recap `space` URI.
|
|
1073
|
+
* One op per action.
|
|
1074
|
+
*
|
|
1075
|
+
* Returns `[]` for session-only / restored-without-siwe modes and for any
|
|
1076
|
+
* unparseable SIWE — the primary grant is simply not registered in that case.
|
|
1077
|
+
*/
|
|
1078
|
+
private recapOperationsFromSession;
|
|
1079
|
+
/**
|
|
1080
|
+
* Register the base (primary) session's own recap as a synthetic runtime
|
|
1081
|
+
* grant tagged `provenance: "primary"` so it always out-ranks other covering
|
|
1082
|
+
* grants in {@link findGrantForOperations}. This closes the selection-design
|
|
1083
|
+
* hazard where a broad — possibly broken — bootstrap/delegated grant could
|
|
1084
|
+
* hijack an operation the primary session itself already authorized (TC-111).
|
|
1085
|
+
*
|
|
1086
|
+
* Two safety exclusions:
|
|
1087
|
+
* - Ops whose space is in `lastActivationSkippedSpaceIds` are dropped: the
|
|
1088
|
+
* node refused to activate those spaces this sign-in even though the recap
|
|
1089
|
+
* claims them. Including them would let the synthetic primary out-rank a
|
|
1090
|
+
* working grant and 401 (the "skipped-activation inverted hijack").
|
|
1091
|
+
* - Encryption `resource` ops are kept as-is (space-independent).
|
|
1092
|
+
*
|
|
1093
|
+
* No-ops when nothing remains after exclusion. Callers (`signIn`,
|
|
1094
|
+
* `restoreSession`) clear `runtimePermissionGrants` first, so no dupes.
|
|
1095
|
+
*/
|
|
1096
|
+
private registerPrimarySessionGrant;
|
|
1055
1097
|
private writeManifestRegistryRecords;
|
|
1056
1098
|
private scheduleAccountRegistrySync;
|
|
1057
1099
|
private withAccountRegistryRetry;
|
|
@@ -910,6 +910,12 @@ declare class TinyCloudNode {
|
|
|
910
910
|
private _delegationManager?;
|
|
911
911
|
private _spaceService?;
|
|
912
912
|
private runtimePermissionGrants;
|
|
913
|
+
/**
|
|
914
|
+
* Memoized `recapOperationsFromSession` result, keyed by the exact SIWE it
|
|
915
|
+
* was parsed from. The primary session is stable for the life of a sign-in,
|
|
916
|
+
* so this avoids re-parsing the recap on every registration.
|
|
917
|
+
*/
|
|
918
|
+
private _recapOperationsCache?;
|
|
913
919
|
/**
|
|
914
920
|
* TinyCloudSession captured by {@link restoreSession} when there's no
|
|
915
921
|
* auth-layer signer available (session-only mode used by OpenKey-backed
|
|
@@ -1052,6 +1058,42 @@ declare class TinyCloudNode {
|
|
|
1052
1058
|
private isFreshBootstrapAccount;
|
|
1053
1059
|
private runAccountBootstrap;
|
|
1054
1060
|
private registerBootstrapRuntimeGrant;
|
|
1061
|
+
/**
|
|
1062
|
+
* Map the base session's OWN recap into runtime permission operations.
|
|
1063
|
+
*
|
|
1064
|
+
* Uses the RAW `parseRecapFromSiwe` binding — NOT `parseRecapCapabilities`,
|
|
1065
|
+
* whose `normalizeSpace` collapses `tinycloud:pkh:...:<owner>:<space>` to a
|
|
1066
|
+
* bare short name and would conflate two owners' identically-named spaces.
|
|
1067
|
+
* We must keep the full owner-scoped URI so a synthetic primary grant can
|
|
1068
|
+
* never cover an operation on a different owner's space.
|
|
1069
|
+
*
|
|
1070
|
+
* Mirrors {@link operationsFromDelegation}'s op shape: encryption network
|
|
1071
|
+
* entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
|
|
1072
|
+
* other entry becomes a `spaceId` op carrying the raw recap `space` URI.
|
|
1073
|
+
* One op per action.
|
|
1074
|
+
*
|
|
1075
|
+
* Returns `[]` for session-only / restored-without-siwe modes and for any
|
|
1076
|
+
* unparseable SIWE — the primary grant is simply not registered in that case.
|
|
1077
|
+
*/
|
|
1078
|
+
private recapOperationsFromSession;
|
|
1079
|
+
/**
|
|
1080
|
+
* Register the base (primary) session's own recap as a synthetic runtime
|
|
1081
|
+
* grant tagged `provenance: "primary"` so it always out-ranks other covering
|
|
1082
|
+
* grants in {@link findGrantForOperations}. This closes the selection-design
|
|
1083
|
+
* hazard where a broad — possibly broken — bootstrap/delegated grant could
|
|
1084
|
+
* hijack an operation the primary session itself already authorized (TC-111).
|
|
1085
|
+
*
|
|
1086
|
+
* Two safety exclusions:
|
|
1087
|
+
* - Ops whose space is in `lastActivationSkippedSpaceIds` are dropped: the
|
|
1088
|
+
* node refused to activate those spaces this sign-in even though the recap
|
|
1089
|
+
* claims them. Including them would let the synthetic primary out-rank a
|
|
1090
|
+
* working grant and 401 (the "skipped-activation inverted hijack").
|
|
1091
|
+
* - Encryption `resource` ops are kept as-is (space-independent).
|
|
1092
|
+
*
|
|
1093
|
+
* No-ops when nothing remains after exclusion. Callers (`signIn`,
|
|
1094
|
+
* `restoreSession`) clear `runtimePermissionGrants` first, so no dupes.
|
|
1095
|
+
*/
|
|
1096
|
+
private registerPrimarySessionGrant;
|
|
1055
1097
|
private writeManifestRegistryRecords;
|
|
1056
1098
|
private scheduleAccountRegistrySync;
|
|
1057
1099
|
private withAccountRegistryRetry;
|
package/dist/core.cjs
CHANGED
|
@@ -2147,6 +2147,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2147
2147
|
await this.tc.signIn(options);
|
|
2148
2148
|
this.syncResolvedHostFromAuth();
|
|
2149
2149
|
this.initializeServices();
|
|
2150
|
+
const primarySession = this.currentTinyCloudSession();
|
|
2151
|
+
if (primarySession) {
|
|
2152
|
+
this.registerPrimarySessionGrant(primarySession);
|
|
2153
|
+
}
|
|
2150
2154
|
const bootstrapped = await this.bootstrapAccountIfNeeded();
|
|
2151
2155
|
await this.ensureRequestedEncryptionNetworks();
|
|
2152
2156
|
if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
|
|
@@ -2390,7 +2394,107 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2390
2394
|
host: this.config.host
|
|
2391
2395
|
},
|
|
2392
2396
|
operations,
|
|
2393
|
-
expiresAt
|
|
2397
|
+
expiresAt,
|
|
2398
|
+
provenance: "bootstrap"
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Map the base session's OWN recap into runtime permission operations.
|
|
2403
|
+
*
|
|
2404
|
+
* Uses the RAW `parseRecapFromSiwe` binding — NOT `parseRecapCapabilities`,
|
|
2405
|
+
* whose `normalizeSpace` collapses `tinycloud:pkh:...:<owner>:<space>` to a
|
|
2406
|
+
* bare short name and would conflate two owners' identically-named spaces.
|
|
2407
|
+
* We must keep the full owner-scoped URI so a synthetic primary grant can
|
|
2408
|
+
* never cover an operation on a different owner's space.
|
|
2409
|
+
*
|
|
2410
|
+
* Mirrors {@link operationsFromDelegation}'s op shape: encryption network
|
|
2411
|
+
* entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
|
|
2412
|
+
* other entry becomes a `spaceId` op carrying the raw recap `space` URI.
|
|
2413
|
+
* One op per action.
|
|
2414
|
+
*
|
|
2415
|
+
* Returns `[]` for session-only / restored-without-siwe modes and for any
|
|
2416
|
+
* unparseable SIWE — the primary grant is simply not registered in that case.
|
|
2417
|
+
*/
|
|
2418
|
+
recapOperationsFromSession(session) {
|
|
2419
|
+
const siwe = session.siwe;
|
|
2420
|
+
if (!siwe) {
|
|
2421
|
+
return [];
|
|
2422
|
+
}
|
|
2423
|
+
if (this._recapOperationsCache?.siwe === siwe) {
|
|
2424
|
+
return this._recapOperationsCache.operations;
|
|
2425
|
+
}
|
|
2426
|
+
let operations = [];
|
|
2427
|
+
try {
|
|
2428
|
+
const entries = this.wasmBindings.parseRecapFromSiwe(siwe);
|
|
2429
|
+
if (Array.isArray(entries)) {
|
|
2430
|
+
operations = entries.flatMap((entry) => {
|
|
2431
|
+
const service = this.invocationServiceName(entry.service);
|
|
2432
|
+
return entry.actions.map((action) => ({
|
|
2433
|
+
...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
|
|
2434
|
+
service,
|
|
2435
|
+
path: entry.path,
|
|
2436
|
+
action
|
|
2437
|
+
}));
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
} catch {
|
|
2441
|
+
operations = [];
|
|
2442
|
+
}
|
|
2443
|
+
this._recapOperationsCache = { siwe, operations };
|
|
2444
|
+
return operations;
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Register the base (primary) session's own recap as a synthetic runtime
|
|
2448
|
+
* grant tagged `provenance: "primary"` so it always out-ranks other covering
|
|
2449
|
+
* grants in {@link findGrantForOperations}. This closes the selection-design
|
|
2450
|
+
* hazard where a broad — possibly broken — bootstrap/delegated grant could
|
|
2451
|
+
* hijack an operation the primary session itself already authorized (TC-111).
|
|
2452
|
+
*
|
|
2453
|
+
* Two safety exclusions:
|
|
2454
|
+
* - Ops whose space is in `lastActivationSkippedSpaceIds` are dropped: the
|
|
2455
|
+
* node refused to activate those spaces this sign-in even though the recap
|
|
2456
|
+
* claims them. Including them would let the synthetic primary out-rank a
|
|
2457
|
+
* working grant and 401 (the "skipped-activation inverted hijack").
|
|
2458
|
+
* - Encryption `resource` ops are kept as-is (space-independent).
|
|
2459
|
+
*
|
|
2460
|
+
* No-ops when nothing remains after exclusion. Callers (`signIn`,
|
|
2461
|
+
* `restoreSession`) clear `runtimePermissionGrants` first, so no dupes.
|
|
2462
|
+
*/
|
|
2463
|
+
registerPrimarySessionGrant(session) {
|
|
2464
|
+
const skipped = this.auth ? this.auth.lastActivationSkippedSpaceIds ?? [] : [];
|
|
2465
|
+
const operations = this.recapOperationsFromSession(session).filter(
|
|
2466
|
+
(operation) => operation.spaceId === void 0 || !skipped.some((spaceId) => this.spaceIdsEqual(spaceId, operation.spaceId))
|
|
2467
|
+
);
|
|
2468
|
+
if (operations.length === 0) {
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
const expiresAt = extractSiweExpiration(session.siwe) ?? this.getSessionExpiry();
|
|
2472
|
+
const actions = [...new Set(operations.map((operation) => operation.action))];
|
|
2473
|
+
this.runtimePermissionGrants.push({
|
|
2474
|
+
session: {
|
|
2475
|
+
delegationHeader: session.delegationHeader,
|
|
2476
|
+
delegationCid: session.delegationCid,
|
|
2477
|
+
spaceId: session.spaceId,
|
|
2478
|
+
verificationMethod: session.verificationMethod,
|
|
2479
|
+
jwk: session.jwk
|
|
2480
|
+
},
|
|
2481
|
+
delegation: {
|
|
2482
|
+
cid: session.delegationCid,
|
|
2483
|
+
delegationHeader: session.delegationHeader,
|
|
2484
|
+
delegateDID: session.verificationMethod,
|
|
2485
|
+
delegatorDID: this.did,
|
|
2486
|
+
spaceId: session.spaceId,
|
|
2487
|
+
path: "",
|
|
2488
|
+
actions,
|
|
2489
|
+
expiry: expiresAt,
|
|
2490
|
+
allowSubDelegation: true,
|
|
2491
|
+
ownerAddress: session.address,
|
|
2492
|
+
chainId: session.chainId,
|
|
2493
|
+
host: this.config.host
|
|
2494
|
+
},
|
|
2495
|
+
operations,
|
|
2496
|
+
expiresAt,
|
|
2497
|
+
provenance: "primary"
|
|
2394
2498
|
});
|
|
2395
2499
|
}
|
|
2396
2500
|
async writeManifestRegistryRecords() {
|
|
@@ -2726,6 +2830,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2726
2830
|
} else {
|
|
2727
2831
|
this._restoredTcSession = tcSession;
|
|
2728
2832
|
}
|
|
2833
|
+
this.registerPrimarySessionGrant(tcSession);
|
|
2729
2834
|
}
|
|
2730
2835
|
}
|
|
2731
2836
|
/**
|
|
@@ -3764,7 +3869,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3764
3869
|
getRuntimePermissionDelegations(permissions) {
|
|
3765
3870
|
this.pruneExpiredRuntimePermissionGrants();
|
|
3766
3871
|
if (permissions === void 0) {
|
|
3767
|
-
return this.runtimePermissionGrants.map((grant) => grant.delegation);
|
|
3872
|
+
return this.runtimePermissionGrants.filter((grant) => grant.provenance !== "primary").map((grant) => grant.delegation);
|
|
3768
3873
|
}
|
|
3769
3874
|
const session = this.currentTinyCloudSession();
|
|
3770
3875
|
if (!session || !Array.isArray(permissions) || permissions.length === 0) {
|
|
@@ -3916,7 +4021,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3916
4021
|
},
|
|
3917
4022
|
delegation,
|
|
3918
4023
|
operations: this.permissionOperations(delegatedEntries, spaceId),
|
|
3919
|
-
expiresAt
|
|
4024
|
+
expiresAt,
|
|
4025
|
+
provenance: "runtime"
|
|
3920
4026
|
});
|
|
3921
4027
|
delegations.push(delegation);
|
|
3922
4028
|
}
|
|
@@ -4577,7 +4683,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4577
4683
|
return grants;
|
|
4578
4684
|
}
|
|
4579
4685
|
for (const operation of operations) {
|
|
4580
|
-
const grant = this.findGrantForOperation(operation);
|
|
4686
|
+
const grant = this.findGrantForOperation(operation, { excludePrimary: true });
|
|
4581
4687
|
if (!grant) {
|
|
4582
4688
|
return [];
|
|
4583
4689
|
}
|
|
@@ -4617,7 +4723,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4617
4723
|
},
|
|
4618
4724
|
delegation,
|
|
4619
4725
|
operations,
|
|
4620
|
-
expiresAt: delegation.expiry
|
|
4726
|
+
expiresAt: delegation.expiry,
|
|
4727
|
+
provenance: "delegated"
|
|
4621
4728
|
};
|
|
4622
4729
|
}
|
|
4623
4730
|
installRuntimeGrantFromServiceSession(delegation, session, expiresAt) {
|
|
@@ -4632,7 +4739,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4632
4739
|
session,
|
|
4633
4740
|
delegation,
|
|
4634
4741
|
operations,
|
|
4635
|
-
expiresAt
|
|
4742
|
+
expiresAt,
|
|
4743
|
+
provenance: "delegated"
|
|
4636
4744
|
});
|
|
4637
4745
|
}
|
|
4638
4746
|
delegatedResourcesForEntries(entries, spaceId) {
|
|
@@ -4732,21 +4840,28 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4732
4840
|
});
|
|
4733
4841
|
return grant?.session ?? fallback;
|
|
4734
4842
|
}
|
|
4735
|
-
findGrantForOperations(operations) {
|
|
4843
|
+
findGrantForOperations(operations, options) {
|
|
4736
4844
|
if (operations.length === 0) {
|
|
4737
4845
|
return void 0;
|
|
4738
4846
|
}
|
|
4739
4847
|
this.pruneExpiredRuntimePermissionGrants();
|
|
4740
|
-
|
|
4848
|
+
const covering = this.runtimePermissionGrants.filter((grant) => {
|
|
4849
|
+
if (options?.excludePrimary && grant.provenance === "primary") {
|
|
4850
|
+
return false;
|
|
4851
|
+
}
|
|
4741
4852
|
return operations.every(
|
|
4742
4853
|
(operation) => grant.operations.some(
|
|
4743
4854
|
(granted) => this.operationCovers(granted, operation)
|
|
4744
4855
|
)
|
|
4745
4856
|
);
|
|
4746
4857
|
});
|
|
4858
|
+
if (covering.length === 0) {
|
|
4859
|
+
return void 0;
|
|
4860
|
+
}
|
|
4861
|
+
return covering.find((grant) => grant.provenance === "primary") ?? covering[0];
|
|
4747
4862
|
}
|
|
4748
|
-
findGrantForOperation(operation) {
|
|
4749
|
-
return this.findGrantForOperations([operation]);
|
|
4863
|
+
findGrantForOperation(operation, options) {
|
|
4864
|
+
return this.findGrantForOperations([operation], options);
|
|
4750
4865
|
}
|
|
4751
4866
|
pruneExpiredRuntimePermissionGrants() {
|
|
4752
4867
|
const now = Date.now();
|