@tinycloud/node-sdk 2.2.0-beta.7 → 2.2.0-beta.9
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-C3s0bgRe.d.cts → core-DcJ27GsA.d.cts} +58 -4
- package/dist/{core-C3s0bgRe.d.ts → core-DcJ27GsA.d.ts} +58 -4
- package/dist/core.cjs +499 -70
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +502 -73
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +499 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +502 -73
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/core.js
CHANGED
|
@@ -972,7 +972,7 @@ import {
|
|
|
972
972
|
ACCOUNT_REGISTRY_SPACE,
|
|
973
973
|
PermissionNotInManifestError,
|
|
974
974
|
SessionExpiredError,
|
|
975
|
-
|
|
975
|
+
expandPermissionEntries as expandPermissionEntriesCore,
|
|
976
976
|
isCapabilitySubset,
|
|
977
977
|
parseRecapCapabilities,
|
|
978
978
|
SERVICE_LONG_TO_SHORT
|
|
@@ -1240,49 +1240,29 @@ function secretsError(code, message, cause) {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
};
|
|
1242
1242
|
}
|
|
1243
|
-
function
|
|
1244
|
-
return
|
|
1243
|
+
function displayActionUrn(action) {
|
|
1244
|
+
return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
|
|
1245
1245
|
}
|
|
1246
|
-
function
|
|
1247
|
-
return
|
|
1246
|
+
function kvActionUrn(action) {
|
|
1247
|
+
return `tinycloud.kv/${action}`;
|
|
1248
1248
|
}
|
|
1249
1249
|
function secretPermissionEntries(name, action) {
|
|
1250
1250
|
return [
|
|
1251
1251
|
{
|
|
1252
|
-
service: "tinycloud.
|
|
1253
|
-
space: SECRETS_SPACE,
|
|
1254
|
-
path: secretResourcePath("keys", name),
|
|
1255
|
-
actions: [action],
|
|
1256
|
-
skipPrefix: true
|
|
1257
|
-
},
|
|
1258
|
-
{
|
|
1259
|
-
service: "tinycloud.kv",
|
|
1252
|
+
service: "tinycloud.vault",
|
|
1260
1253
|
space: SECRETS_SPACE,
|
|
1261
|
-
path:
|
|
1262
|
-
actions: [action],
|
|
1254
|
+
path: `${SECRET_PREFIX}${name}`,
|
|
1255
|
+
actions: [action === "put" ? "write" : "delete"],
|
|
1263
1256
|
skipPrefix: true
|
|
1264
1257
|
}
|
|
1265
1258
|
];
|
|
1266
1259
|
}
|
|
1260
|
+
function secretResourcePath(base, name) {
|
|
1261
|
+
return `${base}/${SECRET_PREFIX}${name}`;
|
|
1262
|
+
}
|
|
1267
1263
|
function isSecretsSpace(space) {
|
|
1268
1264
|
return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
|
|
1269
1265
|
}
|
|
1270
|
-
function composeEscalatedManifest(manifest, additional) {
|
|
1271
|
-
if (Array.isArray(manifest)) {
|
|
1272
|
-
const [primary, ...rest] = manifest;
|
|
1273
|
-
return [
|
|
1274
|
-
{
|
|
1275
|
-
...primary,
|
|
1276
|
-
permissions: [...primary.permissions ?? [], ...additional]
|
|
1277
|
-
},
|
|
1278
|
-
...rest
|
|
1279
|
-
];
|
|
1280
|
-
}
|
|
1281
|
-
return {
|
|
1282
|
-
...manifest,
|
|
1283
|
-
permissions: [...manifest.permissions ?? [], ...additional]
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
1266
|
var NodeSecretsService = class {
|
|
1287
1267
|
constructor(config) {
|
|
1288
1268
|
this.config = config;
|
|
@@ -1295,10 +1275,11 @@ var NodeSecretsService = class {
|
|
|
1295
1275
|
return this.service.isUnlocked;
|
|
1296
1276
|
}
|
|
1297
1277
|
async unlock(signer) {
|
|
1298
|
-
|
|
1299
|
-
|
|
1278
|
+
const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
|
|
1279
|
+
if (effectiveSigner !== void 0) {
|
|
1280
|
+
this.unlockSigner = effectiveSigner;
|
|
1300
1281
|
}
|
|
1301
|
-
const result = await this.service.unlock(
|
|
1282
|
+
const result = await this.service.unlock(effectiveSigner);
|
|
1302
1283
|
if (result.ok) {
|
|
1303
1284
|
this.shouldRestoreUnlock = true;
|
|
1304
1285
|
}
|
|
@@ -1340,29 +1321,16 @@ var NodeSecretsService = class {
|
|
|
1340
1321
|
if (!this.config.canEscalate()) {
|
|
1341
1322
|
return secretsError(
|
|
1342
1323
|
ErrorCodes.PERMISSION_DENIED,
|
|
1343
|
-
`Cannot autosign ${
|
|
1344
|
-
);
|
|
1345
|
-
}
|
|
1346
|
-
const manifest = this.config.getManifest();
|
|
1347
|
-
if (manifest === void 0) {
|
|
1348
|
-
return secretsError(
|
|
1349
|
-
ErrorCodes.PERMISSION_DENIED,
|
|
1350
|
-
`Cannot autosign ${actionUrn(action)} for ${name}; set a manifest before mutating secrets.`
|
|
1324
|
+
`Cannot autosign ${displayActionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
|
|
1351
1325
|
);
|
|
1352
1326
|
}
|
|
1353
1327
|
try {
|
|
1354
|
-
this.config.
|
|
1355
|
-
composeEscalatedManifest(
|
|
1356
|
-
manifest,
|
|
1357
|
-
secretPermissionEntries(name, action)
|
|
1358
|
-
)
|
|
1359
|
-
);
|
|
1360
|
-
await this.config.signIn();
|
|
1328
|
+
await this.config.grantPermissions(secretPermissionEntries(name, action));
|
|
1361
1329
|
return this.restoreUnlockAfterEscalation();
|
|
1362
1330
|
} catch (error) {
|
|
1363
1331
|
return secretsError(
|
|
1364
1332
|
ErrorCodes.PERMISSION_DENIED,
|
|
1365
|
-
error instanceof Error ? error.message : `Autosign escalation for ${
|
|
1333
|
+
error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
|
|
1366
1334
|
error instanceof Error ? error : void 0
|
|
1367
1335
|
);
|
|
1368
1336
|
}
|
|
@@ -1379,7 +1347,7 @@ var NodeSecretsService = class {
|
|
|
1379
1347
|
return false;
|
|
1380
1348
|
}
|
|
1381
1349
|
const manifests = Array.isArray(manifest) ? manifest : [manifest];
|
|
1382
|
-
const requiredAction =
|
|
1350
|
+
const requiredAction = kvActionUrn(action);
|
|
1383
1351
|
return manifests.some((entry) => {
|
|
1384
1352
|
const resolved = resolveManifest(entry);
|
|
1385
1353
|
return ["keys", "vault"].every(
|
|
@@ -1422,6 +1390,30 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1422
1390
|
this.auth = null;
|
|
1423
1391
|
this.tc = null;
|
|
1424
1392
|
this._chainId = 1;
|
|
1393
|
+
this.runtimePermissionGrants = [];
|
|
1394
|
+
this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
|
|
1395
|
+
return this.wasmBindings.invoke(
|
|
1396
|
+
this.selectInvocationSession(session, service, path, action),
|
|
1397
|
+
service,
|
|
1398
|
+
path,
|
|
1399
|
+
action,
|
|
1400
|
+
facts
|
|
1401
|
+
);
|
|
1402
|
+
};
|
|
1403
|
+
this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
|
|
1404
|
+
if (!this.wasmBindings.invokeAny) {
|
|
1405
|
+
throw new Error("WASM binding does not support invokeAny");
|
|
1406
|
+
}
|
|
1407
|
+
const grant = this.findGrantForOperations(
|
|
1408
|
+
entries.map((entry) => ({
|
|
1409
|
+
spaceId: entry.spaceId,
|
|
1410
|
+
service: this.invocationServiceName(entry.service),
|
|
1411
|
+
path: entry.path,
|
|
1412
|
+
action: entry.action
|
|
1413
|
+
}))
|
|
1414
|
+
);
|
|
1415
|
+
return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
|
|
1416
|
+
};
|
|
1425
1417
|
this.explicitHost = config.host;
|
|
1426
1418
|
this.config = {
|
|
1427
1419
|
...config,
|
|
@@ -1457,7 +1449,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1457
1449
|
this._sharingService = new SharingService({
|
|
1458
1450
|
hosts: [this.config.host],
|
|
1459
1451
|
// session: undefined - not needed for receive()
|
|
1460
|
-
invoke: this.
|
|
1452
|
+
invoke: this.invokeWithRuntimePermissions,
|
|
1461
1453
|
fetch: globalThis.fetch.bind(globalThis),
|
|
1462
1454
|
keyProvider: this._keyProvider,
|
|
1463
1455
|
registry: this._capabilityRegistry,
|
|
@@ -1525,7 +1517,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1525
1517
|
includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
|
|
1526
1518
|
});
|
|
1527
1519
|
this.tc = new TinyCloud(this.auth, {
|
|
1528
|
-
invokeAny: this.
|
|
1520
|
+
invokeAny: this.invokeAnyWithRuntimePermissions
|
|
1529
1521
|
});
|
|
1530
1522
|
}
|
|
1531
1523
|
syncResolvedHostFromAuth() {
|
|
@@ -1650,6 +1642,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1650
1642
|
this._secrets = void 0;
|
|
1651
1643
|
this._spaceService = void 0;
|
|
1652
1644
|
this._serviceContext = void 0;
|
|
1645
|
+
this.runtimePermissionGrants = [];
|
|
1653
1646
|
await this.tc.signIn(options);
|
|
1654
1647
|
this.syncResolvedHostFromAuth();
|
|
1655
1648
|
this.initializeServices();
|
|
@@ -1739,6 +1732,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1739
1732
|
this._secrets = void 0;
|
|
1740
1733
|
this._spaceService = void 0;
|
|
1741
1734
|
this._serviceContext = void 0;
|
|
1735
|
+
this.runtimePermissionGrants = [];
|
|
1742
1736
|
if (sessionData.address) {
|
|
1743
1737
|
this._address = sessionData.address;
|
|
1744
1738
|
}
|
|
@@ -1746,8 +1740,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1746
1740
|
this._chainId = sessionData.chainId;
|
|
1747
1741
|
}
|
|
1748
1742
|
this._serviceContext = new ServiceContext2({
|
|
1749
|
-
invoke: this.
|
|
1750
|
-
invokeAny: this.
|
|
1743
|
+
invoke: this.invokeWithRuntimePermissions,
|
|
1744
|
+
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
1751
1745
|
fetch: globalThis.fetch.bind(globalThis),
|
|
1752
1746
|
hosts: [this.config.host]
|
|
1753
1747
|
});
|
|
@@ -1833,7 +1827,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1833
1827
|
includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
|
|
1834
1828
|
});
|
|
1835
1829
|
this.tc = new TinyCloud(this.auth, {
|
|
1836
|
-
invokeAny: this.
|
|
1830
|
+
invokeAny: this.invokeAnyWithRuntimePermissions
|
|
1837
1831
|
});
|
|
1838
1832
|
this.config.prefix = prefix;
|
|
1839
1833
|
}
|
|
@@ -1877,7 +1871,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1877
1871
|
includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
|
|
1878
1872
|
});
|
|
1879
1873
|
this.tc = new TinyCloud(this.auth, {
|
|
1880
|
-
invokeAny: this.
|
|
1874
|
+
invokeAny: this.invokeAnyWithRuntimePermissions
|
|
1881
1875
|
});
|
|
1882
1876
|
this.config.prefix = prefix;
|
|
1883
1877
|
}
|
|
@@ -1890,10 +1884,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
1890
1884
|
if (!session) {
|
|
1891
1885
|
return;
|
|
1892
1886
|
}
|
|
1893
|
-
this.tc.initializeServices(this.
|
|
1887
|
+
this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
|
|
1894
1888
|
this._serviceContext = new ServiceContext2({
|
|
1895
|
-
invoke: this.
|
|
1896
|
-
invokeAny: this.
|
|
1889
|
+
invoke: this.invokeWithRuntimePermissions,
|
|
1890
|
+
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
1897
1891
|
fetch: globalThis.fetch.bind(globalThis),
|
|
1898
1892
|
hosts: [this.config.host]
|
|
1899
1893
|
});
|
|
@@ -2063,7 +2057,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2063
2057
|
this._delegationManager = new DelegationManager({
|
|
2064
2058
|
hosts: [this.config.host],
|
|
2065
2059
|
session: serviceSession,
|
|
2066
|
-
invoke: this.
|
|
2060
|
+
invoke: this.invokeWithRuntimePermissions,
|
|
2067
2061
|
fetch: globalThis.fetch.bind(globalThis)
|
|
2068
2062
|
});
|
|
2069
2063
|
this._spaceService = new SpaceService({
|
|
@@ -2330,9 +2324,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2330
2324
|
this._secrets = new NodeSecretsService({
|
|
2331
2325
|
getService: () => this.getBaseSecrets(),
|
|
2332
2326
|
getManifest: () => this.manifest,
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2327
|
+
grantPermissions: (additional) => this.grantRuntimePermissions(additional),
|
|
2328
|
+
canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
|
|
2329
|
+
getUnlockSigner: () => this.signer ?? void 0
|
|
2336
2330
|
});
|
|
2337
2331
|
}
|
|
2338
2332
|
return this._secrets;
|
|
@@ -2416,6 +2410,171 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2416
2410
|
}
|
|
2417
2411
|
};
|
|
2418
2412
|
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Check whether the current session or an approved runtime delegation covers
|
|
2415
|
+
* every requested permission.
|
|
2416
|
+
*/
|
|
2417
|
+
hasRuntimePermissions(permissions) {
|
|
2418
|
+
const session = this.auth?.tinyCloudSession;
|
|
2419
|
+
if (!session || !Array.isArray(permissions) || permissions.length === 0) {
|
|
2420
|
+
return false;
|
|
2421
|
+
}
|
|
2422
|
+
const expanded = this.expandPermissionEntries(permissions);
|
|
2423
|
+
if (this.sessionCoversPermissionEntries(session, expanded)) {
|
|
2424
|
+
return true;
|
|
2425
|
+
}
|
|
2426
|
+
return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Return installed runtime permission delegations. When `permissions` is
|
|
2430
|
+
* provided, only delegations currently covering those permissions are
|
|
2431
|
+
* returned. Base-session manifest permissions are not represented here.
|
|
2432
|
+
*/
|
|
2433
|
+
getRuntimePermissionDelegations(permissions) {
|
|
2434
|
+
this.pruneExpiredRuntimePermissionGrants();
|
|
2435
|
+
if (permissions === void 0) {
|
|
2436
|
+
return this.runtimePermissionGrants.map((grant) => grant.delegation);
|
|
2437
|
+
}
|
|
2438
|
+
const session = this.auth?.tinyCloudSession;
|
|
2439
|
+
if (!session || !Array.isArray(permissions) || permissions.length === 0) {
|
|
2440
|
+
return [];
|
|
2441
|
+
}
|
|
2442
|
+
const expanded = this.expandPermissionEntries(permissions);
|
|
2443
|
+
return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
|
|
2444
|
+
(grant) => grant.delegation
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Install a portable runtime permission delegation into this SDK instance so
|
|
2449
|
+
* matching service calls and downstream `delegateTo()` calls can use it.
|
|
2450
|
+
*/
|
|
2451
|
+
async useRuntimeDelegation(delegation) {
|
|
2452
|
+
const session = this.auth?.tinyCloudSession;
|
|
2453
|
+
if (!session) {
|
|
2454
|
+
throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
|
|
2455
|
+
}
|
|
2456
|
+
if (delegation.expiry.getTime() <= Date.now()) {
|
|
2457
|
+
throw new SessionExpiredError(delegation.expiry);
|
|
2458
|
+
}
|
|
2459
|
+
const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
|
|
2460
|
+
if (!expectedDids.has(delegation.delegateDID)) {
|
|
2461
|
+
throw new Error(
|
|
2462
|
+
`Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
const targetHost = delegation.host ?? this.config.host;
|
|
2466
|
+
const activateResult = await activateSessionWithHost2(
|
|
2467
|
+
targetHost,
|
|
2468
|
+
delegation.delegationHeader
|
|
2469
|
+
);
|
|
2470
|
+
if (!activateResult.success) {
|
|
2471
|
+
throw new Error(
|
|
2472
|
+
`Failed to activate runtime permission delegation: ${activateResult.error}`
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
|
|
2476
|
+
(grant) => grant.delegation.cid !== delegation.cid
|
|
2477
|
+
);
|
|
2478
|
+
this.runtimePermissionGrants.push(
|
|
2479
|
+
this.runtimeGrantFromDelegation(delegation, session)
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Store additional permissions as narrow delegations to the current session
|
|
2484
|
+
* key. Future service invocations automatically use a stored delegation when
|
|
2485
|
+
* its `(space, service, path, action)` covers the request.
|
|
2486
|
+
*/
|
|
2487
|
+
async grantRuntimePermissions(permissions, options) {
|
|
2488
|
+
if (!Array.isArray(permissions) || permissions.length === 0) {
|
|
2489
|
+
throw new Error("grantRuntimePermissions requires a non-empty permissions array");
|
|
2490
|
+
}
|
|
2491
|
+
const session = this.auth?.tinyCloudSession;
|
|
2492
|
+
if (!session) {
|
|
2493
|
+
throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
|
|
2494
|
+
}
|
|
2495
|
+
const sessionExpiry = extractSiweExpiration(session.siwe);
|
|
2496
|
+
if (sessionExpiry !== void 0) {
|
|
2497
|
+
const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
|
|
2498
|
+
if (sessionExpiry.getTime() <= Date.now() + marginMs) {
|
|
2499
|
+
throw new SessionExpiredError(sessionExpiry);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
const expanded = this.expandPermissionEntries(permissions);
|
|
2503
|
+
if (this.sessionCoversPermissionEntries(session, expanded)) {
|
|
2504
|
+
return [];
|
|
2505
|
+
}
|
|
2506
|
+
const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
|
|
2507
|
+
if (existingGrants.length > 0) {
|
|
2508
|
+
return existingGrants.map((grant) => grant.delegation);
|
|
2509
|
+
}
|
|
2510
|
+
if (!this.signer) {
|
|
2511
|
+
throw new Error(
|
|
2512
|
+
"grantRuntimePermissions requires wallet mode with a signer or privateKey."
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
const bySpace = /* @__PURE__ */ new Map();
|
|
2516
|
+
for (const entry of expanded) {
|
|
2517
|
+
const spaceId = this.resolvePermissionSpace(entry.space, session);
|
|
2518
|
+
const current = bySpace.get(spaceId) ?? [];
|
|
2519
|
+
current.push(entry);
|
|
2520
|
+
bySpace.set(spaceId, current);
|
|
2521
|
+
}
|
|
2522
|
+
const now = /* @__PURE__ */ new Date();
|
|
2523
|
+
const requestedExpiryMs = resolveExpiryMs(options?.expiry);
|
|
2524
|
+
let expiresAt = new Date(now.getTime() + requestedExpiryMs);
|
|
2525
|
+
if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
|
|
2526
|
+
expiresAt = sessionExpiry;
|
|
2527
|
+
}
|
|
2528
|
+
const delegations = [];
|
|
2529
|
+
for (const [spaceId, entries] of bySpace) {
|
|
2530
|
+
const abilities = this.permissionsToAbilities(entries);
|
|
2531
|
+
const prepared = this.wasmBindings.prepareSession({
|
|
2532
|
+
abilities,
|
|
2533
|
+
address: this.wasmBindings.ensureEip55(session.address),
|
|
2534
|
+
chainId: session.chainId,
|
|
2535
|
+
domain: this.siweDomain,
|
|
2536
|
+
issuedAt: now.toISOString(),
|
|
2537
|
+
expirationTime: expiresAt.toISOString(),
|
|
2538
|
+
spaceId,
|
|
2539
|
+
jwk: session.jwk
|
|
2540
|
+
});
|
|
2541
|
+
const signature = await this.signer.signMessage(prepared.siwe);
|
|
2542
|
+
const delegatedSession = this.wasmBindings.completeSessionSetup({
|
|
2543
|
+
...prepared,
|
|
2544
|
+
signature
|
|
2545
|
+
});
|
|
2546
|
+
const activateResult = await activateSessionWithHost2(
|
|
2547
|
+
this.config.host,
|
|
2548
|
+
delegatedSession.delegationHeader
|
|
2549
|
+
);
|
|
2550
|
+
if (!activateResult.success) {
|
|
2551
|
+
throw new Error(
|
|
2552
|
+
`Failed to activate runtime permission delegation: ${activateResult.error}`
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
const delegation = this.runtimeDelegationFromSession(
|
|
2556
|
+
delegatedSession,
|
|
2557
|
+
entries,
|
|
2558
|
+
spaceId,
|
|
2559
|
+
session,
|
|
2560
|
+
expiresAt
|
|
2561
|
+
);
|
|
2562
|
+
this.runtimePermissionGrants.push({
|
|
2563
|
+
session: {
|
|
2564
|
+
delegationHeader: delegatedSession.delegationHeader,
|
|
2565
|
+
delegationCid: delegatedSession.delegationCid,
|
|
2566
|
+
spaceId,
|
|
2567
|
+
verificationMethod: session.verificationMethod,
|
|
2568
|
+
jwk: session.jwk
|
|
2569
|
+
},
|
|
2570
|
+
delegation,
|
|
2571
|
+
operations: this.permissionOperations(entries, spaceId),
|
|
2572
|
+
expiresAt
|
|
2573
|
+
});
|
|
2574
|
+
delegations.push(delegation);
|
|
2575
|
+
}
|
|
2576
|
+
return delegations;
|
|
2577
|
+
}
|
|
2419
2578
|
/**
|
|
2420
2579
|
* Get the DelegationManager for delegation CRUD operations.
|
|
2421
2580
|
*
|
|
@@ -2604,7 +2763,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2604
2763
|
if (this._serviceContext) {
|
|
2605
2764
|
const publicKV = new KVService2({ prefix: "" });
|
|
2606
2765
|
const publicContext = new ServiceContext2({
|
|
2607
|
-
invoke: this.
|
|
2766
|
+
invoke: this.invokeWithRuntimePermissions,
|
|
2608
2767
|
fetch: this._serviceContext.fetch,
|
|
2609
2768
|
hosts: this._serviceContext.hosts
|
|
2610
2769
|
});
|
|
@@ -2692,8 +2851,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2692
2851
|
* Issue a delegation using the capability-chain flow.
|
|
2693
2852
|
*
|
|
2694
2853
|
* When every requested permission is a subset of the current
|
|
2695
|
-
* session's recap,
|
|
2696
|
-
*
|
|
2854
|
+
* session's recap, or of one installed runtime permission delegation,
|
|
2855
|
+
* the delegation is signed by the session key via WASM — no wallet
|
|
2856
|
+
* prompt. When at least one is NOT derivable, a
|
|
2697
2857
|
* {@link PermissionNotInManifestError} is raised (carrying the
|
|
2698
2858
|
* missing entries) so the caller can trigger an escalation flow
|
|
2699
2859
|
* (e.g. `TinyCloudWeb.requestPermissions`). Passing
|
|
@@ -2743,10 +2903,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2743
2903
|
"delegateTo requires a non-empty permissions array"
|
|
2744
2904
|
);
|
|
2745
2905
|
}
|
|
2746
|
-
const expandedEntries =
|
|
2747
|
-
...entry,
|
|
2748
|
-
actions: expandActionShortNames(entry.service, entry.actions)
|
|
2749
|
-
}));
|
|
2906
|
+
const expandedEntries = this.expandPermissionEntries(permissions);
|
|
2750
2907
|
const now = /* @__PURE__ */ new Date();
|
|
2751
2908
|
const expiryMs = resolveExpiryMs(options?.expiry);
|
|
2752
2909
|
const expirationTime = new Date(now.getTime() + expiryMs);
|
|
@@ -2773,6 +2930,23 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2773
2930
|
);
|
|
2774
2931
|
const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
|
|
2775
2932
|
if (!subset) {
|
|
2933
|
+
const runtimeGrant = this.findGrantForOperations(
|
|
2934
|
+
this.permissionEntriesToOperations(expandedEntries, session)
|
|
2935
|
+
);
|
|
2936
|
+
if (runtimeGrant) {
|
|
2937
|
+
const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
|
|
2938
|
+
if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
|
|
2939
|
+
throw new SessionExpiredError(runtimeGrant.expiresAt);
|
|
2940
|
+
}
|
|
2941
|
+
const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
|
|
2942
|
+
const delegation2 = await this.createDelegationViaRuntimeGrant(
|
|
2943
|
+
did,
|
|
2944
|
+
expandedEntries,
|
|
2945
|
+
runtimeExpiration,
|
|
2946
|
+
runtimeGrant
|
|
2947
|
+
);
|
|
2948
|
+
return { delegation: delegation2, prompted: false };
|
|
2949
|
+
}
|
|
2776
2950
|
throw new PermissionNotInManifestError(missing, granted);
|
|
2777
2951
|
}
|
|
2778
2952
|
const delegation = await this.createDelegationViaWasmPath(
|
|
@@ -2917,6 +3091,41 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2917
3091
|
host: this.config.host
|
|
2918
3092
|
};
|
|
2919
3093
|
}
|
|
3094
|
+
async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
|
|
3095
|
+
const result = this.createDelegationWrapper({
|
|
3096
|
+
session: grant.session,
|
|
3097
|
+
delegateDID: did,
|
|
3098
|
+
spaceId: grant.session.spaceId,
|
|
3099
|
+
abilities: this.permissionsToAbilities(entries),
|
|
3100
|
+
expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
|
|
3101
|
+
});
|
|
3102
|
+
const primary = result.resources[0];
|
|
3103
|
+
const delegationHeader = { Authorization: result.delegation };
|
|
3104
|
+
const targetHost = grant.delegation.host ?? this.config.host;
|
|
3105
|
+
const activateResult = await activateSessionWithHost2(
|
|
3106
|
+
targetHost,
|
|
3107
|
+
delegationHeader
|
|
3108
|
+
);
|
|
3109
|
+
if (!activateResult.success) {
|
|
3110
|
+
throw new Error(
|
|
3111
|
+
`Failed to activate delegation with host: ${activateResult.error}`
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
return {
|
|
3115
|
+
cid: result.cid,
|
|
3116
|
+
delegationHeader,
|
|
3117
|
+
spaceId: grant.session.spaceId,
|
|
3118
|
+
path: primary.path,
|
|
3119
|
+
actions: primary.actions,
|
|
3120
|
+
resources: result.resources,
|
|
3121
|
+
disableSubDelegation: false,
|
|
3122
|
+
expiry: result.expiry,
|
|
3123
|
+
delegateDID: did,
|
|
3124
|
+
ownerAddress: grant.delegation.ownerAddress,
|
|
3125
|
+
chainId: grant.delegation.chainId,
|
|
3126
|
+
host: targetHost
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
2920
3129
|
resolvePermissionSpace(space, session) {
|
|
2921
3130
|
if (space === void 0) {
|
|
2922
3131
|
return this.wasmBindings.makeSpaceId(
|
|
@@ -2933,6 +3142,220 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
2933
3142
|
}
|
|
2934
3143
|
return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
|
|
2935
3144
|
}
|
|
3145
|
+
expandPermissionEntries(permissions) {
|
|
3146
|
+
return expandPermissionEntriesCore(permissions);
|
|
3147
|
+
}
|
|
3148
|
+
shortServiceName(service) {
|
|
3149
|
+
const short = SERVICE_LONG_TO_SHORT[service];
|
|
3150
|
+
if (short === void 0) {
|
|
3151
|
+
throw new Error(
|
|
3152
|
+
`unknown service '${service}' \u2014 no short-form mapping`
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
return short;
|
|
3156
|
+
}
|
|
3157
|
+
permissionsToAbilities(entries) {
|
|
3158
|
+
const abilities = {};
|
|
3159
|
+
for (const entry of entries) {
|
|
3160
|
+
const service = this.shortServiceName(entry.service);
|
|
3161
|
+
abilities[service] ?? (abilities[service] = {});
|
|
3162
|
+
const existing = abilities[service][entry.path] ?? [];
|
|
3163
|
+
const seen = new Set(existing);
|
|
3164
|
+
for (const action of entry.actions) {
|
|
3165
|
+
if (!seen.has(action)) {
|
|
3166
|
+
existing.push(action);
|
|
3167
|
+
seen.add(action);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
abilities[service][entry.path] = existing;
|
|
3171
|
+
}
|
|
3172
|
+
return abilities;
|
|
3173
|
+
}
|
|
3174
|
+
permissionOperations(entries, spaceId) {
|
|
3175
|
+
return entries.flatMap((entry) => {
|
|
3176
|
+
const service = this.shortServiceName(entry.service);
|
|
3177
|
+
return entry.actions.map((action) => ({
|
|
3178
|
+
spaceId,
|
|
3179
|
+
service,
|
|
3180
|
+
path: entry.path,
|
|
3181
|
+
action
|
|
3182
|
+
}));
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
sessionCoversPermissionEntries(session, entries) {
|
|
3186
|
+
try {
|
|
3187
|
+
const granted = parseRecapCapabilities(
|
|
3188
|
+
(siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
|
|
3189
|
+
session.siwe
|
|
3190
|
+
);
|
|
3191
|
+
return isCapabilitySubset(entries, granted).subset;
|
|
3192
|
+
} catch {
|
|
3193
|
+
return false;
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
permissionEntriesToOperations(entries, session) {
|
|
3197
|
+
return entries.flatMap((entry) => {
|
|
3198
|
+
const spaceId = this.resolvePermissionSpace(entry.space, session);
|
|
3199
|
+
const service = this.shortServiceName(entry.service);
|
|
3200
|
+
return entry.actions.map((action) => ({
|
|
3201
|
+
spaceId,
|
|
3202
|
+
service,
|
|
3203
|
+
path: entry.path,
|
|
3204
|
+
action
|
|
3205
|
+
}));
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
findRuntimeGrantsForPermissionEntries(entries, session) {
|
|
3209
|
+
const grants = [];
|
|
3210
|
+
const operations = this.permissionEntriesToOperations(entries, session);
|
|
3211
|
+
if (operations.length === 0) {
|
|
3212
|
+
return grants;
|
|
3213
|
+
}
|
|
3214
|
+
for (const operation of operations) {
|
|
3215
|
+
const grant = this.findGrantForOperation(operation);
|
|
3216
|
+
if (!grant) {
|
|
3217
|
+
return [];
|
|
3218
|
+
}
|
|
3219
|
+
if (!grants.includes(grant)) {
|
|
3220
|
+
grants.push(grant);
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
return grants;
|
|
3224
|
+
}
|
|
3225
|
+
runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
|
|
3226
|
+
const resources = this.delegatedResourcesForEntries(entries, spaceId);
|
|
3227
|
+
const primary = resources[0];
|
|
3228
|
+
return {
|
|
3229
|
+
cid: delegatedSession.delegationCid,
|
|
3230
|
+
delegationHeader: delegatedSession.delegationHeader,
|
|
3231
|
+
spaceId,
|
|
3232
|
+
path: primary.path,
|
|
3233
|
+
actions: primary.actions,
|
|
3234
|
+
resources,
|
|
3235
|
+
disableSubDelegation: false,
|
|
3236
|
+
expiry: expiresAt,
|
|
3237
|
+
delegateDID: session.verificationMethod,
|
|
3238
|
+
ownerAddress: session.address,
|
|
3239
|
+
chainId: session.chainId,
|
|
3240
|
+
host: this.config.host
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
runtimeGrantFromDelegation(delegation, session) {
|
|
3244
|
+
const operations = this.operationsFromDelegation(delegation);
|
|
3245
|
+
return {
|
|
3246
|
+
session: {
|
|
3247
|
+
delegationHeader: delegation.delegationHeader,
|
|
3248
|
+
delegationCid: delegation.cid,
|
|
3249
|
+
spaceId: delegation.spaceId,
|
|
3250
|
+
verificationMethod: session.verificationMethod,
|
|
3251
|
+
jwk: session.jwk
|
|
3252
|
+
},
|
|
3253
|
+
delegation,
|
|
3254
|
+
operations,
|
|
3255
|
+
expiresAt: delegation.expiry
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
delegatedResourcesForEntries(entries, spaceId) {
|
|
3259
|
+
return entries.map((entry) => ({
|
|
3260
|
+
service: this.shortServiceName(entry.service),
|
|
3261
|
+
space: spaceId,
|
|
3262
|
+
path: entry.path,
|
|
3263
|
+
actions: [...entry.actions]
|
|
3264
|
+
}));
|
|
3265
|
+
}
|
|
3266
|
+
operationsFromDelegation(delegation) {
|
|
3267
|
+
const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
|
|
3268
|
+
return resources.flatMap(
|
|
3269
|
+
(resource) => resource.actions.map((action) => ({
|
|
3270
|
+
spaceId: resource.space,
|
|
3271
|
+
service: this.invocationServiceName(resource.service),
|
|
3272
|
+
path: resource.path,
|
|
3273
|
+
action
|
|
3274
|
+
}))
|
|
3275
|
+
);
|
|
3276
|
+
}
|
|
3277
|
+
flatDelegationResources(delegation) {
|
|
3278
|
+
const byService = /* @__PURE__ */ new Map();
|
|
3279
|
+
for (const action of delegation.actions) {
|
|
3280
|
+
const service = this.shortServiceName(action.split("/")[0]);
|
|
3281
|
+
const actions = byService.get(service) ?? [];
|
|
3282
|
+
actions.push(action);
|
|
3283
|
+
byService.set(service, actions);
|
|
3284
|
+
}
|
|
3285
|
+
return [...byService.entries()].map(([service, actions]) => ({
|
|
3286
|
+
service,
|
|
3287
|
+
space: delegation.spaceId,
|
|
3288
|
+
path: delegation.path,
|
|
3289
|
+
actions
|
|
3290
|
+
}));
|
|
3291
|
+
}
|
|
3292
|
+
selectInvocationSession(fallback, service, path, action) {
|
|
3293
|
+
const grant = this.findGrantForOperation({
|
|
3294
|
+
spaceId: fallback.spaceId,
|
|
3295
|
+
service: this.invocationServiceName(service),
|
|
3296
|
+
path,
|
|
3297
|
+
action
|
|
3298
|
+
});
|
|
3299
|
+
return grant?.session ?? fallback;
|
|
3300
|
+
}
|
|
3301
|
+
findGrantForOperations(operations) {
|
|
3302
|
+
if (operations.length === 0) {
|
|
3303
|
+
return void 0;
|
|
3304
|
+
}
|
|
3305
|
+
this.pruneExpiredRuntimePermissionGrants();
|
|
3306
|
+
return this.runtimePermissionGrants.find((grant) => {
|
|
3307
|
+
return operations.every(
|
|
3308
|
+
(operation) => grant.operations.some(
|
|
3309
|
+
(granted) => this.operationCovers(granted, operation)
|
|
3310
|
+
)
|
|
3311
|
+
);
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
findGrantForOperation(operation) {
|
|
3315
|
+
return this.findGrantForOperations([operation]);
|
|
3316
|
+
}
|
|
3317
|
+
pruneExpiredRuntimePermissionGrants() {
|
|
3318
|
+
const now = Date.now();
|
|
3319
|
+
this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
|
|
3320
|
+
(grant) => grant.expiresAt.getTime() > now
|
|
3321
|
+
);
|
|
3322
|
+
}
|
|
3323
|
+
operationCovers(granted, requested) {
|
|
3324
|
+
return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
|
|
3325
|
+
}
|
|
3326
|
+
actionContains(grantedAction, requestedAction) {
|
|
3327
|
+
if (grantedAction === requestedAction) {
|
|
3328
|
+
return true;
|
|
3329
|
+
}
|
|
3330
|
+
if (grantedAction.endsWith("/*")) {
|
|
3331
|
+
const prefix = grantedAction.slice(0, -2);
|
|
3332
|
+
return requestedAction.startsWith(`${prefix}/`);
|
|
3333
|
+
}
|
|
3334
|
+
return false;
|
|
3335
|
+
}
|
|
3336
|
+
invocationServiceName(service) {
|
|
3337
|
+
return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
|
|
3338
|
+
}
|
|
3339
|
+
pathContains(grantedPath, requestedPath) {
|
|
3340
|
+
if (grantedPath === "" || grantedPath === "/") {
|
|
3341
|
+
return true;
|
|
3342
|
+
}
|
|
3343
|
+
if (grantedPath.endsWith("/**")) {
|
|
3344
|
+
return requestedPath.startsWith(grantedPath.slice(0, -3));
|
|
3345
|
+
}
|
|
3346
|
+
if (grantedPath.endsWith("/*")) {
|
|
3347
|
+
const prefix = grantedPath.slice(0, -2);
|
|
3348
|
+
if (!requestedPath.startsWith(prefix)) {
|
|
3349
|
+
return false;
|
|
3350
|
+
}
|
|
3351
|
+
const remainder = requestedPath.slice(prefix.length);
|
|
3352
|
+
return !remainder.includes("/") || remainder === "/";
|
|
3353
|
+
}
|
|
3354
|
+
if (grantedPath.endsWith("/")) {
|
|
3355
|
+
return requestedPath.startsWith(grantedPath);
|
|
3356
|
+
}
|
|
3357
|
+
return grantedPath === requestedPath;
|
|
3358
|
+
}
|
|
2936
3359
|
/**
|
|
2937
3360
|
* Issue a delegation via the legacy wallet-signed SIWE path for a single
|
|
2938
3361
|
* {@link PermissionEntry}. Shares the implementation with the public
|
|
@@ -3320,6 +3743,7 @@ import {
|
|
|
3320
3743
|
ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
|
|
3321
3744
|
DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
|
|
3322
3745
|
DEFAULT_MANIFEST_VERSION,
|
|
3746
|
+
VAULT_PERMISSION_SERVICE,
|
|
3323
3747
|
PermissionNotInManifestError as PermissionNotInManifestError2,
|
|
3324
3748
|
SessionExpiredError as SessionExpiredError2,
|
|
3325
3749
|
ManifestValidationError,
|
|
@@ -3328,7 +3752,9 @@ import {
|
|
|
3328
3752
|
validateManifest,
|
|
3329
3753
|
loadManifest,
|
|
3330
3754
|
isCapabilitySubset as isCapabilitySubset2,
|
|
3331
|
-
expandActionShortNames
|
|
3755
|
+
expandActionShortNames,
|
|
3756
|
+
expandPermissionEntries,
|
|
3757
|
+
expandPermissionEntry,
|
|
3332
3758
|
parseExpiry as parseExpiry2,
|
|
3333
3759
|
resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
|
|
3334
3760
|
} from "@tinycloud/sdk-core";
|
|
@@ -3418,6 +3844,7 @@ export {
|
|
|
3418
3844
|
TinyCloud2 as TinyCloud,
|
|
3419
3845
|
TinyCloudNode,
|
|
3420
3846
|
UnsupportedFeatureError2 as UnsupportedFeatureError,
|
|
3847
|
+
VAULT_PERMISSION_SERVICE,
|
|
3421
3848
|
VaultHeaders,
|
|
3422
3849
|
VaultPublicSpaceKVActions,
|
|
3423
3850
|
VersionCheckError,
|
|
@@ -3433,7 +3860,9 @@ export {
|
|
|
3433
3860
|
defaultSignStrategy,
|
|
3434
3861
|
defaultSpaceCreationHandler,
|
|
3435
3862
|
deserializeDelegation,
|
|
3436
|
-
|
|
3863
|
+
expandActionShortNames,
|
|
3864
|
+
expandPermissionEntries,
|
|
3865
|
+
expandPermissionEntry,
|
|
3437
3866
|
isCapabilitySubset2 as isCapabilitySubset,
|
|
3438
3867
|
loadManifest,
|
|
3439
3868
|
makePublicSpaceId2 as makePublicSpaceId,
|