@tinycloud/sdk-core 2.4.0-beta.1 → 2.4.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/index.cjs +2224 -1355
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +210 -59
- package/dist/index.d.ts +210 -59
- package/dist/index.js +2221 -1349
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -188,7 +188,15 @@ var PersistedSessionDataSchema = z2.object({
|
|
|
188
188
|
/** Schema version for migrations */
|
|
189
189
|
version: z2.string(),
|
|
190
190
|
/** Optional ENS data */
|
|
191
|
-
ens: EnsDataSchema2.optional()
|
|
191
|
+
ens: EnsDataSchema2.optional(),
|
|
192
|
+
/**
|
|
193
|
+
* TinyCloud hosts this session was created against. Persisted so a
|
|
194
|
+
* restored session resolves to the same node without re-running the
|
|
195
|
+
* registry/fallback resolution (or the wallet sign-in flow). Optional
|
|
196
|
+
* for backward compatibility with sessions persisted before this field
|
|
197
|
+
* existed — those restore and lazily re-resolve their hosts.
|
|
198
|
+
*/
|
|
199
|
+
tinycloudHosts: z2.array(z2.string()).optional()
|
|
192
200
|
});
|
|
193
201
|
var TinyCloudSessionSchema = z2.object({
|
|
194
202
|
/** User's Ethereum address */
|
|
@@ -970,6 +978,7 @@ var SpaceService = class {
|
|
|
970
978
|
this._userDid = config.userDid;
|
|
971
979
|
this.sharingService = config.sharingService;
|
|
972
980
|
this.createDelegationFn = config.createDelegation;
|
|
981
|
+
this.onSpaceRegisteredFn = config.onSpaceRegistered;
|
|
973
982
|
}
|
|
974
983
|
/**
|
|
975
984
|
* Update the service configuration.
|
|
@@ -985,6 +994,7 @@ var SpaceService = class {
|
|
|
985
994
|
if (config.userDid !== void 0) this._userDid = config.userDid;
|
|
986
995
|
if (config.sharingService) this.sharingService = config.sharingService;
|
|
987
996
|
if (config.createDelegation) this.createDelegationFn = config.createDelegation;
|
|
997
|
+
if (config.onSpaceRegistered) this.onSpaceRegisteredFn = config.onSpaceRegistered;
|
|
988
998
|
this.spaceCache.clear();
|
|
989
999
|
this.infoCache.clear();
|
|
990
1000
|
}
|
|
@@ -1035,6 +1045,9 @@ var SpaceService = class {
|
|
|
1035
1045
|
spaces.push(...delegatedSpaces);
|
|
1036
1046
|
}
|
|
1037
1047
|
const uniqueSpaces = this.deduplicateSpaces(spaces);
|
|
1048
|
+
for (const space of uniqueSpaces) {
|
|
1049
|
+
this.notifySpaceRegistered(space);
|
|
1050
|
+
}
|
|
1038
1051
|
return ok(uniqueSpaces);
|
|
1039
1052
|
} catch (error) {
|
|
1040
1053
|
return err(
|
|
@@ -1232,6 +1245,7 @@ var SpaceService = class {
|
|
|
1232
1245
|
permissions: ["*"]
|
|
1233
1246
|
};
|
|
1234
1247
|
this.infoCache.set(spaceInfo.id, { info: spaceInfo, cachedAt: Date.now() });
|
|
1248
|
+
this.notifySpaceRegistered(spaceInfo);
|
|
1235
1249
|
return ok(spaceInfo);
|
|
1236
1250
|
} catch (error) {
|
|
1237
1251
|
return err(
|
|
@@ -1244,6 +1258,11 @@ var SpaceService = class {
|
|
|
1244
1258
|
);
|
|
1245
1259
|
}
|
|
1246
1260
|
}
|
|
1261
|
+
notifySpaceRegistered(space) {
|
|
1262
|
+
if (!this.onSpaceRegisteredFn) return;
|
|
1263
|
+
void Promise.resolve(this.onSpaceRegisteredFn(space)).catch(() => {
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1247
1266
|
// ===========================================================================
|
|
1248
1267
|
// Get Space
|
|
1249
1268
|
// ===========================================================================
|
|
@@ -2151,1474 +2170,2326 @@ var TinyCloud = class _TinyCloud {
|
|
|
2151
2170
|
}
|
|
2152
2171
|
};
|
|
2153
2172
|
|
|
2154
|
-
// src/
|
|
2173
|
+
// src/account/AccountService.ts
|
|
2155
2174
|
import {
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
ok as ok4,
|
|
2160
|
-
err as err4,
|
|
2161
|
-
serviceError as serviceError4,
|
|
2162
|
-
ErrorCodes as ErrorCodes2,
|
|
2163
|
-
defaultRetryPolicy as defaultRetryPolicy2,
|
|
2164
|
-
SQLService as SQLService2,
|
|
2165
|
-
DatabaseHandle,
|
|
2166
|
-
SQLAction,
|
|
2167
|
-
DuckDbService as DuckDbService2,
|
|
2168
|
-
DuckDbDatabaseHandle,
|
|
2169
|
-
DuckDbAction,
|
|
2170
|
-
HooksService as HooksService2,
|
|
2171
|
-
DataVaultService,
|
|
2172
|
-
VaultHeaders,
|
|
2173
|
-
VaultPublicSpaceKVActions,
|
|
2174
|
-
createVaultCrypto,
|
|
2175
|
-
SecretsService,
|
|
2176
|
-
SECRET_NAME_RE as SECRET_NAME_RE2,
|
|
2177
|
-
canonicalizeSecretScope,
|
|
2178
|
-
resolveSecretListPrefix,
|
|
2179
|
-
resolveSecretPath as resolveSecretPath2,
|
|
2180
|
-
EncryptionService,
|
|
2181
|
-
parseNetworkId as parseNetworkId2,
|
|
2182
|
-
buildNetworkId as buildNetworkId2,
|
|
2183
|
-
isNetworkId,
|
|
2184
|
-
networkDiscoveryKey,
|
|
2185
|
-
NetworkIdError,
|
|
2186
|
-
ENCRYPTION_NETWORK_URN_PREFIX,
|
|
2187
|
-
NETWORK_NAME_PATTERN,
|
|
2188
|
-
canonicalizeEncryptionJson,
|
|
2189
|
-
canonicalHashHex,
|
|
2190
|
-
hexEncode,
|
|
2191
|
-
hexDecode,
|
|
2192
|
-
base64Encode,
|
|
2193
|
-
base64Decode,
|
|
2194
|
-
utf8Encode,
|
|
2195
|
-
utf8Decode,
|
|
2196
|
-
encryptToNetwork,
|
|
2197
|
-
decryptEnvelopeWithKey,
|
|
2198
|
-
validateEnvelope,
|
|
2199
|
-
generateRandomReceiverKey,
|
|
2200
|
-
deriveSignedReceiverKey,
|
|
2201
|
-
buildCanonicalDecryptRequest,
|
|
2202
|
-
buildDecryptFacts,
|
|
2203
|
-
buildDecryptAttenuation,
|
|
2204
|
-
buildDecryptInvocation,
|
|
2205
|
-
checkDecryptInvocationInput,
|
|
2206
|
-
verifyDecryptResponse,
|
|
2207
|
-
canonicalSignedResponse,
|
|
2208
|
-
openWrappedKey,
|
|
2209
|
-
discoverNetwork,
|
|
2210
|
-
ensureNetworkUsableForDecrypt,
|
|
2211
|
-
DEFAULT_ENCRYPTION_ALG,
|
|
2212
|
-
ENVELOPE_VERSION,
|
|
2213
|
-
DEFAULT_KEY_VERSION,
|
|
2214
|
-
DECRYPT_FACT_TYPE,
|
|
2215
|
-
DECRYPT_RESULT_TYPE,
|
|
2216
|
-
DECRYPT_ACTION,
|
|
2217
|
-
ENCRYPTION_SERVICE,
|
|
2218
|
-
ENCRYPTION_SERVICE_SHORT,
|
|
2219
|
-
encryptionError
|
|
2175
|
+
err as err3,
|
|
2176
|
+
ok as ok3,
|
|
2177
|
+
serviceError as serviceError3
|
|
2220
2178
|
} from "@tinycloud/sdk-services";
|
|
2221
2179
|
|
|
2222
|
-
// src/
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
)
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
throw new Error(`Failed to get peer ID: ${res.status} - ${error}`);
|
|
2180
|
+
// src/manifest.ts
|
|
2181
|
+
import ms from "ms";
|
|
2182
|
+
import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
|
|
2183
|
+
var ManifestValidationError = class extends Error {
|
|
2184
|
+
constructor(message) {
|
|
2185
|
+
super(`Manifest validation failed: ${message}`);
|
|
2186
|
+
this.name = "ManifestValidationError";
|
|
2230
2187
|
}
|
|
2231
|
-
|
|
2188
|
+
};
|
|
2189
|
+
var DEFAULT_EXPIRY = "30d";
|
|
2190
|
+
var DEFAULT_DEFAULTS = true;
|
|
2191
|
+
var DEFAULT_MANIFEST_VERSION = 1;
|
|
2192
|
+
var DEFAULT_MANIFEST_SPACE = "applications";
|
|
2193
|
+
var ACCOUNT_REGISTRY_SPACE = "account";
|
|
2194
|
+
var ACCOUNT_REGISTRY_PATH = "applications/";
|
|
2195
|
+
var SECRETS_SPACE = "secrets";
|
|
2196
|
+
var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
|
|
2197
|
+
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
2198
|
+
kv: "tinycloud.kv",
|
|
2199
|
+
sql: "tinycloud.sql",
|
|
2200
|
+
duckdb: "tinycloud.duckdb",
|
|
2201
|
+
capabilities: "tinycloud.capabilities",
|
|
2202
|
+
hooks: "tinycloud.hooks",
|
|
2203
|
+
encryption: "tinycloud.encryption"
|
|
2204
|
+
});
|
|
2205
|
+
var ENCRYPTION_PERMISSION_SERVICE = "tinycloud.encryption";
|
|
2206
|
+
var ENCRYPTION_MANIFEST_SPACE = "encryption";
|
|
2207
|
+
var SERVICE_LONG_TO_SHORT = Object.freeze(
|
|
2208
|
+
Object.fromEntries(
|
|
2209
|
+
Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
|
|
2210
|
+
)
|
|
2211
|
+
);
|
|
2212
|
+
var DEFAULT_STANDARD_ENTRIES = [
|
|
2213
|
+
{
|
|
2214
|
+
service: "tinycloud.kv",
|
|
2215
|
+
space: DEFAULT_MANIFEST_SPACE,
|
|
2216
|
+
path: "/",
|
|
2217
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
service: "tinycloud.sql",
|
|
2221
|
+
space: DEFAULT_MANIFEST_SPACE,
|
|
2222
|
+
path: "/",
|
|
2223
|
+
actions: ["read", "write"]
|
|
2224
|
+
}
|
|
2225
|
+
];
|
|
2226
|
+
var DEFAULT_ADMIN_ENTRIES = [
|
|
2227
|
+
{
|
|
2228
|
+
service: "tinycloud.kv",
|
|
2229
|
+
space: DEFAULT_MANIFEST_SPACE,
|
|
2230
|
+
path: "/",
|
|
2231
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
service: "tinycloud.sql",
|
|
2235
|
+
space: DEFAULT_MANIFEST_SPACE,
|
|
2236
|
+
path: "/",
|
|
2237
|
+
actions: ["read", "write", "ddl"]
|
|
2238
|
+
}
|
|
2239
|
+
];
|
|
2240
|
+
var DEFAULT_ALL_ENTRIES = [
|
|
2241
|
+
{
|
|
2242
|
+
service: "tinycloud.kv",
|
|
2243
|
+
space: DEFAULT_MANIFEST_SPACE,
|
|
2244
|
+
path: "/",
|
|
2245
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2246
|
+
},
|
|
2247
|
+
{
|
|
2248
|
+
service: "tinycloud.sql",
|
|
2249
|
+
space: DEFAULT_MANIFEST_SPACE,
|
|
2250
|
+
path: "/",
|
|
2251
|
+
actions: ["read", "write", "ddl"]
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
service: "tinycloud.duckdb",
|
|
2255
|
+
space: DEFAULT_MANIFEST_SPACE,
|
|
2256
|
+
path: "/",
|
|
2257
|
+
actions: ["read", "write"]
|
|
2258
|
+
}
|
|
2259
|
+
];
|
|
2260
|
+
function parseExpiry(duration) {
|
|
2261
|
+
if (typeof duration !== "string" || duration.length === 0) {
|
|
2262
|
+
throw new ManifestValidationError(
|
|
2263
|
+
`expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
2266
|
+
const parsed = ms(duration);
|
|
2267
|
+
if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
|
|
2268
|
+
throw new ManifestValidationError(
|
|
2269
|
+
`invalid expiry duration: ${JSON.stringify(duration)}`
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
return parsed;
|
|
2232
2273
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2274
|
+
function expandActionShortNames(service, actions) {
|
|
2275
|
+
return actions.map((a) => {
|
|
2276
|
+
if (a.includes("/")) {
|
|
2277
|
+
return a;
|
|
2278
|
+
}
|
|
2279
|
+
return `${service}/${a}`;
|
|
2237
2280
|
});
|
|
2238
|
-
return {
|
|
2239
|
-
success: res.ok,
|
|
2240
|
-
status: res.status,
|
|
2241
|
-
error: res.ok ? void 0 : await res.text().catch(() => res.statusText)
|
|
2242
|
-
};
|
|
2243
2281
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
headers: delegationHeader
|
|
2248
|
-
});
|
|
2249
|
-
if (res.ok) {
|
|
2250
|
-
try {
|
|
2251
|
-
const body = await res.json();
|
|
2252
|
-
return {
|
|
2253
|
-
success: true,
|
|
2254
|
-
status: res.status,
|
|
2255
|
-
activated: body.activated ?? [],
|
|
2256
|
-
skipped: body.skipped ?? []
|
|
2257
|
-
};
|
|
2258
|
-
} catch {
|
|
2259
|
-
return {
|
|
2260
|
-
success: true,
|
|
2261
|
-
status: res.status,
|
|
2262
|
-
activated: [],
|
|
2263
|
-
skipped: []
|
|
2264
|
-
};
|
|
2265
|
-
}
|
|
2282
|
+
function expandPermissionEntry(entry) {
|
|
2283
|
+
if (entry.service === ENCRYPTION_PERMISSION_SERVICE) {
|
|
2284
|
+
return expandEncryptionPermissionEntry(entry);
|
|
2266
2285
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
CREATE: "tinycloud.delegation/create",
|
|
2277
|
-
REVOKE: "tinycloud.delegation/revoke",
|
|
2278
|
-
LIST: "tinycloud.delegation/list",
|
|
2279
|
-
GET: "tinycloud.delegation/get",
|
|
2280
|
-
CHECK: "tinycloud.delegation/check"
|
|
2281
|
-
};
|
|
2282
|
-
function createError(code, message, cause, meta) {
|
|
2283
|
-
return {
|
|
2284
|
-
code,
|
|
2285
|
-
message,
|
|
2286
|
-
service: "delegation",
|
|
2287
|
-
cause,
|
|
2288
|
-
meta
|
|
2289
|
-
};
|
|
2286
|
+
if (entry.service !== VAULT_PERMISSION_SERVICE) {
|
|
2287
|
+
return [
|
|
2288
|
+
{
|
|
2289
|
+
...entry,
|
|
2290
|
+
actions: expandActionShortNames(entry.service, entry.actions)
|
|
2291
|
+
}
|
|
2292
|
+
];
|
|
2293
|
+
}
|
|
2294
|
+
return expandVaultPermissionEntry(entry);
|
|
2290
2295
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
*/
|
|
2297
|
-
constructor(config) {
|
|
2298
|
-
this.hosts = config.hosts;
|
|
2299
|
-
this.session = config.session;
|
|
2300
|
-
this.invoke = config.invoke;
|
|
2301
|
-
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2296
|
+
function expandEncryptionPermissionEntry(entry) {
|
|
2297
|
+
if (typeof entry.path !== "string" || !entry.path.startsWith("urn:tinycloud:encryption:")) {
|
|
2298
|
+
throw new ManifestValidationError(
|
|
2299
|
+
`tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
|
|
2300
|
+
);
|
|
2302
2301
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
updateSession(session) {
|
|
2309
|
-
this.session = session;
|
|
2310
|
-
}
|
|
2311
|
-
/**
|
|
2312
|
-
* Gets the primary host URL.
|
|
2313
|
-
*/
|
|
2314
|
-
get host() {
|
|
2315
|
-
return this.hosts[0];
|
|
2316
|
-
}
|
|
2317
|
-
/**
|
|
2318
|
-
* Executes an invoke operation against the delegation API.
|
|
2319
|
-
*/
|
|
2320
|
-
async invokeOperation(path, action, body) {
|
|
2321
|
-
const headers = this.invoke(this.session, "delegation", path, action);
|
|
2322
|
-
return this.fetchFn(`${this.host}/invoke`, {
|
|
2323
|
-
method: "POST",
|
|
2324
|
-
headers,
|
|
2325
|
-
body
|
|
2326
|
-
});
|
|
2327
|
-
}
|
|
2328
|
-
/**
|
|
2329
|
-
* Creates a new delegation.
|
|
2330
|
-
*
|
|
2331
|
-
* Delegates specific permissions to another DID for a given path.
|
|
2332
|
-
* The delegatee can then use these permissions to access resources
|
|
2333
|
-
* within the specified scope.
|
|
2334
|
-
*
|
|
2335
|
-
* @param params - Parameters for the delegation
|
|
2336
|
-
* @returns Result containing the created Delegation or an error
|
|
2337
|
-
*
|
|
2338
|
-
* @example
|
|
2339
|
-
* ```typescript
|
|
2340
|
-
* const result = await manager.create({
|
|
2341
|
-
* delegateDID: bob.did,
|
|
2342
|
-
* path: "documents/shared/",
|
|
2343
|
-
* actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
|
|
2344
|
-
* expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
|
|
2345
|
-
* });
|
|
2346
|
-
* ```
|
|
2347
|
-
*/
|
|
2348
|
-
async create(params) {
|
|
2349
|
-
if (!params.delegateDID) {
|
|
2350
|
-
return {
|
|
2351
|
-
ok: false,
|
|
2352
|
-
error: createError(
|
|
2353
|
-
DelegationErrorCodes.INVALID_INPUT,
|
|
2354
|
-
"delegateDID is required"
|
|
2355
|
-
)
|
|
2356
|
-
};
|
|
2302
|
+
const normalizedActions = [];
|
|
2303
|
+
for (const action of entry.actions) {
|
|
2304
|
+
if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
|
|
2305
|
+
normalizedActions.push("tinycloud.encryption/decrypt");
|
|
2306
|
+
continue;
|
|
2357
2307
|
}
|
|
2358
|
-
if (
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
error: createError(
|
|
2362
|
-
DelegationErrorCodes.INVALID_INPUT,
|
|
2363
|
-
"path is required"
|
|
2364
|
-
)
|
|
2365
|
-
};
|
|
2308
|
+
if (action === "network.create" || action === "tinycloud.encryption/network.create") {
|
|
2309
|
+
normalizedActions.push("tinycloud.encryption/network.create");
|
|
2310
|
+
continue;
|
|
2366
2311
|
}
|
|
2367
|
-
if (
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
error: createError(
|
|
2371
|
-
DelegationErrorCodes.INVALID_INPUT,
|
|
2372
|
-
"at least one action is required"
|
|
2373
|
-
)
|
|
2374
|
-
};
|
|
2312
|
+
if (action === "network.revoke" || action === "tinycloud.encryption/network.revoke") {
|
|
2313
|
+
normalizedActions.push("tinycloud.encryption/network.revoke");
|
|
2314
|
+
continue;
|
|
2375
2315
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
path: params.path,
|
|
2380
|
-
actions: params.actions,
|
|
2381
|
-
expiry: params.expiry?.toISOString(),
|
|
2382
|
-
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
2383
|
-
statement: params.statement
|
|
2384
|
-
});
|
|
2385
|
-
const response = await this.invokeOperation(
|
|
2386
|
-
params.path,
|
|
2387
|
-
DelegationAction.CREATE,
|
|
2388
|
-
body
|
|
2316
|
+
if (action.includes("/")) {
|
|
2317
|
+
throw new ManifestValidationError(
|
|
2318
|
+
`unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
|
|
2389
2319
|
);
|
|
2390
|
-
if (!response.ok) {
|
|
2391
|
-
const errorText = await response.text();
|
|
2392
|
-
return {
|
|
2393
|
-
ok: false,
|
|
2394
|
-
error: createError(
|
|
2395
|
-
DelegationErrorCodes.CREATION_FAILED,
|
|
2396
|
-
`Failed to create delegation: ${response.status} - ${errorText}`,
|
|
2397
|
-
void 0,
|
|
2398
|
-
{ status: response.status, path: params.path }
|
|
2399
|
-
)
|
|
2400
|
-
};
|
|
2401
|
-
}
|
|
2402
|
-
const apiResponse = await response.json();
|
|
2403
|
-
const delegation = {
|
|
2404
|
-
cid: apiResponse.cid ?? "",
|
|
2405
|
-
delegateDID: params.delegateDID,
|
|
2406
|
-
spaceId: this.session.spaceId,
|
|
2407
|
-
path: params.path,
|
|
2408
|
-
actions: params.actions,
|
|
2409
|
-
expiry: params.expiry ?? new Date(Date.now() + EXPIRY.SHARE_MS),
|
|
2410
|
-
isRevoked: false,
|
|
2411
|
-
allowSubDelegation: !(params.disableSubDelegation ?? false),
|
|
2412
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
2413
|
-
};
|
|
2414
|
-
return { ok: true, data: delegation };
|
|
2415
|
-
} catch (error) {
|
|
2416
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2417
|
-
return {
|
|
2418
|
-
ok: false,
|
|
2419
|
-
error: createError(
|
|
2420
|
-
DelegationErrorCodes.ABORTED,
|
|
2421
|
-
"Request aborted",
|
|
2422
|
-
error
|
|
2423
|
-
)
|
|
2424
|
-
};
|
|
2425
|
-
}
|
|
2426
|
-
return {
|
|
2427
|
-
ok: false,
|
|
2428
|
-
error: createError(
|
|
2429
|
-
DelegationErrorCodes.NETWORK_ERROR,
|
|
2430
|
-
`Network error during delegation creation: ${String(error)}`,
|
|
2431
|
-
error instanceof Error ? error : void 0
|
|
2432
|
-
)
|
|
2433
|
-
};
|
|
2434
2320
|
}
|
|
2321
|
+
throw new ManifestValidationError(
|
|
2322
|
+
`unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
|
|
2323
|
+
);
|
|
2435
2324
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
* @param cid - The CID of the delegation to revoke
|
|
2443
|
-
* @returns Result indicating success or an error
|
|
2444
|
-
*
|
|
2445
|
-
* @example
|
|
2446
|
-
* ```typescript
|
|
2447
|
-
* const result = await manager.revoke("bafy...");
|
|
2448
|
-
* if (result.ok) {
|
|
2449
|
-
* console.log("Delegation revoked successfully");
|
|
2450
|
-
* }
|
|
2451
|
-
* ```
|
|
2452
|
-
*/
|
|
2453
|
-
async revoke(cid) {
|
|
2454
|
-
if (!cid) {
|
|
2455
|
-
return {
|
|
2456
|
-
ok: false,
|
|
2457
|
-
error: createError(
|
|
2458
|
-
DelegationErrorCodes.INVALID_INPUT,
|
|
2459
|
-
"cid is required"
|
|
2460
|
-
)
|
|
2461
|
-
};
|
|
2325
|
+
const dedupedActions = [];
|
|
2326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2327
|
+
for (const a of normalizedActions) {
|
|
2328
|
+
if (!seen.has(a)) {
|
|
2329
|
+
dedupedActions.push(a);
|
|
2330
|
+
seen.add(a);
|
|
2462
2331
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
return {
|
|
2474
|
-
ok: false,
|
|
2475
|
-
error: createError(
|
|
2476
|
-
DelegationErrorCodes.NOT_FOUND,
|
|
2477
|
-
`Delegation not found: ${cid}`
|
|
2478
|
-
)
|
|
2479
|
-
};
|
|
2480
|
-
}
|
|
2481
|
-
return {
|
|
2482
|
-
ok: false,
|
|
2483
|
-
error: createError(
|
|
2484
|
-
DelegationErrorCodes.REVOCATION_FAILED,
|
|
2485
|
-
`Failed to revoke delegation: ${response.status} - ${errorText}`,
|
|
2486
|
-
void 0,
|
|
2487
|
-
{ status: response.status, cid }
|
|
2488
|
-
)
|
|
2489
|
-
};
|
|
2490
|
-
}
|
|
2491
|
-
return { ok: true, data: void 0 };
|
|
2492
|
-
} catch (error) {
|
|
2493
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2494
|
-
return {
|
|
2495
|
-
ok: false,
|
|
2496
|
-
error: createError(
|
|
2497
|
-
DelegationErrorCodes.ABORTED,
|
|
2498
|
-
"Request aborted",
|
|
2499
|
-
error
|
|
2500
|
-
)
|
|
2501
|
-
};
|
|
2502
|
-
}
|
|
2503
|
-
return {
|
|
2504
|
-
ok: false,
|
|
2505
|
-
error: createError(
|
|
2506
|
-
DelegationErrorCodes.NETWORK_ERROR,
|
|
2507
|
-
`Network error during delegation revocation: ${String(error)}`,
|
|
2508
|
-
error instanceof Error ? error : void 0
|
|
2509
|
-
)
|
|
2510
|
-
};
|
|
2332
|
+
}
|
|
2333
|
+
return [
|
|
2334
|
+
{
|
|
2335
|
+
service: ENCRYPTION_PERMISSION_SERVICE,
|
|
2336
|
+
space: ENCRYPTION_MANIFEST_SPACE,
|
|
2337
|
+
path: entry.path,
|
|
2338
|
+
actions: dedupedActions,
|
|
2339
|
+
skipPrefix: true,
|
|
2340
|
+
...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
|
|
2341
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
2511
2342
|
}
|
|
2343
|
+
];
|
|
2344
|
+
}
|
|
2345
|
+
function expandPermissionEntries(entries) {
|
|
2346
|
+
return entries.flatMap(expandPermissionEntry);
|
|
2347
|
+
}
|
|
2348
|
+
function applyPrefix(prefix, path, skipPrefix) {
|
|
2349
|
+
if (skipPrefix) {
|
|
2350
|
+
return path;
|
|
2512
2351
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
DelegationErrorCodes.NETWORK_ERROR,
|
|
2576
|
-
`Network error during delegation list: ${String(error)}`,
|
|
2577
|
-
error instanceof Error ? error : void 0
|
|
2578
|
-
)
|
|
2579
|
-
};
|
|
2352
|
+
if (prefix === "") {
|
|
2353
|
+
return path;
|
|
2354
|
+
}
|
|
2355
|
+
if (path.startsWith("/")) {
|
|
2356
|
+
return `${prefix}${path}`;
|
|
2357
|
+
}
|
|
2358
|
+
return `${prefix}/${path}`;
|
|
2359
|
+
}
|
|
2360
|
+
async function loadManifest(url) {
|
|
2361
|
+
const fetchFn = globalThis.fetch;
|
|
2362
|
+
if (typeof fetchFn !== "function") {
|
|
2363
|
+
throw new ManifestValidationError(
|
|
2364
|
+
"loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
const res = await fetchFn(url);
|
|
2368
|
+
if (!res.ok) {
|
|
2369
|
+
throw new ManifestValidationError(
|
|
2370
|
+
`failed to fetch manifest from ${url}: HTTP ${res.status}`
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
const json = await res.json();
|
|
2374
|
+
return validateManifest(json);
|
|
2375
|
+
}
|
|
2376
|
+
function validateManifest(input) {
|
|
2377
|
+
if (input === null || typeof input !== "object") {
|
|
2378
|
+
throw new ManifestValidationError("manifest must be an object");
|
|
2379
|
+
}
|
|
2380
|
+
const m = input;
|
|
2381
|
+
if (m.manifest_version !== void 0 && m.manifest_version !== DEFAULT_MANIFEST_VERSION) {
|
|
2382
|
+
throw new ManifestValidationError(
|
|
2383
|
+
`manifest.manifest_version must be ${DEFAULT_MANIFEST_VERSION}`
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
if (typeof m.app_id !== "string" || m.app_id.length === 0) {
|
|
2387
|
+
throw new ManifestValidationError(
|
|
2388
|
+
"manifest.app_id is required and must be a non-empty string"
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
if (typeof m.name !== "string" || m.name.length === 0) {
|
|
2392
|
+
throw new ManifestValidationError(
|
|
2393
|
+
"manifest.name is required and must be a non-empty string"
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
if (m.did !== void 0 && (typeof m.did !== "string" || m.did.length === 0)) {
|
|
2397
|
+
throw new ManifestValidationError(
|
|
2398
|
+
"manifest.did must be a non-empty DID string"
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
2401
|
+
if (m.space !== void 0 && (typeof m.space !== "string" || m.space.length === 0)) {
|
|
2402
|
+
throw new ManifestValidationError(
|
|
2403
|
+
"manifest.space must be a non-empty string"
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
if (m.expiry !== void 0) {
|
|
2407
|
+
parseExpiry(m.expiry);
|
|
2408
|
+
}
|
|
2409
|
+
if (m.permissions !== void 0) {
|
|
2410
|
+
if (!Array.isArray(m.permissions)) {
|
|
2411
|
+
throw new ManifestValidationError(
|
|
2412
|
+
"manifest.permissions must be an array"
|
|
2413
|
+
);
|
|
2580
2414
|
}
|
|
2415
|
+
m.permissions.forEach(
|
|
2416
|
+
(p, i) => validatePermissionEntry(p, `permissions[${i}]`)
|
|
2417
|
+
);
|
|
2581
2418
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
* for (const delegation of result.data) {
|
|
2597
|
-
* console.log(`- ${delegation.delegatorDID} -> ${delegation.delegateDID}`);
|
|
2598
|
-
* }
|
|
2599
|
-
* }
|
|
2600
|
-
* ```
|
|
2601
|
-
*/
|
|
2602
|
-
async getChain(cid) {
|
|
2603
|
-
if (!cid) {
|
|
2604
|
-
return {
|
|
2605
|
-
ok: false,
|
|
2606
|
-
error: createError(
|
|
2607
|
-
DelegationErrorCodes.INVALID_INPUT,
|
|
2608
|
-
"cid is required"
|
|
2609
|
-
)
|
|
2610
|
-
};
|
|
2419
|
+
if (m.secrets !== void 0) {
|
|
2420
|
+
validateManifestSecrets(m.secrets);
|
|
2421
|
+
}
|
|
2422
|
+
return m;
|
|
2423
|
+
}
|
|
2424
|
+
function validateManifestSecrets(secrets) {
|
|
2425
|
+
if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
|
|
2426
|
+
throw new ManifestValidationError("manifest.secrets must be an object");
|
|
2427
|
+
}
|
|
2428
|
+
for (const [name, spec] of Object.entries(secrets)) {
|
|
2429
|
+
if (!SECRET_NAME_RE.test(name)) {
|
|
2430
|
+
throw new ManifestValidationError(
|
|
2431
|
+
`manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
|
|
2432
|
+
);
|
|
2611
2433
|
}
|
|
2612
2434
|
try {
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
DelegationAction.GET,
|
|
2617
|
-
body
|
|
2435
|
+
resolveSecretPath(
|
|
2436
|
+
secretNameFromSpec(name, spec),
|
|
2437
|
+
{ scope: secretScopeFromSpec(spec) }
|
|
2618
2438
|
);
|
|
2619
|
-
if (!response.ok) {
|
|
2620
|
-
const errorText = await response.text();
|
|
2621
|
-
if (response.status === 404) {
|
|
2622
|
-
return {
|
|
2623
|
-
ok: false,
|
|
2624
|
-
error: createError(
|
|
2625
|
-
DelegationErrorCodes.NOT_FOUND,
|
|
2626
|
-
`Delegation not found: ${cid}`
|
|
2627
|
-
)
|
|
2628
|
-
};
|
|
2629
|
-
}
|
|
2630
|
-
return {
|
|
2631
|
-
ok: false,
|
|
2632
|
-
error: createError(
|
|
2633
|
-
DelegationErrorCodes.NETWORK_ERROR,
|
|
2634
|
-
`Failed to get delegation chain: ${response.status} - ${errorText}`,
|
|
2635
|
-
void 0,
|
|
2636
|
-
{ status: response.status, cid }
|
|
2637
|
-
)
|
|
2638
|
-
};
|
|
2639
|
-
}
|
|
2640
|
-
const data = await response.json();
|
|
2641
|
-
const chain = data.chain.map((item) => ({
|
|
2642
|
-
cid: item.cid,
|
|
2643
|
-
delegateDID: item.delegateDID,
|
|
2644
|
-
delegatorDID: item.delegatorDID,
|
|
2645
|
-
spaceId: item.spaceId,
|
|
2646
|
-
path: item.path,
|
|
2647
|
-
actions: item.actions,
|
|
2648
|
-
expiry: new Date(item.expiry),
|
|
2649
|
-
isRevoked: item.isRevoked,
|
|
2650
|
-
createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
|
|
2651
|
-
parentCid: item.parentCid,
|
|
2652
|
-
allowSubDelegation: item.allowSubDelegation
|
|
2653
|
-
}));
|
|
2654
|
-
return { ok: true, data: chain };
|
|
2655
2439
|
} catch (error) {
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2440
|
+
throw new ManifestValidationError(
|
|
2441
|
+
`manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
const actions = secretActionsFromSpec(name, spec);
|
|
2445
|
+
if (actions.length === 0) {
|
|
2446
|
+
throw new ManifestValidationError(
|
|
2447
|
+
`manifest.secrets.${name} actions must be non-empty`
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
for (const action of actions) {
|
|
2451
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
2452
|
+
throw new ManifestValidationError(
|
|
2453
|
+
`manifest.secrets.${name} actions must be non-empty strings`
|
|
2454
|
+
);
|
|
2665
2455
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
DelegationErrorCodes.NETWORK_ERROR,
|
|
2670
|
-
`Network error during chain retrieval: ${String(error)}`,
|
|
2671
|
-
error instanceof Error ? error : void 0
|
|
2672
|
-
)
|
|
2673
|
-
};
|
|
2456
|
+
}
|
|
2457
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
|
|
2458
|
+
parseExpiry(spec.expiry);
|
|
2674
2459
|
}
|
|
2675
2460
|
}
|
|
2676
|
-
/**
|
|
2677
|
-
* Checks if the current session has permission for a given path and action.
|
|
2678
|
-
*
|
|
2679
|
-
* This can be used to verify permissions before attempting an operation,
|
|
2680
|
-
* or to implement custom access control logic.
|
|
2681
|
-
*
|
|
2682
|
-
* @param path - The resource path to check
|
|
2683
|
-
* @param action - The action to check (e.g., "tinycloud.kv/get")
|
|
2684
|
-
* @returns Result containing a boolean indicating permission or an error
|
|
2685
|
-
*
|
|
2686
|
-
* @example
|
|
2687
|
-
* ```typescript
|
|
2688
|
-
* const result = await manager.checkPermission("documents/private/", "tinycloud.kv/put");
|
|
2689
|
-
* if (result.ok && result.data) {
|
|
2690
|
-
* console.log("Permission granted");
|
|
2691
|
-
* } else {
|
|
2692
|
-
* console.log("Permission denied");
|
|
2693
|
-
* }
|
|
2694
|
-
* ```
|
|
2695
|
-
*/
|
|
2696
|
-
async checkPermission(path, action) {
|
|
2697
|
-
if (!path) {
|
|
2698
|
-
return {
|
|
2699
|
-
ok: false,
|
|
2700
|
-
error: createError(
|
|
2701
|
-
DelegationErrorCodes.INVALID_INPUT,
|
|
2702
|
-
"path is required"
|
|
2703
|
-
)
|
|
2704
|
-
};
|
|
2705
|
-
}
|
|
2706
|
-
if (!action) {
|
|
2707
|
-
return {
|
|
2708
|
-
ok: false,
|
|
2709
|
-
error: createError(
|
|
2710
|
-
DelegationErrorCodes.INVALID_INPUT,
|
|
2711
|
-
"action is required"
|
|
2712
|
-
)
|
|
2713
|
-
};
|
|
2714
|
-
}
|
|
2715
|
-
try {
|
|
2716
|
-
const body = JSON.stringify({ path, action });
|
|
2717
|
-
const response = await this.invokeOperation(
|
|
2718
|
-
path,
|
|
2719
|
-
DelegationAction.CHECK,
|
|
2720
|
-
body
|
|
2721
|
-
);
|
|
2722
|
-
if (!response.ok) {
|
|
2723
|
-
if (response.status === 403) {
|
|
2724
|
-
return { ok: true, data: false };
|
|
2725
|
-
}
|
|
2726
|
-
const errorText = await response.text();
|
|
2727
|
-
return {
|
|
2728
|
-
ok: false,
|
|
2729
|
-
error: createError(
|
|
2730
|
-
DelegationErrorCodes.NETWORK_ERROR,
|
|
2731
|
-
`Failed to check permission: ${response.status} - ${errorText}`,
|
|
2732
|
-
void 0,
|
|
2733
|
-
{ status: response.status, path, action }
|
|
2734
|
-
)
|
|
2735
|
-
};
|
|
2736
|
-
}
|
|
2737
|
-
const data = await response.json();
|
|
2738
|
-
return { ok: true, data: data.allowed };
|
|
2739
|
-
} catch (error) {
|
|
2740
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2741
|
-
return {
|
|
2742
|
-
ok: false,
|
|
2743
|
-
error: createError(
|
|
2744
|
-
DelegationErrorCodes.ABORTED,
|
|
2745
|
-
"Request aborted",
|
|
2746
|
-
error
|
|
2747
|
-
)
|
|
2748
|
-
};
|
|
2749
|
-
}
|
|
2750
|
-
return {
|
|
2751
|
-
ok: false,
|
|
2752
|
-
error: createError(
|
|
2753
|
-
DelegationErrorCodes.NETWORK_ERROR,
|
|
2754
|
-
`Network error during permission check: ${String(error)}`,
|
|
2755
|
-
error instanceof Error ? error : void 0
|
|
2756
|
-
)
|
|
2757
|
-
};
|
|
2758
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
};
|
|
2761
|
-
|
|
2762
|
-
// src/delegations/SharingService.schema.ts
|
|
2763
|
-
import { z as z5 } from "zod";
|
|
2764
|
-
var EncodedShareDataSchema = z5.object({
|
|
2765
|
-
/** Private key in JWK format (must include d parameter) */
|
|
2766
|
-
key: JWKSchema.refine(
|
|
2767
|
-
(jwk) => typeof jwk.d === "string" && jwk.d.length > 0,
|
|
2768
|
-
{ message: "JWK must include private key (d parameter)" }
|
|
2769
|
-
),
|
|
2770
|
-
/** DID of the key */
|
|
2771
|
-
keyDid: z5.string().min(1, "keyDid is required"),
|
|
2772
|
-
/** The delegation granting access */
|
|
2773
|
-
delegation: DelegationSchema,
|
|
2774
|
-
/** Resource path this link grants access to */
|
|
2775
|
-
path: z5.string().min(1, "path is required"),
|
|
2776
|
-
/** TinyCloud host URL */
|
|
2777
|
-
host: z5.string().url("host must be a valid URL"),
|
|
2778
|
-
/** Space ID */
|
|
2779
|
-
spaceId: z5.string().min(1, "spaceId is required"),
|
|
2780
|
-
/** Schema version (must be 1) */
|
|
2781
|
-
version: z5.literal(1)
|
|
2782
|
-
});
|
|
2783
|
-
var ReceiveOptionsSchema = z5.object({
|
|
2784
|
-
/**
|
|
2785
|
-
* Whether to automatically create a sub-delegation to the current session key.
|
|
2786
|
-
* Default: true
|
|
2787
|
-
*/
|
|
2788
|
-
autoSubdelegate: z5.boolean().optional(),
|
|
2789
|
-
/**
|
|
2790
|
-
* Whether to use the current session key for operations (requires autoSubdelegate).
|
|
2791
|
-
* Default: true
|
|
2792
|
-
*/
|
|
2793
|
-
useSessionKey: z5.boolean().optional(),
|
|
2794
|
-
/**
|
|
2795
|
-
* Ingestion options passed to CapabilityKeyRegistry.
|
|
2796
|
-
*/
|
|
2797
|
-
ingestOptions: IngestOptionsSchema.optional()
|
|
2798
|
-
});
|
|
2799
|
-
var SharingServiceConfigSchema = z5.object({
|
|
2800
|
-
/** TinyCloud host URLs */
|
|
2801
|
-
hosts: z5.array(z5.string().url()).min(1, "At least one host URL is required"),
|
|
2802
|
-
/**
|
|
2803
|
-
* Active session for authentication.
|
|
2804
|
-
* Required for generate(), optional for receive().
|
|
2805
|
-
*/
|
|
2806
|
-
session: z5.unknown().refine(
|
|
2807
|
-
(val) => val === void 0 || val !== null && typeof val === "object",
|
|
2808
|
-
{ message: "Expected a ServiceSession object or undefined" }
|
|
2809
|
-
).optional(),
|
|
2810
|
-
/** Platform-specific invoke function */
|
|
2811
|
-
invoke: z5.unknown().refine((val) => typeof val === "function", {
|
|
2812
|
-
message: "Expected an invoke function"
|
|
2813
|
-
}),
|
|
2814
|
-
/** Optional custom fetch implementation */
|
|
2815
|
-
fetch: z5.unknown().refine(
|
|
2816
|
-
(val) => val === void 0 || typeof val === "function",
|
|
2817
|
-
{ message: "Expected a fetch function or undefined" }
|
|
2818
|
-
).optional(),
|
|
2819
|
-
/** Key provider for cryptographic operations */
|
|
2820
|
-
keyProvider: KeyProviderSchema,
|
|
2821
|
-
/** Capability key registry for key/delegation management */
|
|
2822
|
-
registry: z5.unknown().refine(
|
|
2823
|
-
(val) => val !== null && typeof val === "object",
|
|
2824
|
-
{ message: "Expected an ICapabilityKeyRegistry object" }
|
|
2825
|
-
),
|
|
2826
|
-
/**
|
|
2827
|
-
* Delegation manager for creating delegations.
|
|
2828
|
-
* Required for generate(), optional for receive().
|
|
2829
|
-
*/
|
|
2830
|
-
delegationManager: z5.unknown().refine(
|
|
2831
|
-
(val) => val === void 0 || val !== null && typeof val === "object",
|
|
2832
|
-
{ message: "Expected a DelegationManager object or undefined" }
|
|
2833
|
-
).optional(),
|
|
2834
|
-
/** Factory for creating KV service instances */
|
|
2835
|
-
createKVService: z5.unknown().refine(
|
|
2836
|
-
(val) => typeof val === "function",
|
|
2837
|
-
{ message: "Expected a createKVService factory function" }
|
|
2838
|
-
),
|
|
2839
|
-
/** Base URL for sharing links (e.g., "https://share.myapp.com") */
|
|
2840
|
-
baseUrl: z5.string().optional(),
|
|
2841
|
-
/**
|
|
2842
|
-
* Custom delegation creation function.
|
|
2843
|
-
*/
|
|
2844
|
-
createDelegation: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
2845
|
-
message: "Expected a createDelegation function or undefined"
|
|
2846
|
-
}).optional(),
|
|
2847
|
-
/**
|
|
2848
|
-
* WASM function for client-side delegation creation.
|
|
2849
|
-
*/
|
|
2850
|
-
createDelegationWasm: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
2851
|
-
message: "Expected a createDelegationWasm function or undefined"
|
|
2852
|
-
}).optional(),
|
|
2853
|
-
/**
|
|
2854
|
-
* Path prefix for KV operations.
|
|
2855
|
-
*/
|
|
2856
|
-
pathPrefix: z5.string().optional(),
|
|
2857
|
-
/**
|
|
2858
|
-
* Session expiry time.
|
|
2859
|
-
*/
|
|
2860
|
-
sessionExpiry: z5.date().optional(),
|
|
2861
|
-
/**
|
|
2862
|
-
* Callback to create a DIRECT delegation from wallet to share key.
|
|
2863
|
-
* This is the preferred method for long-lived share links because it
|
|
2864
|
-
* bypasses the session delegation chain entirely.
|
|
2865
|
-
*/
|
|
2866
|
-
onRootDelegationNeeded: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
2867
|
-
message: "Expected an onRootDelegationNeeded function or undefined"
|
|
2868
|
-
}).optional()
|
|
2869
|
-
});
|
|
2870
|
-
function validateEncodedShareData(data) {
|
|
2871
|
-
const result = EncodedShareDataSchema.safeParse(data);
|
|
2872
|
-
if (!result.success) {
|
|
2873
|
-
return {
|
|
2874
|
-
ok: false,
|
|
2875
|
-
error: {
|
|
2876
|
-
code: DelegationErrorCodes.VALIDATION_ERROR,
|
|
2877
|
-
message: `Invalid share data: ${result.error.message}`,
|
|
2878
|
-
service: "delegation",
|
|
2879
|
-
meta: { issues: result.error.issues }
|
|
2880
|
-
}
|
|
2881
|
-
};
|
|
2882
|
-
}
|
|
2883
|
-
return { ok: true, data: result.data };
|
|
2884
2461
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
|
|
2889
|
-
var ManifestValidationError = class extends Error {
|
|
2890
|
-
constructor(message) {
|
|
2891
|
-
super(`Manifest validation failed: ${message}`);
|
|
2892
|
-
this.name = "ManifestValidationError";
|
|
2893
|
-
}
|
|
2894
|
-
};
|
|
2895
|
-
var DEFAULT_EXPIRY = "30d";
|
|
2896
|
-
var DEFAULT_DEFAULTS = true;
|
|
2897
|
-
var DEFAULT_MANIFEST_VERSION = 1;
|
|
2898
|
-
var DEFAULT_MANIFEST_SPACE = "applications";
|
|
2899
|
-
var ACCOUNT_REGISTRY_SPACE = "account";
|
|
2900
|
-
var ACCOUNT_REGISTRY_PATH = "applications/";
|
|
2901
|
-
var SECRETS_SPACE = "secrets";
|
|
2902
|
-
var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
|
|
2903
|
-
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
2904
|
-
kv: "tinycloud.kv",
|
|
2905
|
-
sql: "tinycloud.sql",
|
|
2906
|
-
duckdb: "tinycloud.duckdb",
|
|
2907
|
-
capabilities: "tinycloud.capabilities",
|
|
2908
|
-
hooks: "tinycloud.hooks",
|
|
2909
|
-
encryption: "tinycloud.encryption"
|
|
2910
|
-
});
|
|
2911
|
-
var ENCRYPTION_PERMISSION_SERVICE = "tinycloud.encryption";
|
|
2912
|
-
var ENCRYPTION_MANIFEST_SPACE = "encryption";
|
|
2913
|
-
var SERVICE_LONG_TO_SHORT = Object.freeze(
|
|
2914
|
-
Object.fromEntries(
|
|
2915
|
-
Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
|
|
2916
|
-
)
|
|
2917
|
-
);
|
|
2918
|
-
var DEFAULT_STANDARD_ENTRIES = [
|
|
2919
|
-
{
|
|
2920
|
-
service: "tinycloud.kv",
|
|
2921
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2922
|
-
path: "/",
|
|
2923
|
-
actions: ["get", "put", "del", "list", "metadata"]
|
|
2924
|
-
},
|
|
2925
|
-
{
|
|
2926
|
-
service: "tinycloud.sql",
|
|
2927
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2928
|
-
path: "/",
|
|
2929
|
-
actions: ["read", "write"]
|
|
2462
|
+
function validatePermissionEntry(p, path) {
|
|
2463
|
+
if (p === null || typeof p !== "object") {
|
|
2464
|
+
throw new ManifestValidationError(`${path} must be an object`);
|
|
2930
2465
|
}
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
service: "tinycloud.kv",
|
|
2935
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2936
|
-
path: "/",
|
|
2937
|
-
actions: ["get", "put", "del", "list", "metadata"]
|
|
2938
|
-
},
|
|
2939
|
-
{
|
|
2940
|
-
service: "tinycloud.sql",
|
|
2941
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2942
|
-
path: "/",
|
|
2943
|
-
actions: ["read", "write", "ddl"]
|
|
2466
|
+
const entry = p;
|
|
2467
|
+
if (typeof entry.service !== "string" || entry.service.length === 0) {
|
|
2468
|
+
throw new ManifestValidationError(`${path}.service is required`);
|
|
2944
2469
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2950
|
-
path: "/",
|
|
2951
|
-
actions: ["get", "put", "del", "list", "metadata"]
|
|
2952
|
-
},
|
|
2953
|
-
{
|
|
2954
|
-
service: "tinycloud.sql",
|
|
2955
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2956
|
-
path: "/",
|
|
2957
|
-
actions: ["read", "write", "ddl"]
|
|
2958
|
-
},
|
|
2959
|
-
{
|
|
2960
|
-
service: "tinycloud.duckdb",
|
|
2961
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2962
|
-
path: "/",
|
|
2963
|
-
actions: ["read", "write"]
|
|
2470
|
+
if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space.length === 0)) {
|
|
2471
|
+
throw new ManifestValidationError(
|
|
2472
|
+
`${path}.space must be a non-empty string`
|
|
2473
|
+
);
|
|
2964
2474
|
}
|
|
2965
|
-
|
|
2966
|
-
function parseExpiry(duration) {
|
|
2967
|
-
if (typeof duration !== "string" || duration.length === 0) {
|
|
2475
|
+
if (typeof entry.path !== "string") {
|
|
2968
2476
|
throw new ManifestValidationError(
|
|
2969
|
-
|
|
2477
|
+
`${path}.path is required (use "" or "/" for root)`
|
|
2970
2478
|
);
|
|
2971
2479
|
}
|
|
2972
|
-
|
|
2973
|
-
if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
|
|
2480
|
+
if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
|
|
2974
2481
|
throw new ManifestValidationError(
|
|
2975
|
-
|
|
2482
|
+
`${path}.actions must be a non-empty array`
|
|
2976
2483
|
);
|
|
2977
2484
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
return a;
|
|
2485
|
+
for (const action of entry.actions) {
|
|
2486
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
2487
|
+
throw new ManifestValidationError(
|
|
2488
|
+
`${path}.actions must contain non-empty strings`
|
|
2489
|
+
);
|
|
2984
2490
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
2491
|
+
if (entry.service === VAULT_PERMISSION_SERVICE) {
|
|
2492
|
+
vaultActionExpansion(action);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
if (entry.expiry !== void 0) {
|
|
2496
|
+
parseExpiry(entry.expiry);
|
|
2497
|
+
}
|
|
2987
2498
|
}
|
|
2988
|
-
function
|
|
2989
|
-
if (
|
|
2990
|
-
return
|
|
2499
|
+
function normalizeDefaults(value) {
|
|
2500
|
+
if (value === void 0) {
|
|
2501
|
+
return DEFAULT_DEFAULTS;
|
|
2991
2502
|
}
|
|
2992
|
-
if (
|
|
2993
|
-
return
|
|
2994
|
-
{
|
|
2995
|
-
...entry,
|
|
2996
|
-
actions: expandActionShortNames(entry.service, entry.actions)
|
|
2997
|
-
}
|
|
2998
|
-
];
|
|
2503
|
+
if (typeof value === "boolean") {
|
|
2504
|
+
return value;
|
|
2999
2505
|
}
|
|
3000
|
-
|
|
2506
|
+
if (typeof value !== "string") {
|
|
2507
|
+
return true;
|
|
2508
|
+
}
|
|
2509
|
+
const normalized = value.trim().toLowerCase();
|
|
2510
|
+
if (normalized === "admin" || normalized === "all") {
|
|
2511
|
+
return normalized;
|
|
2512
|
+
}
|
|
2513
|
+
return true;
|
|
3001
2514
|
}
|
|
3002
|
-
function
|
|
3003
|
-
if (
|
|
3004
|
-
|
|
3005
|
-
`tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
|
|
3006
|
-
);
|
|
2515
|
+
function defaultEntriesForTier(tier) {
|
|
2516
|
+
if (tier === false) {
|
|
2517
|
+
return [];
|
|
3007
2518
|
}
|
|
3008
|
-
const
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
2519
|
+
const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
|
|
2520
|
+
return source.map((e) => ({
|
|
2521
|
+
service: e.service,
|
|
2522
|
+
space: e.space,
|
|
2523
|
+
path: e.path,
|
|
2524
|
+
actions: [...e.actions],
|
|
2525
|
+
...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
|
|
2526
|
+
}));
|
|
2527
|
+
}
|
|
2528
|
+
function resolveManifest(input) {
|
|
2529
|
+
const manifest = validateManifest(input);
|
|
2530
|
+
const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.app_id;
|
|
2531
|
+
const space = manifest.space ?? DEFAULT_MANIFEST_SPACE;
|
|
2532
|
+
const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
|
|
2533
|
+
const includePublicSpace = manifest.includePublicSpace ?? true;
|
|
2534
|
+
const tier = normalizeDefaults(manifest.defaults);
|
|
2535
|
+
const defaultEntries = defaultEntriesForTier(tier);
|
|
2536
|
+
const explicitEntries = manifest.permissions ?? [];
|
|
2537
|
+
const secretEntries = secretEntriesForManifest(manifest.secrets);
|
|
2538
|
+
const allEntries = [
|
|
2539
|
+
...defaultEntries,
|
|
2540
|
+
...explicitEntries,
|
|
2541
|
+
...secretEntries
|
|
2542
|
+
];
|
|
2543
|
+
const resources = withCapabilitiesReadForSpaces(
|
|
2544
|
+
allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
|
|
2545
|
+
);
|
|
2546
|
+
const additionalDelegates = manifest.did === void 0 ? [] : [
|
|
2547
|
+
{
|
|
2548
|
+
did: manifest.did,
|
|
2549
|
+
name: manifest.name,
|
|
2550
|
+
expiryMs,
|
|
2551
|
+
permissions: resources.map(cloneResourceCapability)
|
|
2552
|
+
}
|
|
2553
|
+
];
|
|
2554
|
+
return {
|
|
2555
|
+
app_id: manifest.app_id,
|
|
2556
|
+
...manifest.did !== void 0 ? { did: manifest.did } : {},
|
|
2557
|
+
space,
|
|
2558
|
+
resources,
|
|
2559
|
+
expiryMs,
|
|
2560
|
+
includePublicSpace,
|
|
2561
|
+
additionalDelegates
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
function normalizeSecretActions(actions) {
|
|
2565
|
+
const out = [];
|
|
2566
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2567
|
+
const add = (action) => {
|
|
2568
|
+
if (!seen.has(action)) {
|
|
2569
|
+
out.push(action);
|
|
2570
|
+
seen.add(action);
|
|
2571
|
+
}
|
|
2572
|
+
};
|
|
2573
|
+
for (const action of actions) {
|
|
2574
|
+
if (action === "read") {
|
|
2575
|
+
add("get");
|
|
3012
2576
|
continue;
|
|
3013
2577
|
}
|
|
3014
|
-
if (action === "
|
|
3015
|
-
|
|
2578
|
+
if (action === "write") {
|
|
2579
|
+
add("put");
|
|
3016
2580
|
continue;
|
|
3017
2581
|
}
|
|
3018
|
-
if (action === "
|
|
3019
|
-
|
|
2582
|
+
if (action === "delete") {
|
|
2583
|
+
add("del");
|
|
3020
2584
|
continue;
|
|
3021
2585
|
}
|
|
3022
|
-
if (action
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
2586
|
+
if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
|
|
2587
|
+
add(action);
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
|
|
2591
|
+
add(action);
|
|
2592
|
+
continue;
|
|
3026
2593
|
}
|
|
3027
2594
|
throw new ManifestValidationError(
|
|
3028
|
-
`unknown
|
|
2595
|
+
`unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
|
|
3029
2596
|
);
|
|
3030
2597
|
}
|
|
3031
|
-
|
|
3032
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3033
|
-
for (const a of normalizedActions) {
|
|
3034
|
-
if (!seen.has(a)) {
|
|
3035
|
-
dedupedActions.push(a);
|
|
3036
|
-
seen.add(a);
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
return [
|
|
3040
|
-
{
|
|
3041
|
-
service: ENCRYPTION_PERMISSION_SERVICE,
|
|
3042
|
-
space: ENCRYPTION_MANIFEST_SPACE,
|
|
3043
|
-
path: entry.path,
|
|
3044
|
-
actions: dedupedActions,
|
|
3045
|
-
skipPrefix: true,
|
|
3046
|
-
...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
|
|
3047
|
-
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
3048
|
-
}
|
|
3049
|
-
];
|
|
3050
|
-
}
|
|
3051
|
-
function expandPermissionEntries(entries) {
|
|
3052
|
-
return entries.flatMap(expandPermissionEntry);
|
|
2598
|
+
return out;
|
|
3053
2599
|
}
|
|
3054
|
-
function
|
|
3055
|
-
if (
|
|
3056
|
-
return
|
|
3057
|
-
}
|
|
3058
|
-
if (prefix === "") {
|
|
3059
|
-
return path;
|
|
3060
|
-
}
|
|
3061
|
-
if (path.startsWith("/")) {
|
|
3062
|
-
return `${prefix}${path}`;
|
|
2600
|
+
function secretNameFromSpec(fallbackName, spec) {
|
|
2601
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
|
|
2602
|
+
return spec.name ?? fallbackName;
|
|
3063
2603
|
}
|
|
3064
|
-
return
|
|
2604
|
+
return fallbackName;
|
|
3065
2605
|
}
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
throw new ManifestValidationError(
|
|
3070
|
-
"loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
|
|
3071
|
-
);
|
|
3072
|
-
}
|
|
3073
|
-
const res = await fetchFn(url);
|
|
3074
|
-
if (!res.ok) {
|
|
3075
|
-
throw new ManifestValidationError(
|
|
3076
|
-
`failed to fetch manifest from ${url}: HTTP ${res.status}`
|
|
3077
|
-
);
|
|
2606
|
+
function secretScopeFromSpec(spec) {
|
|
2607
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
|
|
2608
|
+
return spec.scope;
|
|
3078
2609
|
}
|
|
3079
|
-
|
|
3080
|
-
return validateManifest(json);
|
|
2610
|
+
return void 0;
|
|
3081
2611
|
}
|
|
3082
|
-
function
|
|
3083
|
-
if (
|
|
3084
|
-
|
|
3085
|
-
}
|
|
3086
|
-
const m = input;
|
|
3087
|
-
if (m.manifest_version !== void 0 && m.manifest_version !== DEFAULT_MANIFEST_VERSION) {
|
|
3088
|
-
throw new ManifestValidationError(
|
|
3089
|
-
`manifest.manifest_version must be ${DEFAULT_MANIFEST_VERSION}`
|
|
3090
|
-
);
|
|
3091
|
-
}
|
|
3092
|
-
if (typeof m.app_id !== "string" || m.app_id.length === 0) {
|
|
3093
|
-
throw new ManifestValidationError(
|
|
3094
|
-
"manifest.app_id is required and must be a non-empty string"
|
|
3095
|
-
);
|
|
2612
|
+
function secretActionsFromSpec(name, spec) {
|
|
2613
|
+
if (spec === true) {
|
|
2614
|
+
return ["read"];
|
|
3096
2615
|
}
|
|
3097
|
-
if (typeof
|
|
3098
|
-
|
|
3099
|
-
"manifest.name is required and must be a non-empty string"
|
|
3100
|
-
);
|
|
2616
|
+
if (typeof spec === "string") {
|
|
2617
|
+
return [spec];
|
|
3101
2618
|
}
|
|
3102
|
-
if (
|
|
3103
|
-
|
|
3104
|
-
"manifest.did must be a non-empty DID string"
|
|
3105
|
-
);
|
|
2619
|
+
if (Array.isArray(spec)) {
|
|
2620
|
+
return spec;
|
|
3106
2621
|
}
|
|
3107
|
-
if (
|
|
2622
|
+
if (spec === null || typeof spec !== "object") {
|
|
3108
2623
|
throw new ManifestValidationError(
|
|
3109
|
-
|
|
2624
|
+
`manifest.secrets.${name} must be true, a string action, an actions array, or an object`
|
|
3110
2625
|
);
|
|
3111
2626
|
}
|
|
3112
|
-
if (
|
|
3113
|
-
|
|
2627
|
+
if (spec.actions === void 0) {
|
|
2628
|
+
return ["read"];
|
|
3114
2629
|
}
|
|
3115
|
-
if (
|
|
3116
|
-
|
|
3117
|
-
throw new ManifestValidationError(
|
|
3118
|
-
"manifest.permissions must be an array"
|
|
3119
|
-
);
|
|
3120
|
-
}
|
|
3121
|
-
m.permissions.forEach(
|
|
3122
|
-
(p, i) => validatePermissionEntry(p, `permissions[${i}]`)
|
|
3123
|
-
);
|
|
2630
|
+
if (typeof spec.actions === "string") {
|
|
2631
|
+
return [spec.actions];
|
|
3124
2632
|
}
|
|
3125
|
-
if (
|
|
3126
|
-
|
|
2633
|
+
if (Array.isArray(spec.actions)) {
|
|
2634
|
+
return spec.actions;
|
|
3127
2635
|
}
|
|
3128
|
-
|
|
2636
|
+
throw new ManifestValidationError(
|
|
2637
|
+
`manifest.secrets.${name}.actions must be a string or array`
|
|
2638
|
+
);
|
|
3129
2639
|
}
|
|
3130
|
-
function
|
|
3131
|
-
if (secrets ===
|
|
3132
|
-
|
|
2640
|
+
function secretEntriesForManifest(secrets) {
|
|
2641
|
+
if (secrets === void 0) {
|
|
2642
|
+
return [];
|
|
3133
2643
|
}
|
|
2644
|
+
const entries = [];
|
|
3134
2645
|
for (const [name, spec] of Object.entries(secrets)) {
|
|
3135
|
-
if (!SECRET_NAME_RE.test(name)) {
|
|
3136
|
-
throw new ManifestValidationError(
|
|
3137
|
-
`manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
|
|
3138
|
-
);
|
|
3139
|
-
}
|
|
3140
|
-
try {
|
|
3141
|
-
resolveSecretPath(
|
|
3142
|
-
secretNameFromSpec(name, spec),
|
|
3143
|
-
{ scope: secretScopeFromSpec(spec) }
|
|
3144
|
-
);
|
|
3145
|
-
} catch (error) {
|
|
3146
|
-
throw new ManifestValidationError(
|
|
3147
|
-
`manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
|
|
3148
|
-
);
|
|
3149
|
-
}
|
|
3150
2646
|
const actions = secretActionsFromSpec(name, spec);
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
2647
|
+
const secretPath = resolveSecretPath(
|
|
2648
|
+
secretNameFromSpec(name, spec),
|
|
2649
|
+
{ scope: secretScopeFromSpec(spec) }
|
|
2650
|
+
);
|
|
2651
|
+
const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
|
|
2652
|
+
entries.push({
|
|
2653
|
+
service: VAULT_PERMISSION_SERVICE,
|
|
2654
|
+
space: SECRETS_SPACE,
|
|
2655
|
+
path: secretPath.vaultKey,
|
|
2656
|
+
actions: normalizeSecretActions(actions),
|
|
2657
|
+
skipPrefix: true,
|
|
2658
|
+
...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
|
|
2659
|
+
...extra.description !== void 0 ? { description: extra.description } : {}
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
return entries;
|
|
2663
|
+
}
|
|
2664
|
+
function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
|
|
2665
|
+
const skipPrefixForEntry = entry.skipPrefix === true || entry.service === ENCRYPTION_PERMISSION_SERVICE;
|
|
2666
|
+
const resolvedPath = applyPrefix(prefix, entry.path, skipPrefixForEntry);
|
|
2667
|
+
const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
|
|
2668
|
+
return expandPermissionEntry({
|
|
2669
|
+
...entry,
|
|
2670
|
+
space: entry.space ?? inheritedSpace,
|
|
2671
|
+
path: resolvedPath,
|
|
2672
|
+
skipPrefix: true
|
|
2673
|
+
}).map((expanded) => ({
|
|
2674
|
+
service: expanded.service,
|
|
2675
|
+
space: expanded.space ?? inheritedSpace,
|
|
2676
|
+
path: expanded.path,
|
|
2677
|
+
actions: expanded.actions,
|
|
2678
|
+
// Only populate `expiryMs` when the entry had its own expiry override.
|
|
2679
|
+
// When absent, callers use the parent (delegation or manifest) expiry
|
|
2680
|
+
// which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
|
|
2681
|
+
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
|
|
2682
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
2683
|
+
}));
|
|
2684
|
+
}
|
|
2685
|
+
function expandVaultPermissionEntry(entry) {
|
|
2686
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
2687
|
+
for (const action of entry.actions) {
|
|
2688
|
+
const expansion = vaultActionExpansion(action);
|
|
2689
|
+
for (const base of expansion.bases) {
|
|
2690
|
+
const actions = byBase.get(base) ?? [];
|
|
2691
|
+
if (!actions.includes(expansion.action)) {
|
|
2692
|
+
actions.push(expansion.action);
|
|
3161
2693
|
}
|
|
3162
|
-
|
|
3163
|
-
if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
|
|
3164
|
-
parseExpiry(spec.expiry);
|
|
2694
|
+
byBase.set(base, actions);
|
|
3165
2695
|
}
|
|
3166
2696
|
}
|
|
2697
|
+
return [...byBase.entries()].map(([base, actions]) => ({
|
|
2698
|
+
...entry,
|
|
2699
|
+
service: "tinycloud.kv",
|
|
2700
|
+
path: vaultKVPath(base, entry.path),
|
|
2701
|
+
actions,
|
|
2702
|
+
skipPrefix: true
|
|
2703
|
+
}));
|
|
3167
2704
|
}
|
|
3168
|
-
function
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
const entry = p;
|
|
3173
|
-
if (typeof entry.service !== "string" || entry.service.length === 0) {
|
|
3174
|
-
throw new ManifestValidationError(`${path}.service is required`);
|
|
2705
|
+
function vaultActionExpansion(action) {
|
|
2706
|
+
const normalized = normalizeVaultAction(action);
|
|
2707
|
+
if (normalized === "read" || normalized === "get") {
|
|
2708
|
+
return { bases: ["vault"], action: "tinycloud.kv/get" };
|
|
3175
2709
|
}
|
|
3176
|
-
if (
|
|
3177
|
-
|
|
3178
|
-
`${path}.space must be a non-empty string`
|
|
3179
|
-
);
|
|
2710
|
+
if (normalized === "write" || normalized === "put") {
|
|
2711
|
+
return { bases: ["vault"], action: "tinycloud.kv/put" };
|
|
3180
2712
|
}
|
|
3181
|
-
if (
|
|
3182
|
-
|
|
3183
|
-
`${path}.path is required (use "" or "/" for root)`
|
|
3184
|
-
);
|
|
2713
|
+
if (normalized === "delete" || normalized === "del") {
|
|
2714
|
+
return { bases: ["vault"], action: "tinycloud.kv/del" };
|
|
3185
2715
|
}
|
|
3186
|
-
if (
|
|
3187
|
-
|
|
3188
|
-
`${path}.actions must be a non-empty array`
|
|
3189
|
-
);
|
|
2716
|
+
if (normalized === "list") {
|
|
2717
|
+
return { bases: ["vault"], action: "tinycloud.kv/list" };
|
|
3190
2718
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
throw new ManifestValidationError(
|
|
3194
|
-
`${path}.actions must contain non-empty strings`
|
|
3195
|
-
);
|
|
3196
|
-
}
|
|
3197
|
-
if (entry.service === VAULT_PERMISSION_SERVICE) {
|
|
3198
|
-
vaultActionExpansion(action);
|
|
3199
|
-
}
|
|
2719
|
+
if (normalized === "head") {
|
|
2720
|
+
return { bases: ["vault"], action: "tinycloud.kv/get" };
|
|
3200
2721
|
}
|
|
3201
|
-
if (
|
|
3202
|
-
|
|
2722
|
+
if (normalized === "metadata") {
|
|
2723
|
+
return { bases: ["vault"], action: "tinycloud.kv/metadata" };
|
|
3203
2724
|
}
|
|
2725
|
+
throw new ManifestValidationError(
|
|
2726
|
+
`unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
|
|
2727
|
+
);
|
|
3204
2728
|
}
|
|
3205
|
-
function
|
|
3206
|
-
if (
|
|
3207
|
-
return
|
|
2729
|
+
function normalizeVaultAction(action) {
|
|
2730
|
+
if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
|
|
2731
|
+
return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
|
|
3208
2732
|
}
|
|
3209
|
-
if (
|
|
3210
|
-
return
|
|
2733
|
+
if (action.startsWith("tinycloud.kv/")) {
|
|
2734
|
+
return action.slice("tinycloud.kv/".length);
|
|
3211
2735
|
}
|
|
3212
|
-
if (
|
|
3213
|
-
|
|
2736
|
+
if (action.includes("/")) {
|
|
2737
|
+
throw new ManifestValidationError(
|
|
2738
|
+
`unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
|
|
2739
|
+
);
|
|
3214
2740
|
}
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
2741
|
+
return action;
|
|
2742
|
+
}
|
|
2743
|
+
function vaultKVPath(base, path) {
|
|
2744
|
+
const normalized = path.startsWith("/") ? path.slice(1) : path;
|
|
2745
|
+
return `${base}/${normalized}`;
|
|
2746
|
+
}
|
|
2747
|
+
function cloneResourceCapability(entry) {
|
|
2748
|
+
return {
|
|
2749
|
+
service: entry.service,
|
|
2750
|
+
space: entry.space,
|
|
2751
|
+
path: entry.path,
|
|
2752
|
+
actions: [...entry.actions],
|
|
2753
|
+
...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
|
|
2754
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
function clonePermissionEntry(entry) {
|
|
2758
|
+
return {
|
|
2759
|
+
service: entry.service,
|
|
2760
|
+
...entry.space !== void 0 ? { space: entry.space } : {},
|
|
2761
|
+
path: entry.path,
|
|
2762
|
+
actions: [...entry.actions],
|
|
2763
|
+
...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
|
|
2764
|
+
...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
|
|
2765
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
function dedupeResources(resources) {
|
|
2769
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2770
|
+
for (const resource of resources) {
|
|
2771
|
+
const key = `${resource.service}\0${resource.space}\0${resource.path}\0${resource.expiryMs ?? ""}`;
|
|
2772
|
+
const existing = byKey.get(key);
|
|
2773
|
+
if (existing === void 0) {
|
|
2774
|
+
byKey.set(key, cloneResourceCapability(resource));
|
|
2775
|
+
continue;
|
|
2776
|
+
}
|
|
2777
|
+
const seen = new Set(existing.actions);
|
|
2778
|
+
for (const action of resource.actions) {
|
|
2779
|
+
if (!seen.has(action)) {
|
|
2780
|
+
existing.actions.push(action);
|
|
2781
|
+
seen.add(action);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
if (existing.description === void 0 && resource.description !== void 0) {
|
|
2785
|
+
existing.description = resource.description;
|
|
2786
|
+
}
|
|
3218
2787
|
}
|
|
3219
|
-
return
|
|
2788
|
+
return [...byKey.values()];
|
|
3220
2789
|
}
|
|
3221
|
-
function
|
|
3222
|
-
|
|
2790
|
+
function capabilitiesReadPermission(space) {
|
|
2791
|
+
return {
|
|
2792
|
+
service: "tinycloud.capabilities",
|
|
2793
|
+
space,
|
|
2794
|
+
path: "",
|
|
2795
|
+
actions: ["tinycloud.capabilities/read"]
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
function withCapabilitiesReadForSpaces(resources) {
|
|
2799
|
+
if (resources.length === 0) {
|
|
3223
2800
|
return [];
|
|
3224
2801
|
}
|
|
3225
|
-
const
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
2802
|
+
const spaces = new Set(
|
|
2803
|
+
resources.filter((resource) => resource.service !== ENCRYPTION_PERMISSION_SERVICE).map((resource) => resource.space)
|
|
2804
|
+
);
|
|
2805
|
+
return dedupeResources([
|
|
2806
|
+
...resources,
|
|
2807
|
+
...[...spaces].map(capabilitiesReadPermission)
|
|
2808
|
+
]);
|
|
2809
|
+
}
|
|
2810
|
+
function accountRegistryPermissions() {
|
|
2811
|
+
return [ACCOUNT_REGISTRY_PATH, "spaces/"].map((path) => ({
|
|
2812
|
+
service: "tinycloud.kv",
|
|
2813
|
+
space: ACCOUNT_REGISTRY_SPACE,
|
|
2814
|
+
path,
|
|
2815
|
+
actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
|
|
3232
2816
|
}));
|
|
3233
2817
|
}
|
|
3234
|
-
function
|
|
3235
|
-
const manifest = validateManifest(input);
|
|
3236
|
-
const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.app_id;
|
|
3237
|
-
const space = manifest.space ?? DEFAULT_MANIFEST_SPACE;
|
|
3238
|
-
const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
|
|
3239
|
-
const includePublicSpace = manifest.includePublicSpace ?? true;
|
|
3240
|
-
const tier = normalizeDefaults(manifest.defaults);
|
|
3241
|
-
const defaultEntries = defaultEntriesForTier(tier);
|
|
3242
|
-
const explicitEntries = manifest.permissions ?? [];
|
|
3243
|
-
const secretEntries = secretEntriesForManifest(manifest.secrets);
|
|
3244
|
-
const allEntries = [
|
|
3245
|
-
...defaultEntries,
|
|
3246
|
-
...explicitEntries,
|
|
3247
|
-
...secretEntries
|
|
3248
|
-
];
|
|
3249
|
-
const resources = withCapabilitiesReadForSpaces(
|
|
3250
|
-
allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
|
|
3251
|
-
);
|
|
3252
|
-
const additionalDelegates = manifest.did === void 0 ? [] : [
|
|
3253
|
-
{
|
|
3254
|
-
did: manifest.did,
|
|
3255
|
-
name: manifest.name,
|
|
3256
|
-
expiryMs,
|
|
3257
|
-
permissions: resources.map(cloneResourceCapability)
|
|
3258
|
-
}
|
|
3259
|
-
];
|
|
2818
|
+
function accountRegistryIndexPermission() {
|
|
3260
2819
|
return {
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
expiryMs,
|
|
3266
|
-
includePublicSpace,
|
|
3267
|
-
additionalDelegates
|
|
2820
|
+
service: "tinycloud.sql",
|
|
2821
|
+
space: ACCOUNT_REGISTRY_SPACE,
|
|
2822
|
+
path: "account",
|
|
2823
|
+
actions: ["tinycloud.sql/read", "tinycloud.sql/write", "tinycloud.sql/ddl"]
|
|
3268
2824
|
};
|
|
3269
2825
|
}
|
|
3270
|
-
function
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
2826
|
+
function composeManifestRequest(inputs, options = {}) {
|
|
2827
|
+
if (!Array.isArray(inputs) || inputs.length === 0) {
|
|
2828
|
+
throw new ManifestValidationError(
|
|
2829
|
+
"composeManifestRequest requires at least one manifest"
|
|
2830
|
+
);
|
|
2831
|
+
}
|
|
2832
|
+
const includeAccountRegistryPermissions = options.includeAccountRegistryPermissions ?? true;
|
|
2833
|
+
const manifests = inputs.map(validateManifest);
|
|
2834
|
+
const resolved = manifests.map(resolveManifest);
|
|
2835
|
+
const resources = resolved.flatMap((entry) => entry.resources);
|
|
2836
|
+
const delegationTargets = resolved.flatMap(
|
|
2837
|
+
(entry) => entry.additionalDelegates.map((delegate) => ({
|
|
2838
|
+
...delegate,
|
|
2839
|
+
permissions: dedupeResources(delegate.permissions)
|
|
2840
|
+
}))
|
|
2841
|
+
);
|
|
2842
|
+
if (includeAccountRegistryPermissions) {
|
|
2843
|
+
resources.push(...accountRegistryPermissions());
|
|
2844
|
+
resources.push(accountRegistryIndexPermission());
|
|
2845
|
+
}
|
|
2846
|
+
const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
|
|
2847
|
+
const manifestsByAppId = /* @__PURE__ */ new Map();
|
|
2848
|
+
for (const manifest of manifests) {
|
|
2849
|
+
const current = manifestsByAppId.get(manifest.app_id);
|
|
2850
|
+
if (current === void 0) {
|
|
2851
|
+
manifestsByAppId.set(manifest.app_id, [manifest]);
|
|
2852
|
+
} else {
|
|
2853
|
+
current.push(manifest);
|
|
3277
2854
|
}
|
|
2855
|
+
}
|
|
2856
|
+
const registryRecords = includeAccountRegistryPermissions ? [...manifestsByAppId.entries()].map(([app_id, appManifests]) => ({
|
|
2857
|
+
key: `${ACCOUNT_REGISTRY_PATH}${app_id}`,
|
|
2858
|
+
app_id,
|
|
2859
|
+
manifests: appManifests.map((manifest) => ({
|
|
2860
|
+
...manifest,
|
|
2861
|
+
permissions: manifest.permissions?.map(clonePermissionEntry)
|
|
2862
|
+
}))
|
|
2863
|
+
})) : [];
|
|
2864
|
+
return {
|
|
2865
|
+
manifests,
|
|
2866
|
+
resources: resourcesWithImplicitCapabilities,
|
|
2867
|
+
delegationTargets,
|
|
2868
|
+
registryRecords,
|
|
2869
|
+
expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
|
|
2870
|
+
includePublicSpace: resolved.some((entry) => entry.includePublicSpace)
|
|
3278
2871
|
};
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
if (
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
if (action === "delete") {
|
|
3289
|
-
add("del");
|
|
3290
|
-
continue;
|
|
2872
|
+
}
|
|
2873
|
+
function resourceCapabilitiesToAbilitiesMap(resources) {
|
|
2874
|
+
const out = {};
|
|
2875
|
+
for (const r of resources) {
|
|
2876
|
+
const shortService = SERVICE_LONG_TO_SHORT[r.service];
|
|
2877
|
+
if (shortService === void 0) {
|
|
2878
|
+
throw new ManifestValidationError(
|
|
2879
|
+
`unknown service '${r.service}' \u2014 no short-form mapping. Known services: ${Object.keys(SERVICE_LONG_TO_SHORT).join(", ")}`
|
|
2880
|
+
);
|
|
3291
2881
|
}
|
|
3292
|
-
if (
|
|
3293
|
-
|
|
3294
|
-
continue;
|
|
2882
|
+
if (out[shortService] === void 0) {
|
|
2883
|
+
out[shortService] = {};
|
|
3295
2884
|
}
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
2885
|
+
const pathsMap = out[shortService];
|
|
2886
|
+
const existing = pathsMap[r.path];
|
|
2887
|
+
if (existing === void 0) {
|
|
2888
|
+
pathsMap[r.path] = [...r.actions];
|
|
2889
|
+
} else {
|
|
2890
|
+
const seen = new Set(existing);
|
|
2891
|
+
for (const action of r.actions) {
|
|
2892
|
+
if (!seen.has(action)) {
|
|
2893
|
+
existing.push(action);
|
|
2894
|
+
seen.add(action);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
3299
2897
|
}
|
|
3300
|
-
throw new ManifestValidationError(
|
|
3301
|
-
`unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
|
|
3302
|
-
);
|
|
3303
2898
|
}
|
|
3304
2899
|
return out;
|
|
3305
2900
|
}
|
|
3306
|
-
function
|
|
3307
|
-
|
|
3308
|
-
|
|
2901
|
+
function resourceCapabilitiesToSpaceAbilitiesMap(resources) {
|
|
2902
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2903
|
+
for (const resource of resources) {
|
|
2904
|
+
const entries = grouped.get(resource.space);
|
|
2905
|
+
if (entries === void 0) {
|
|
2906
|
+
grouped.set(resource.space, [resource]);
|
|
2907
|
+
} else {
|
|
2908
|
+
entries.push(resource);
|
|
2909
|
+
}
|
|
3309
2910
|
}
|
|
3310
|
-
|
|
2911
|
+
const out = {};
|
|
2912
|
+
for (const [space, entries] of grouped.entries()) {
|
|
2913
|
+
out[space] = resourceCapabilitiesToAbilitiesMap(entries);
|
|
2914
|
+
}
|
|
2915
|
+
return out;
|
|
3311
2916
|
}
|
|
3312
|
-
function
|
|
3313
|
-
|
|
3314
|
-
|
|
2917
|
+
function manifestAbilitiesUnion(resolved) {
|
|
2918
|
+
const all = [...resolved.resources];
|
|
2919
|
+
for (const delegate of resolved.additionalDelegates) {
|
|
2920
|
+
for (const perm of delegate.permissions) {
|
|
2921
|
+
all.push(perm);
|
|
2922
|
+
}
|
|
3315
2923
|
}
|
|
3316
|
-
return
|
|
2924
|
+
return resourceCapabilitiesToAbilitiesMap(all);
|
|
3317
2925
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
2926
|
+
|
|
2927
|
+
// src/account/AccountService.ts
|
|
2928
|
+
var SERVICE_NAME2 = "account";
|
|
2929
|
+
var ACCOUNT_INDEX_DB = "account";
|
|
2930
|
+
var ACCOUNT_INDEX_NAMESPACE = "tinycloud.account.index";
|
|
2931
|
+
var ACCOUNT_SPACES_PATH = "spaces/";
|
|
2932
|
+
var AccountService = class {
|
|
2933
|
+
constructor(config) {
|
|
2934
|
+
this.config = config;
|
|
2935
|
+
this.applications = {
|
|
2936
|
+
list: async (options = {}) => {
|
|
2937
|
+
if (options.preferIndex) {
|
|
2938
|
+
const indexed = await this.index.applications.list();
|
|
2939
|
+
if (indexed.ok && indexed.data.length > 0) return indexed;
|
|
2940
|
+
if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
|
|
2941
|
+
const canonical = await this.applications.list();
|
|
2942
|
+
if (canonical.ok && options.refreshIndex !== false) {
|
|
2943
|
+
await this.replaceApplicationsIndexQuietly(canonical.data);
|
|
2944
|
+
}
|
|
2945
|
+
return canonical;
|
|
2946
|
+
}
|
|
2947
|
+
const kvResult = this.accountKV();
|
|
2948
|
+
if (!kvResult.ok) return kvResult;
|
|
2949
|
+
const listed = await kvResult.data.list({ prefix: ACCOUNT_REGISTRY_PATH });
|
|
2950
|
+
if (!listed.ok) return accountErr(listed.error);
|
|
2951
|
+
const applications = [];
|
|
2952
|
+
for (const key of listed.data.keys) {
|
|
2953
|
+
const loaded = await kvResult.data.get(key);
|
|
2954
|
+
if (!loaded.ok) return accountErr(loaded.error);
|
|
2955
|
+
applications.push(applicationFromRecord(key, loaded.data.data));
|
|
2956
|
+
}
|
|
2957
|
+
applications.sort((a, b) => a.appId.localeCompare(b.appId));
|
|
2958
|
+
return ok3(applications);
|
|
2959
|
+
},
|
|
2960
|
+
get: async (appId) => {
|
|
2961
|
+
const kvResult = this.accountKV();
|
|
2962
|
+
if (!kvResult.ok) return kvResult;
|
|
2963
|
+
const key = applicationKey(appId);
|
|
2964
|
+
const loaded = await kvResult.data.get(key);
|
|
2965
|
+
if (!loaded.ok) return accountErr(loaded.error);
|
|
2966
|
+
return ok3(applicationFromRecord(key, loaded.data.data));
|
|
2967
|
+
},
|
|
2968
|
+
register: async (manifest) => {
|
|
2969
|
+
const manifests = Array.isArray(manifest) ? manifest : [manifest];
|
|
2970
|
+
const request = composeManifestRequest(manifests);
|
|
2971
|
+
if (request.registryRecords.length === 0) {
|
|
2972
|
+
return err3(
|
|
2973
|
+
serviceError3(
|
|
2974
|
+
"INVALID_MANIFEST",
|
|
2975
|
+
"Manifest did not produce an account application registry record",
|
|
2976
|
+
SERVICE_NAME2
|
|
2977
|
+
)
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
await this.config.ensureAccountSpaceHosted?.();
|
|
2981
|
+
const kvResult = this.accountKV();
|
|
2982
|
+
if (!kvResult.ok) return kvResult;
|
|
2983
|
+
let registered;
|
|
2984
|
+
for (const record of request.registryRecords) {
|
|
2985
|
+
const manifestHash = hashJson(record.manifests);
|
|
2986
|
+
if (await this.indexHasApplicationHash(record.app_id, manifestHash)) {
|
|
2987
|
+
registered = {
|
|
2988
|
+
appId: record.app_id,
|
|
2989
|
+
manifests: record.manifests,
|
|
2990
|
+
manifestHash,
|
|
2991
|
+
name: record.manifests[0]?.name,
|
|
2992
|
+
description: record.manifests[0]?.description
|
|
2993
|
+
};
|
|
2994
|
+
continue;
|
|
2995
|
+
}
|
|
2996
|
+
const stored = {
|
|
2997
|
+
app_id: record.app_id,
|
|
2998
|
+
manifests: record.manifests,
|
|
2999
|
+
manifest_hash: manifestHash,
|
|
3000
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3001
|
+
};
|
|
3002
|
+
const written = await kvResult.data.put(record.key, stored);
|
|
3003
|
+
if (!written.ok) return accountErr(written.error);
|
|
3004
|
+
registered = applicationFromRecord(record.key, stored);
|
|
3005
|
+
await this.upsertApplicationIndexQuietly(registered);
|
|
3006
|
+
}
|
|
3007
|
+
return ok3(registered);
|
|
3008
|
+
},
|
|
3009
|
+
remove: async (appId) => {
|
|
3010
|
+
const kvResult = this.accountKV();
|
|
3011
|
+
if (!kvResult.ok) return kvResult;
|
|
3012
|
+
const removed = await kvResult.data.delete(applicationKey(appId));
|
|
3013
|
+
if (!removed.ok) return accountErr(removed.error);
|
|
3014
|
+
await this.deleteApplicationIndexQuietly(appId);
|
|
3015
|
+
return ok3(void 0);
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
this.spaces = {
|
|
3019
|
+
list: async (options = {}) => {
|
|
3020
|
+
if (options.preferIndex) {
|
|
3021
|
+
const indexed = await this.index.spaces.list();
|
|
3022
|
+
if (indexed.ok && indexed.data.length > 0) return indexed;
|
|
3023
|
+
if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
|
|
3024
|
+
const canonical = await this.spaces.syncAccessible();
|
|
3025
|
+
if (canonical.ok && options.refreshIndex !== false) {
|
|
3026
|
+
await this.replaceSpacesIndexQuietly(canonical.data);
|
|
3027
|
+
}
|
|
3028
|
+
return canonical;
|
|
3029
|
+
}
|
|
3030
|
+
const kvResult = this.accountKV();
|
|
3031
|
+
if (!kvResult.ok) return kvResult;
|
|
3032
|
+
const listed = await kvResult.data.list({ prefix: ACCOUNT_SPACES_PATH });
|
|
3033
|
+
if (!listed.ok) return accountErr(listed.error);
|
|
3034
|
+
const spaces = [];
|
|
3035
|
+
for (const key of listed.data.keys) {
|
|
3036
|
+
const loaded = await kvResult.data.get(key);
|
|
3037
|
+
if (!loaded.ok) return accountErr(loaded.error);
|
|
3038
|
+
spaces.push(spaceFromRecord(key, loaded.data.data));
|
|
3039
|
+
}
|
|
3040
|
+
spaces.sort((a, b) => a.name.localeCompare(b.name) || a.spaceId.localeCompare(b.spaceId));
|
|
3041
|
+
return ok3(spaces);
|
|
3042
|
+
},
|
|
3043
|
+
get: async (spaceId) => {
|
|
3044
|
+
const kvResult = this.accountKV();
|
|
3045
|
+
if (!kvResult.ok) return kvResult;
|
|
3046
|
+
const loaded = await kvResult.data.get(spaceKey(spaceId));
|
|
3047
|
+
if (!loaded.ok) return accountErr(loaded.error);
|
|
3048
|
+
return ok3(spaceFromRecord(spaceKey(spaceId), loaded.data.data));
|
|
3049
|
+
},
|
|
3050
|
+
register: async (space) => {
|
|
3051
|
+
await this.config.ensureAccountSpaceHosted?.();
|
|
3052
|
+
const kvResult = this.accountKV();
|
|
3053
|
+
if (!kvResult.ok) return kvResult;
|
|
3054
|
+
const stored = spaceRecordFromInput(space);
|
|
3055
|
+
const written = await kvResult.data.put(spaceKey(stored.space_id), stored);
|
|
3056
|
+
if (!written.ok) return accountErr(written.error);
|
|
3057
|
+
const registered = spaceFromRecord(spaceKey(stored.space_id), stored);
|
|
3058
|
+
await this.upsertSpaceIndexQuietly(registered);
|
|
3059
|
+
return ok3(registered);
|
|
3060
|
+
},
|
|
3061
|
+
syncAccessible: async () => {
|
|
3062
|
+
const listed = await this.config.getSpaces().list();
|
|
3063
|
+
if (!listed.ok) return accountErr(listed.error);
|
|
3064
|
+
const registered = [];
|
|
3065
|
+
for (const space of listed.data) {
|
|
3066
|
+
const result = await this.spaces.register(space);
|
|
3067
|
+
if (!result.ok) return result;
|
|
3068
|
+
registered.push(result.data);
|
|
3069
|
+
}
|
|
3070
|
+
return ok3(registered);
|
|
3071
|
+
},
|
|
3072
|
+
remove: async (spaceId) => {
|
|
3073
|
+
const kvResult = this.accountKV();
|
|
3074
|
+
if (!kvResult.ok) return kvResult;
|
|
3075
|
+
const removed = await kvResult.data.delete(spaceKey(spaceId));
|
|
3076
|
+
if (!removed.ok) return accountErr(removed.error);
|
|
3077
|
+
await this.deleteSpaceIndexQuietly(spaceId);
|
|
3078
|
+
return ok3(void 0);
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
this.delegations = {
|
|
3082
|
+
list: async (options = {}) => {
|
|
3083
|
+
if (options.preferIndex) {
|
|
3084
|
+
const indexed = await this.index.delegations.list(options);
|
|
3085
|
+
if (indexed.ok && indexed.data.length > 0) return indexed;
|
|
3086
|
+
if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
|
|
3087
|
+
const live = await this.delegations.list({
|
|
3088
|
+
direction: options.direction,
|
|
3089
|
+
space: options.space
|
|
3090
|
+
});
|
|
3091
|
+
if (live.ok && options.refreshIndex !== false) {
|
|
3092
|
+
await this.replaceDelegationsIndexQuietly(live.data);
|
|
3093
|
+
}
|
|
3094
|
+
return live;
|
|
3095
|
+
}
|
|
3096
|
+
const spaces = await this.config.getSpaces().list();
|
|
3097
|
+
if (!spaces.ok) return accountErr(spaces.error);
|
|
3098
|
+
const targetSpaces = options.space ? spaces.data.filter((space) => space.id === options.space || space.name === options.space) : spaces.data;
|
|
3099
|
+
const delegations = [];
|
|
3100
|
+
for (const space of targetSpaces) {
|
|
3101
|
+
const scoped = this.config.getSpaces().get(space.id).delegations;
|
|
3102
|
+
if (options.direction !== "received") {
|
|
3103
|
+
const granted = await scoped.list();
|
|
3104
|
+
if (!granted.ok) return accountErr(granted.error);
|
|
3105
|
+
delegations.push(...granted.data.map((d) => mapDelegation(d, space, "granted")));
|
|
3106
|
+
}
|
|
3107
|
+
if (options.direction !== "granted") {
|
|
3108
|
+
const received = await scoped.listReceived();
|
|
3109
|
+
if (!received.ok) return accountErr(received.error);
|
|
3110
|
+
delegations.push(...received.data.map((d) => mapDelegation(d, space, "received")));
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
delegations.sort((a, b) => a.spaceId.localeCompare(b.spaceId) || a.cid.localeCompare(b.cid));
|
|
3114
|
+
return ok3(delegations);
|
|
3115
|
+
},
|
|
3116
|
+
revoke: async (options) => {
|
|
3117
|
+
const space = await this.resolveSpace(options.space);
|
|
3118
|
+
if (!space.ok) return space;
|
|
3119
|
+
const revoked = await this.config.getSpaces().get(space.data.id).delegations.revoke(options.cid);
|
|
3120
|
+
if (!revoked.ok) return accountErr(revoked.error);
|
|
3121
|
+
return ok3(void 0);
|
|
3122
|
+
}
|
|
3123
|
+
};
|
|
3124
|
+
this.index = {
|
|
3125
|
+
rebuild: async () => {
|
|
3126
|
+
const dbResult = this.accountDb();
|
|
3127
|
+
if (!dbResult.ok) return dbResult;
|
|
3128
|
+
const applications = await this.applications.list();
|
|
3129
|
+
if (!applications.ok) return applications;
|
|
3130
|
+
const spaces = await this.spaces.list();
|
|
3131
|
+
if (!spaces.ok) return spaces;
|
|
3132
|
+
const delegations = await this.delegations.list();
|
|
3133
|
+
if (!delegations.ok) return delegations;
|
|
3134
|
+
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3135
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3136
|
+
if (!schema.ok) return schema;
|
|
3137
|
+
const statements = [
|
|
3138
|
+
{ sql: "DELETE FROM applications" },
|
|
3139
|
+
{ sql: "DELETE FROM application_state" },
|
|
3140
|
+
{ sql: "DELETE FROM spaces" },
|
|
3141
|
+
{ sql: "DELETE FROM delegations" },
|
|
3142
|
+
{ sql: "DELETE FROM sync_state" },
|
|
3143
|
+
...applications.data.map((app) => ({
|
|
3144
|
+
sql: "INSERT INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
|
|
3145
|
+
params: [
|
|
3146
|
+
app.appId,
|
|
3147
|
+
app.name ?? null,
|
|
3148
|
+
app.description ?? null,
|
|
3149
|
+
app.updatedAt ?? syncedAt,
|
|
3150
|
+
JSON.stringify(app.manifests)
|
|
3151
|
+
]
|
|
3152
|
+
})),
|
|
3153
|
+
...applications.data.map((app) => ({
|
|
3154
|
+
sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
|
|
3155
|
+
params: [app.appId, app.manifestHash ?? hashJson(app.manifests), syncedAt]
|
|
3156
|
+
})),
|
|
3157
|
+
...spaces.data.map((space) => ({
|
|
3158
|
+
sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
3159
|
+
params: [
|
|
3160
|
+
space.spaceId,
|
|
3161
|
+
space.name,
|
|
3162
|
+
space.ownerDid,
|
|
3163
|
+
space.type,
|
|
3164
|
+
JSON.stringify(space.permissions),
|
|
3165
|
+
space.status,
|
|
3166
|
+
space.registeredAt ?? syncedAt,
|
|
3167
|
+
space.updatedAt ?? syncedAt,
|
|
3168
|
+
space.expiresAt?.toISOString() ?? null
|
|
3169
|
+
]
|
|
3170
|
+
})),
|
|
3171
|
+
...delegations.data.map((delegation) => ({
|
|
3172
|
+
sql: "INSERT INTO delegations (cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
3173
|
+
params: [
|
|
3174
|
+
delegation.cid,
|
|
3175
|
+
delegation.direction,
|
|
3176
|
+
delegation.spaceId,
|
|
3177
|
+
delegation.spaceName ?? null,
|
|
3178
|
+
delegation.counterpartyDid,
|
|
3179
|
+
delegation.delegateDid,
|
|
3180
|
+
delegation.delegatorDid ?? null,
|
|
3181
|
+
delegation.path,
|
|
3182
|
+
JSON.stringify(delegation.actions),
|
|
3183
|
+
delegation.expiry.toISOString(),
|
|
3184
|
+
delegation.status,
|
|
3185
|
+
delegation.createdAt?.toISOString() ?? null,
|
|
3186
|
+
syncedAt
|
|
3187
|
+
]
|
|
3188
|
+
})),
|
|
3189
|
+
{
|
|
3190
|
+
sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
|
|
3191
|
+
params: ["applications", syncedAt, applications.data.length]
|
|
3192
|
+
},
|
|
3193
|
+
{
|
|
3194
|
+
sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
|
|
3195
|
+
params: ["spaces", syncedAt, spaces.data.length]
|
|
3196
|
+
},
|
|
3197
|
+
{
|
|
3198
|
+
sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
|
|
3199
|
+
params: ["delegations", syncedAt, delegations.data.length]
|
|
3200
|
+
}
|
|
3201
|
+
];
|
|
3202
|
+
const rebuilt = await dbResult.data.batch(statements);
|
|
3203
|
+
if (!rebuilt.ok) return accountErr(rebuilt.error);
|
|
3204
|
+
return ok3({
|
|
3205
|
+
database: ACCOUNT_INDEX_DB,
|
|
3206
|
+
applications: applications.data.length,
|
|
3207
|
+
spaces: spaces.data.length,
|
|
3208
|
+
delegations: delegations.data.length,
|
|
3209
|
+
syncedAt
|
|
3210
|
+
});
|
|
3211
|
+
},
|
|
3212
|
+
applications: {
|
|
3213
|
+
list: async () => {
|
|
3214
|
+
const dbResult = this.accountDb();
|
|
3215
|
+
if (!dbResult.ok) return dbResult;
|
|
3216
|
+
const queried = await dbResult.data.query(
|
|
3217
|
+
"SELECT applications.app_id, name, description, updated_at, manifest_json, application_state.manifest_hash FROM applications LEFT JOIN application_state ON applications.app_id = application_state.app_id ORDER BY applications.app_id"
|
|
3218
|
+
);
|
|
3219
|
+
if (!queried.ok) return accountErr(queried.error);
|
|
3220
|
+
return ok3(queried.data.rows.map(indexedApplicationFromRow));
|
|
3221
|
+
}
|
|
3222
|
+
},
|
|
3223
|
+
spaces: {
|
|
3224
|
+
list: async () => {
|
|
3225
|
+
const dbResult = this.accountDb();
|
|
3226
|
+
if (!dbResult.ok) return dbResult;
|
|
3227
|
+
const queried = await dbResult.data.query(
|
|
3228
|
+
"SELECT space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at FROM spaces ORDER BY name, space_id"
|
|
3229
|
+
);
|
|
3230
|
+
if (!queried.ok) return accountErr(queried.error);
|
|
3231
|
+
return ok3(queried.data.rows.map(indexedSpaceFromRow));
|
|
3232
|
+
}
|
|
3233
|
+
},
|
|
3234
|
+
delegations: {
|
|
3235
|
+
list: async (options = {}) => {
|
|
3236
|
+
const dbResult = this.accountDb();
|
|
3237
|
+
if (!dbResult.ok) return dbResult;
|
|
3238
|
+
const where = [];
|
|
3239
|
+
const params = [];
|
|
3240
|
+
if (options.direction && options.direction !== "all") {
|
|
3241
|
+
where.push("direction = ?");
|
|
3242
|
+
params.push(options.direction);
|
|
3243
|
+
}
|
|
3244
|
+
if (options.space) {
|
|
3245
|
+
where.push("(space_id = ? OR space_name = ?)");
|
|
3246
|
+
params.push(options.space, options.space);
|
|
3247
|
+
}
|
|
3248
|
+
const queried = await dbResult.data.query(
|
|
3249
|
+
`SELECT cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at FROM delegations${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY space_id, cid`,
|
|
3250
|
+
params
|
|
3251
|
+
);
|
|
3252
|
+
if (!queried.ok) return accountErr(queried.error);
|
|
3253
|
+
return ok3(queried.data.rows.map(indexedDelegationFromRow));
|
|
3254
|
+
}
|
|
3255
|
+
},
|
|
3256
|
+
query: async (sql, params) => {
|
|
3257
|
+
const dbResult = this.accountDb();
|
|
3258
|
+
if (!dbResult.ok) return dbResult;
|
|
3259
|
+
const queried = await dbResult.data.query(sql, params);
|
|
3260
|
+
if (!queried.ok) return accountErr(queried.error);
|
|
3261
|
+
return ok3(queried.data);
|
|
3262
|
+
},
|
|
3263
|
+
status: async () => {
|
|
3264
|
+
const dbResult = this.accountDb();
|
|
3265
|
+
if (!dbResult.ok) return dbResult;
|
|
3266
|
+
const queried = await dbResult.data.query(
|
|
3267
|
+
"SELECT source, synced_at, count FROM sync_state ORDER BY source"
|
|
3268
|
+
);
|
|
3269
|
+
if (!queried.ok) {
|
|
3270
|
+
if (isMissingIndexError(queried.error)) {
|
|
3271
|
+
return ok3({ database: ACCOUNT_INDEX_DB, state: "missing", sources: [] });
|
|
3272
|
+
}
|
|
3273
|
+
return accountErr(queried.error);
|
|
3274
|
+
}
|
|
3275
|
+
return ok3({
|
|
3276
|
+
database: ACCOUNT_INDEX_DB,
|
|
3277
|
+
state: "ready",
|
|
3278
|
+
sources: queried.data.rows.map(([source, syncedAt, count]) => ({
|
|
3279
|
+
source,
|
|
3280
|
+
syncedAt,
|
|
3281
|
+
count
|
|
3282
|
+
}))
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
}
|
|
3287
|
+
async status() {
|
|
3288
|
+
const apps = await this.applications.list();
|
|
3289
|
+
if (!apps.ok) return apps;
|
|
3290
|
+
const delegations = await this.delegations.list();
|
|
3291
|
+
if (!delegations.ok) return delegations;
|
|
3292
|
+
const spaces = await this.spaces.list();
|
|
3293
|
+
if (!spaces.ok) return spaces;
|
|
3294
|
+
return ok3({
|
|
3295
|
+
did: this.config.getDid(),
|
|
3296
|
+
host: this.config.getHost(),
|
|
3297
|
+
primarySpaceId: this.config.getPrimarySpaceId(),
|
|
3298
|
+
accountSpaceId: this.config.getAccountSpaceId(),
|
|
3299
|
+
applications: apps.data.length,
|
|
3300
|
+
spaces: spaces.data.length,
|
|
3301
|
+
grantedDelegations: delegations.data.filter((d) => d.direction === "granted").length,
|
|
3302
|
+
receivedDelegations: delegations.data.filter((d) => d.direction === "received").length
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
accountKV() {
|
|
3306
|
+
const accountSpaceId = this.config.getAccountSpaceId();
|
|
3307
|
+
if (!accountSpaceId) {
|
|
3308
|
+
return err3(
|
|
3309
|
+
serviceError3(
|
|
3310
|
+
"ACCOUNT_SPACE_UNAVAILABLE",
|
|
3311
|
+
"Account space is unavailable. Sign in with a wallet-backed profile first.",
|
|
3312
|
+
SERVICE_NAME2
|
|
3313
|
+
)
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3316
|
+
return ok3(this.config.getSpaces().get(accountSpaceId).kv);
|
|
3317
|
+
}
|
|
3318
|
+
accountDb() {
|
|
3319
|
+
const db = this.config.getAccountDb?.();
|
|
3320
|
+
if (!db) {
|
|
3321
|
+
return err3(
|
|
3322
|
+
serviceError3(
|
|
3323
|
+
"ACCOUNT_INDEX_UNAVAILABLE",
|
|
3324
|
+
"Account index database is unavailable. Sign in with a wallet-backed profile first.",
|
|
3325
|
+
SERVICE_NAME2
|
|
3326
|
+
)
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
return ok3(db);
|
|
3321
3330
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
3331
|
+
async indexHasApplicationHash(appId, manifestHash) {
|
|
3332
|
+
const dbResult = this.accountDb();
|
|
3333
|
+
if (!dbResult.ok) return false;
|
|
3334
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3335
|
+
if (!schema.ok) return false;
|
|
3336
|
+
const queried = await dbResult.data.query(
|
|
3337
|
+
"SELECT 1 FROM application_state WHERE app_id = ? AND manifest_hash = ? LIMIT 1",
|
|
3338
|
+
[appId, manifestHash]
|
|
3339
|
+
);
|
|
3340
|
+
return queried.ok && queried.data.rows.length > 0;
|
|
3341
|
+
}
|
|
3342
|
+
async upsertApplicationIndexQuietly(app) {
|
|
3343
|
+
await ignoreIndexFailure(() => this.upsertApplicationIndex(app));
|
|
3344
|
+
}
|
|
3345
|
+
async upsertApplicationIndex(app) {
|
|
3346
|
+
const dbResult = this.accountDb();
|
|
3347
|
+
if (!dbResult.ok) return ok3(void 0);
|
|
3348
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3349
|
+
if (!schema.ok) return schema;
|
|
3350
|
+
const updatedAt = app.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3351
|
+
const manifestHash = app.manifestHash ?? hashJson(app.manifests);
|
|
3352
|
+
const written = await dbResult.data.batch([
|
|
3353
|
+
{
|
|
3354
|
+
sql: "INSERT OR REPLACE INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
|
|
3355
|
+
params: [
|
|
3356
|
+
app.appId,
|
|
3357
|
+
app.name ?? null,
|
|
3358
|
+
app.description ?? null,
|
|
3359
|
+
updatedAt,
|
|
3360
|
+
JSON.stringify(app.manifests)
|
|
3361
|
+
]
|
|
3362
|
+
},
|
|
3363
|
+
{
|
|
3364
|
+
sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
|
|
3365
|
+
params: [app.appId, manifestHash, updatedAt]
|
|
3366
|
+
}
|
|
3367
|
+
]);
|
|
3368
|
+
if (!written.ok) return accountErr(written.error);
|
|
3369
|
+
return ok3(void 0);
|
|
3324
3370
|
}
|
|
3325
|
-
|
|
3326
|
-
|
|
3371
|
+
async deleteApplicationIndexQuietly(appId) {
|
|
3372
|
+
await ignoreIndexFailure(() => this.deleteApplicationIndex(appId));
|
|
3373
|
+
}
|
|
3374
|
+
async deleteApplicationIndex(appId) {
|
|
3375
|
+
const dbResult = this.accountDb();
|
|
3376
|
+
if (!dbResult.ok) return ok3(void 0);
|
|
3377
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3378
|
+
if (!schema.ok) return schema;
|
|
3379
|
+
const deleted = await dbResult.data.batch([
|
|
3380
|
+
{ sql: "DELETE FROM applications WHERE app_id = ?", params: [appId] },
|
|
3381
|
+
{ sql: "DELETE FROM application_state WHERE app_id = ?", params: [appId] }
|
|
3382
|
+
]);
|
|
3383
|
+
if (!deleted.ok) return accountErr(deleted.error);
|
|
3384
|
+
return ok3(void 0);
|
|
3327
3385
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
`manifest.secrets.${name} must be true, a string action, an actions array, or an object`
|
|
3331
|
-
);
|
|
3386
|
+
async upsertSpaceIndexQuietly(space) {
|
|
3387
|
+
await ignoreIndexFailure(() => this.upsertSpaceIndex(space));
|
|
3332
3388
|
}
|
|
3333
|
-
|
|
3334
|
-
|
|
3389
|
+
async upsertSpaceIndex(space) {
|
|
3390
|
+
const dbResult = this.accountDb();
|
|
3391
|
+
if (!dbResult.ok) return ok3(void 0);
|
|
3392
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3393
|
+
if (!schema.ok) return schema;
|
|
3394
|
+
const updatedAt = space.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3395
|
+
const written = await dbResult.data.batch([
|
|
3396
|
+
{
|
|
3397
|
+
sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
3398
|
+
params: [
|
|
3399
|
+
space.spaceId,
|
|
3400
|
+
space.name,
|
|
3401
|
+
space.ownerDid,
|
|
3402
|
+
space.type,
|
|
3403
|
+
JSON.stringify(space.permissions),
|
|
3404
|
+
space.status,
|
|
3405
|
+
space.registeredAt ?? updatedAt,
|
|
3406
|
+
updatedAt,
|
|
3407
|
+
space.expiresAt?.toISOString() ?? null
|
|
3408
|
+
]
|
|
3409
|
+
}
|
|
3410
|
+
]);
|
|
3411
|
+
if (!written.ok) return accountErr(written.error);
|
|
3412
|
+
return ok3(void 0);
|
|
3335
3413
|
}
|
|
3336
|
-
|
|
3337
|
-
|
|
3414
|
+
async deleteSpaceIndexQuietly(spaceId) {
|
|
3415
|
+
await ignoreIndexFailure(() => this.deleteSpaceIndex(spaceId));
|
|
3416
|
+
}
|
|
3417
|
+
async deleteSpaceIndex(spaceId) {
|
|
3418
|
+
const dbResult = this.accountDb();
|
|
3419
|
+
if (!dbResult.ok) return ok3(void 0);
|
|
3420
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3421
|
+
if (!schema.ok) return schema;
|
|
3422
|
+
const deleted = await dbResult.data.batch([
|
|
3423
|
+
{ sql: "DELETE FROM spaces WHERE space_id = ?", params: [spaceId] }
|
|
3424
|
+
]);
|
|
3425
|
+
if (!deleted.ok) return accountErr(deleted.error);
|
|
3426
|
+
return ok3(void 0);
|
|
3338
3427
|
}
|
|
3339
|
-
|
|
3340
|
-
|
|
3428
|
+
async resolveSpace(space) {
|
|
3429
|
+
const listed = await this.config.getSpaces().list();
|
|
3430
|
+
if (!listed.ok) return accountErr(listed.error);
|
|
3431
|
+
const found = listed.data.find((candidate) => candidate.id === space || candidate.name === space);
|
|
3432
|
+
if (!found) {
|
|
3433
|
+
return err3(
|
|
3434
|
+
serviceError3("SPACE_NOT_FOUND", `No account space found for ${JSON.stringify(space)}`, SERVICE_NAME2)
|
|
3435
|
+
);
|
|
3436
|
+
}
|
|
3437
|
+
return ok3(found);
|
|
3438
|
+
}
|
|
3439
|
+
async replaceApplicationsIndexQuietly(applications) {
|
|
3440
|
+
await ignoreIndexFailure(async () => {
|
|
3441
|
+
const dbResult = this.accountDb();
|
|
3442
|
+
if (!dbResult.ok) return;
|
|
3443
|
+
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3444
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3445
|
+
if (!schema.ok) return;
|
|
3446
|
+
await dbResult.data.batch([
|
|
3447
|
+
{ sql: "DELETE FROM applications" },
|
|
3448
|
+
{ sql: "DELETE FROM application_state" },
|
|
3449
|
+
...applications.map((app) => ({
|
|
3450
|
+
sql: "INSERT OR REPLACE INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
|
|
3451
|
+
params: [
|
|
3452
|
+
app.appId,
|
|
3453
|
+
app.name ?? null,
|
|
3454
|
+
app.description ?? null,
|
|
3455
|
+
app.updatedAt ?? syncedAt,
|
|
3456
|
+
JSON.stringify(app.manifests)
|
|
3457
|
+
]
|
|
3458
|
+
})),
|
|
3459
|
+
...applications.map((app) => ({
|
|
3460
|
+
sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
|
|
3461
|
+
params: [app.appId, app.manifestHash ?? hashJson(app.manifests), syncedAt]
|
|
3462
|
+
})),
|
|
3463
|
+
{
|
|
3464
|
+
sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
|
|
3465
|
+
params: ["applications", syncedAt, applications.length]
|
|
3466
|
+
}
|
|
3467
|
+
]);
|
|
3468
|
+
});
|
|
3341
3469
|
}
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3470
|
+
async replaceSpacesIndexQuietly(spaces) {
|
|
3471
|
+
await ignoreIndexFailure(async () => {
|
|
3472
|
+
const dbResult = this.accountDb();
|
|
3473
|
+
if (!dbResult.ok) return;
|
|
3474
|
+
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3475
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3476
|
+
if (!schema.ok) return;
|
|
3477
|
+
await dbResult.data.batch([
|
|
3478
|
+
{ sql: "DELETE FROM spaces" },
|
|
3479
|
+
...spaces.map((space) => ({
|
|
3480
|
+
sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
3481
|
+
params: [
|
|
3482
|
+
space.spaceId,
|
|
3483
|
+
space.name,
|
|
3484
|
+
space.ownerDid,
|
|
3485
|
+
space.type,
|
|
3486
|
+
JSON.stringify(space.permissions),
|
|
3487
|
+
space.status,
|
|
3488
|
+
space.registeredAt ?? syncedAt,
|
|
3489
|
+
space.updatedAt ?? syncedAt,
|
|
3490
|
+
space.expiresAt?.toISOString() ?? null
|
|
3491
|
+
]
|
|
3492
|
+
})),
|
|
3493
|
+
{
|
|
3494
|
+
sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
|
|
3495
|
+
params: ["spaces", syncedAt, spaces.length]
|
|
3496
|
+
}
|
|
3497
|
+
]);
|
|
3498
|
+
});
|
|
3349
3499
|
}
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3500
|
+
async replaceDelegationsIndexQuietly(delegations) {
|
|
3501
|
+
await ignoreIndexFailure(async () => {
|
|
3502
|
+
const dbResult = this.accountDb();
|
|
3503
|
+
if (!dbResult.ok) return;
|
|
3504
|
+
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3505
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
3506
|
+
if (!schema.ok) return;
|
|
3507
|
+
await dbResult.data.batch([
|
|
3508
|
+
{ sql: "DELETE FROM delegations" },
|
|
3509
|
+
...delegations.map((delegation) => ({
|
|
3510
|
+
sql: "INSERT OR REPLACE INTO delegations (cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
3511
|
+
params: [
|
|
3512
|
+
delegation.cid,
|
|
3513
|
+
delegation.direction,
|
|
3514
|
+
delegation.spaceId,
|
|
3515
|
+
delegation.spaceName ?? null,
|
|
3516
|
+
delegation.counterpartyDid,
|
|
3517
|
+
delegation.delegateDid,
|
|
3518
|
+
delegation.delegatorDid ?? null,
|
|
3519
|
+
delegation.path,
|
|
3520
|
+
JSON.stringify(delegation.actions),
|
|
3521
|
+
delegation.expiry.toISOString(),
|
|
3522
|
+
delegation.status,
|
|
3523
|
+
delegation.createdAt?.toISOString() ?? null,
|
|
3524
|
+
syncedAt
|
|
3525
|
+
]
|
|
3526
|
+
})),
|
|
3527
|
+
{
|
|
3528
|
+
sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
|
|
3529
|
+
params: ["delegations", syncedAt, delegations.length]
|
|
3530
|
+
}
|
|
3531
|
+
]);
|
|
3366
3532
|
});
|
|
3367
3533
|
}
|
|
3368
|
-
|
|
3534
|
+
async ensureAccountIndex(db) {
|
|
3535
|
+
const migrated = await db.migrations.apply({
|
|
3536
|
+
namespace: ACCOUNT_INDEX_NAMESPACE,
|
|
3537
|
+
migrations: [
|
|
3538
|
+
{
|
|
3539
|
+
id: "001_initial",
|
|
3540
|
+
sql: ACCOUNT_INDEX_SCHEMA
|
|
3541
|
+
}
|
|
3542
|
+
]
|
|
3543
|
+
});
|
|
3544
|
+
if (!migrated.ok) return accountErr(migrated.error);
|
|
3545
|
+
return ok3(void 0);
|
|
3546
|
+
}
|
|
3547
|
+
};
|
|
3548
|
+
var ACCOUNT_INDEX_SCHEMA = [
|
|
3549
|
+
`CREATE TABLE IF NOT EXISTS applications (
|
|
3550
|
+
app_id TEXT PRIMARY KEY,
|
|
3551
|
+
name TEXT,
|
|
3552
|
+
description TEXT,
|
|
3553
|
+
updated_at TEXT,
|
|
3554
|
+
manifest_json TEXT NOT NULL
|
|
3555
|
+
)`,
|
|
3556
|
+
`CREATE TABLE IF NOT EXISTS application_state (
|
|
3557
|
+
app_id TEXT PRIMARY KEY,
|
|
3558
|
+
manifest_hash TEXT NOT NULL,
|
|
3559
|
+
indexed_at TEXT NOT NULL
|
|
3560
|
+
)`,
|
|
3561
|
+
`CREATE TABLE IF NOT EXISTS spaces (
|
|
3562
|
+
space_id TEXT PRIMARY KEY,
|
|
3563
|
+
name TEXT NOT NULL,
|
|
3564
|
+
owner_did TEXT NOT NULL,
|
|
3565
|
+
type TEXT NOT NULL,
|
|
3566
|
+
permissions_json TEXT NOT NULL,
|
|
3567
|
+
status TEXT NOT NULL,
|
|
3568
|
+
registered_at TEXT,
|
|
3569
|
+
updated_at TEXT NOT NULL,
|
|
3570
|
+
expires_at TEXT
|
|
3571
|
+
)`,
|
|
3572
|
+
`CREATE TABLE IF NOT EXISTS delegations (
|
|
3573
|
+
cid TEXT PRIMARY KEY,
|
|
3574
|
+
direction TEXT NOT NULL,
|
|
3575
|
+
space_id TEXT NOT NULL,
|
|
3576
|
+
space_name TEXT,
|
|
3577
|
+
counterparty_did TEXT NOT NULL,
|
|
3578
|
+
delegate_did TEXT NOT NULL,
|
|
3579
|
+
delegator_did TEXT,
|
|
3580
|
+
path TEXT NOT NULL,
|
|
3581
|
+
actions_json TEXT NOT NULL,
|
|
3582
|
+
expiry TEXT NOT NULL,
|
|
3583
|
+
status TEXT NOT NULL,
|
|
3584
|
+
created_at TEXT,
|
|
3585
|
+
updated_at TEXT NOT NULL
|
|
3586
|
+
)`,
|
|
3587
|
+
`CREATE TABLE IF NOT EXISTS sync_state (
|
|
3588
|
+
source TEXT PRIMARY KEY,
|
|
3589
|
+
synced_at TEXT NOT NULL,
|
|
3590
|
+
count INTEGER NOT NULL
|
|
3591
|
+
)`,
|
|
3592
|
+
"CREATE INDEX IF NOT EXISTS idx_delegations_direction ON delegations(direction)",
|
|
3593
|
+
"CREATE INDEX IF NOT EXISTS idx_delegations_space ON delegations(space_id)",
|
|
3594
|
+
"CREATE INDEX IF NOT EXISTS idx_delegations_counterparty ON delegations(counterparty_did)",
|
|
3595
|
+
"CREATE INDEX IF NOT EXISTS idx_spaces_owner ON spaces(owner_did)",
|
|
3596
|
+
"CREATE INDEX IF NOT EXISTS idx_spaces_type ON spaces(type)"
|
|
3597
|
+
];
|
|
3598
|
+
function applicationKey(appId) {
|
|
3599
|
+
return `${ACCOUNT_REGISTRY_PATH}${appId}`;
|
|
3369
3600
|
}
|
|
3370
|
-
function
|
|
3371
|
-
|
|
3372
|
-
const resolvedPath = applyPrefix(prefix, entry.path, skipPrefixForEntry);
|
|
3373
|
-
const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
|
|
3374
|
-
return expandPermissionEntry({
|
|
3375
|
-
...entry,
|
|
3376
|
-
space: entry.space ?? inheritedSpace,
|
|
3377
|
-
path: resolvedPath,
|
|
3378
|
-
skipPrefix: true
|
|
3379
|
-
}).map((expanded) => ({
|
|
3380
|
-
service: expanded.service,
|
|
3381
|
-
space: expanded.space ?? inheritedSpace,
|
|
3382
|
-
path: expanded.path,
|
|
3383
|
-
actions: expanded.actions,
|
|
3384
|
-
// Only populate `expiryMs` when the entry had its own expiry override.
|
|
3385
|
-
// When absent, callers use the parent (delegation or manifest) expiry
|
|
3386
|
-
// which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
|
|
3387
|
-
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
|
|
3388
|
-
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
3389
|
-
}));
|
|
3601
|
+
function appIdFromKey(key) {
|
|
3602
|
+
return key.startsWith(ACCOUNT_REGISTRY_PATH) ? key.slice(ACCOUNT_REGISTRY_PATH.length) : key;
|
|
3390
3603
|
}
|
|
3391
|
-
function
|
|
3392
|
-
const
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
}
|
|
3403
|
-
return [...byBase.entries()].map(([base, actions]) => ({
|
|
3404
|
-
...entry,
|
|
3405
|
-
service: "tinycloud.kv",
|
|
3406
|
-
path: vaultKVPath(base, entry.path),
|
|
3407
|
-
actions,
|
|
3408
|
-
skipPrefix: true
|
|
3409
|
-
}));
|
|
3604
|
+
function applicationFromRecord(key, record) {
|
|
3605
|
+
const manifests = Array.isArray(record.manifests) ? record.manifests : [];
|
|
3606
|
+
const first = manifests[0];
|
|
3607
|
+
return {
|
|
3608
|
+
appId: record.app_id ?? record.appId ?? first?.app_id ?? appIdFromKey(key),
|
|
3609
|
+
manifests,
|
|
3610
|
+
updatedAt: record.updated_at ?? record.updatedAt,
|
|
3611
|
+
name: first?.name,
|
|
3612
|
+
description: first?.description,
|
|
3613
|
+
manifestHash: record.manifest_hash ?? record.manifestHash ?? hashJson(manifests)
|
|
3614
|
+
};
|
|
3410
3615
|
}
|
|
3411
|
-
function
|
|
3412
|
-
const
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3616
|
+
function indexedApplicationFromRow(row) {
|
|
3617
|
+
const [appId, name, description, updatedAt, manifestJson, manifestHash] = row;
|
|
3618
|
+
return {
|
|
3619
|
+
appId,
|
|
3620
|
+
name: name ?? void 0,
|
|
3621
|
+
description: description ?? void 0,
|
|
3622
|
+
updatedAt: updatedAt ?? void 0,
|
|
3623
|
+
manifests: JSON.parse(manifestJson),
|
|
3624
|
+
manifestHash: manifestHash ?? void 0
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
function spaceKey(spaceId) {
|
|
3628
|
+
return `${ACCOUNT_SPACES_PATH}${spaceId}`;
|
|
3629
|
+
}
|
|
3630
|
+
function spaceIdFromKey(key) {
|
|
3631
|
+
return key.startsWith(ACCOUNT_SPACES_PATH) ? key.slice(ACCOUNT_SPACES_PATH.length) : key;
|
|
3632
|
+
}
|
|
3633
|
+
function spaceRecordFromInput(space) {
|
|
3634
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3635
|
+
const accountSpace = "spaceId" in space ? space : {
|
|
3636
|
+
spaceId: space.id,
|
|
3637
|
+
name: space.name ?? space.id.split(":").pop() ?? space.id,
|
|
3638
|
+
ownerDid: space.owner ?? "",
|
|
3639
|
+
type: space.type ?? "discovered",
|
|
3640
|
+
permissions: space.permissions ?? [],
|
|
3641
|
+
status: "active",
|
|
3642
|
+
expiresAt: space.expiresAt
|
|
3643
|
+
};
|
|
3644
|
+
return {
|
|
3645
|
+
space_id: accountSpace.spaceId,
|
|
3646
|
+
name: accountSpace.name,
|
|
3647
|
+
owner_did: accountSpace.ownerDid,
|
|
3648
|
+
type: accountSpace.type,
|
|
3649
|
+
permissions: accountSpace.permissions,
|
|
3650
|
+
status: accountSpace.status,
|
|
3651
|
+
registered_at: accountSpace.registeredAt ?? now,
|
|
3652
|
+
updated_at: now,
|
|
3653
|
+
expires_at: accountSpace.expiresAt instanceof Date ? accountSpace.expiresAt.toISOString() : accountSpace.expiresAt
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
function spaceFromRecord(key, record) {
|
|
3657
|
+
const expiresAt = record.expires_at ?? record.expiresAt;
|
|
3658
|
+
return {
|
|
3659
|
+
spaceId: record.space_id ?? record.spaceId ?? spaceIdFromKey(key),
|
|
3660
|
+
name: record.name ?? spaceIdFromKey(key).split(":").pop() ?? spaceIdFromKey(key),
|
|
3661
|
+
ownerDid: record.owner_did ?? record.ownerDid ?? record.owner ?? "",
|
|
3662
|
+
type: record.type ?? "discovered",
|
|
3663
|
+
permissions: Array.isArray(record.permissions) ? record.permissions : [],
|
|
3664
|
+
status: record.status ?? "active",
|
|
3665
|
+
registeredAt: record.registered_at ?? record.registeredAt,
|
|
3666
|
+
updatedAt: record.updated_at ?? record.updatedAt,
|
|
3667
|
+
expiresAt: expiresAt ? new Date(expiresAt) : void 0
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
function indexedSpaceFromRow(row) {
|
|
3671
|
+
const [spaceId, name, ownerDid, type, permissionsJson, status, registeredAt, updatedAt, expiresAt] = row;
|
|
3672
|
+
return {
|
|
3673
|
+
spaceId,
|
|
3674
|
+
name,
|
|
3675
|
+
ownerDid,
|
|
3676
|
+
type,
|
|
3677
|
+
permissions: JSON.parse(permissionsJson),
|
|
3678
|
+
status,
|
|
3679
|
+
registeredAt: registeredAt ?? void 0,
|
|
3680
|
+
updatedAt,
|
|
3681
|
+
expiresAt: expiresAt ? new Date(expiresAt) : void 0
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
function hashJson(value) {
|
|
3685
|
+
const input = stableJson(value);
|
|
3686
|
+
let hash = 0xcbf29ce484222325n;
|
|
3687
|
+
const prime = 0x100000001b3n;
|
|
3688
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
3689
|
+
hash ^= BigInt(input.charCodeAt(index));
|
|
3690
|
+
hash = BigInt.asUintN(64, hash * prime);
|
|
3691
|
+
}
|
|
3692
|
+
return hash.toString(16).padStart(16, "0");
|
|
3693
|
+
}
|
|
3694
|
+
function stableJson(value) {
|
|
3695
|
+
if (value === null || typeof value !== "object") {
|
|
3696
|
+
return JSON.stringify(value);
|
|
3427
3697
|
}
|
|
3428
|
-
if (
|
|
3429
|
-
return {
|
|
3698
|
+
if (Array.isArray(value)) {
|
|
3699
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
3430
3700
|
}
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
);
|
|
3701
|
+
const object = value;
|
|
3702
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
|
|
3434
3703
|
}
|
|
3435
|
-
function
|
|
3436
|
-
|
|
3437
|
-
|
|
3704
|
+
function indexedDelegationFromRow(row) {
|
|
3705
|
+
const [
|
|
3706
|
+
cid,
|
|
3707
|
+
direction,
|
|
3708
|
+
spaceId,
|
|
3709
|
+
spaceName,
|
|
3710
|
+
counterpartyDid,
|
|
3711
|
+
delegateDid,
|
|
3712
|
+
delegatorDid,
|
|
3713
|
+
path,
|
|
3714
|
+
actionsJson,
|
|
3715
|
+
expiry,
|
|
3716
|
+
status,
|
|
3717
|
+
createdAt
|
|
3718
|
+
] = row;
|
|
3719
|
+
return {
|
|
3720
|
+
cid,
|
|
3721
|
+
direction,
|
|
3722
|
+
spaceId,
|
|
3723
|
+
spaceName: spaceName ?? void 0,
|
|
3724
|
+
counterpartyDid,
|
|
3725
|
+
delegateDid,
|
|
3726
|
+
delegatorDid: delegatorDid ?? void 0,
|
|
3727
|
+
path,
|
|
3728
|
+
actions: JSON.parse(actionsJson),
|
|
3729
|
+
expiry: new Date(expiry),
|
|
3730
|
+
status,
|
|
3731
|
+
createdAt: createdAt ? new Date(createdAt) : void 0
|
|
3732
|
+
};
|
|
3733
|
+
}
|
|
3734
|
+
function mapDelegation(delegation, space, direction) {
|
|
3735
|
+
return {
|
|
3736
|
+
cid: delegation.cid,
|
|
3737
|
+
direction,
|
|
3738
|
+
spaceId: delegation.spaceId || space.id,
|
|
3739
|
+
spaceName: space.name,
|
|
3740
|
+
counterpartyDid: direction === "granted" ? delegation.delegateDID : delegation.delegatorDID ?? delegation.delegateDID,
|
|
3741
|
+
delegateDid: delegation.delegateDID,
|
|
3742
|
+
delegatorDid: delegation.delegatorDID,
|
|
3743
|
+
path: delegation.path,
|
|
3744
|
+
actions: delegation.actions,
|
|
3745
|
+
expiry: delegation.expiry,
|
|
3746
|
+
status: delegation.isRevoked ? "revoked" : delegation.expiry.getTime() <= Date.now() ? "expired" : "active",
|
|
3747
|
+
createdAt: delegation.createdAt
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
function accountErr(error) {
|
|
3751
|
+
return err3(serviceError3(error.code, error.message, SERVICE_NAME2, { cause: error.cause, meta: error.meta }));
|
|
3752
|
+
}
|
|
3753
|
+
function isMissingIndexError(error) {
|
|
3754
|
+
return /no such table:/i.test(error.message);
|
|
3755
|
+
}
|
|
3756
|
+
async function ignoreIndexFailure(task) {
|
|
3757
|
+
try {
|
|
3758
|
+
await task();
|
|
3759
|
+
} catch {
|
|
3438
3760
|
}
|
|
3439
|
-
|
|
3440
|
-
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
// src/index.ts
|
|
3764
|
+
import {
|
|
3765
|
+
ServiceContext as ServiceContext2,
|
|
3766
|
+
KVService as KVService2,
|
|
3767
|
+
PrefixedKVService,
|
|
3768
|
+
ok as ok5,
|
|
3769
|
+
err as err5,
|
|
3770
|
+
serviceError as serviceError5,
|
|
3771
|
+
ErrorCodes as ErrorCodes2,
|
|
3772
|
+
defaultRetryPolicy as defaultRetryPolicy2,
|
|
3773
|
+
SQLService as SQLService2,
|
|
3774
|
+
DatabaseHandle,
|
|
3775
|
+
SQLAction,
|
|
3776
|
+
DuckDbService as DuckDbService2,
|
|
3777
|
+
DuckDbDatabaseHandle,
|
|
3778
|
+
DuckDbAction,
|
|
3779
|
+
HooksService as HooksService2,
|
|
3780
|
+
DataVaultService,
|
|
3781
|
+
VaultHeaders,
|
|
3782
|
+
VaultPublicSpaceKVActions,
|
|
3783
|
+
createVaultCrypto,
|
|
3784
|
+
SecretsService,
|
|
3785
|
+
SECRET_NAME_RE as SECRET_NAME_RE2,
|
|
3786
|
+
canonicalizeSecretScope,
|
|
3787
|
+
resolveSecretListPrefix,
|
|
3788
|
+
resolveSecretPath as resolveSecretPath2,
|
|
3789
|
+
EncryptionService,
|
|
3790
|
+
parseNetworkId as parseNetworkId2,
|
|
3791
|
+
buildNetworkId as buildNetworkId2,
|
|
3792
|
+
isNetworkId,
|
|
3793
|
+
networkDiscoveryKey,
|
|
3794
|
+
NetworkIdError,
|
|
3795
|
+
ENCRYPTION_NETWORK_URN_PREFIX,
|
|
3796
|
+
NETWORK_NAME_PATTERN,
|
|
3797
|
+
canonicalizeEncryptionJson,
|
|
3798
|
+
canonicalHashHex,
|
|
3799
|
+
hexEncode,
|
|
3800
|
+
hexDecode,
|
|
3801
|
+
base64Encode,
|
|
3802
|
+
base64Decode,
|
|
3803
|
+
utf8Encode,
|
|
3804
|
+
utf8Decode,
|
|
3805
|
+
encryptToNetwork,
|
|
3806
|
+
decryptEnvelopeWithKey,
|
|
3807
|
+
validateEnvelope,
|
|
3808
|
+
generateRandomReceiverKey,
|
|
3809
|
+
deriveSignedReceiverKey,
|
|
3810
|
+
buildCanonicalDecryptRequest,
|
|
3811
|
+
buildDecryptFacts,
|
|
3812
|
+
buildDecryptAttenuation,
|
|
3813
|
+
buildDecryptInvocation,
|
|
3814
|
+
checkDecryptInvocationInput,
|
|
3815
|
+
verifyDecryptResponse,
|
|
3816
|
+
canonicalSignedResponse,
|
|
3817
|
+
openWrappedKey,
|
|
3818
|
+
discoverNetwork,
|
|
3819
|
+
ensureNetworkUsableForDecrypt,
|
|
3820
|
+
DEFAULT_ENCRYPTION_ALG,
|
|
3821
|
+
ENVELOPE_VERSION,
|
|
3822
|
+
DEFAULT_KEY_VERSION,
|
|
3823
|
+
DECRYPT_FACT_TYPE,
|
|
3824
|
+
DECRYPT_RESULT_TYPE,
|
|
3825
|
+
DECRYPT_ACTION,
|
|
3826
|
+
ENCRYPTION_SERVICE,
|
|
3827
|
+
ENCRYPTION_SERVICE_SHORT,
|
|
3828
|
+
encryptionError
|
|
3829
|
+
} from "@tinycloud/sdk-services";
|
|
3830
|
+
|
|
3831
|
+
// src/space.ts
|
|
3832
|
+
async function fetchPeerId(host, spaceId) {
|
|
3833
|
+
const res = await fetch(
|
|
3834
|
+
`${host}/peer/generate/${encodeURIComponent(spaceId)}`
|
|
3835
|
+
);
|
|
3836
|
+
if (!res.ok) {
|
|
3837
|
+
const error = await res.text().catch(() => res.statusText);
|
|
3838
|
+
throw new Error(`Failed to get peer ID: ${res.status} - ${error}`);
|
|
3441
3839
|
}
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3840
|
+
return res.text();
|
|
3841
|
+
}
|
|
3842
|
+
async function submitHostDelegation(host, headers) {
|
|
3843
|
+
const res = await fetch(`${host}/delegate`, {
|
|
3844
|
+
method: "POST",
|
|
3845
|
+
headers
|
|
3846
|
+
});
|
|
3847
|
+
return {
|
|
3848
|
+
success: res.ok,
|
|
3849
|
+
status: res.status,
|
|
3850
|
+
error: res.ok ? void 0 : await res.text().catch(() => res.statusText)
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
async function activateSessionWithHost(host, delegationHeader) {
|
|
3854
|
+
const res = await fetch(`${host}/delegate`, {
|
|
3855
|
+
method: "POST",
|
|
3856
|
+
headers: delegationHeader
|
|
3857
|
+
});
|
|
3858
|
+
if (res.ok) {
|
|
3859
|
+
try {
|
|
3860
|
+
const body = await res.json();
|
|
3861
|
+
return {
|
|
3862
|
+
success: true,
|
|
3863
|
+
status: res.status,
|
|
3864
|
+
activated: body.activated ?? [],
|
|
3865
|
+
skipped: body.skipped ?? []
|
|
3866
|
+
};
|
|
3867
|
+
} catch {
|
|
3868
|
+
return {
|
|
3869
|
+
success: true,
|
|
3870
|
+
status: res.status,
|
|
3871
|
+
activated: [],
|
|
3872
|
+
skipped: []
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3446
3875
|
}
|
|
3447
|
-
return action;
|
|
3448
|
-
}
|
|
3449
|
-
function vaultKVPath(base, path) {
|
|
3450
|
-
const normalized = path.startsWith("/") ? path.slice(1) : path;
|
|
3451
|
-
return `${base}/${normalized}`;
|
|
3452
|
-
}
|
|
3453
|
-
function cloneResourceCapability(entry) {
|
|
3454
3876
|
return {
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
actions: [...entry.actions],
|
|
3459
|
-
...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
|
|
3460
|
-
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
3877
|
+
success: false,
|
|
3878
|
+
status: res.status,
|
|
3879
|
+
error: await res.text().catch(() => res.statusText)
|
|
3461
3880
|
};
|
|
3462
3881
|
}
|
|
3463
|
-
|
|
3882
|
+
|
|
3883
|
+
// src/delegations/DelegationManager.ts
|
|
3884
|
+
var DelegationAction = {
|
|
3885
|
+
CREATE: "tinycloud.delegation/create",
|
|
3886
|
+
REVOKE: "tinycloud.delegation/revoke",
|
|
3887
|
+
LIST: "tinycloud.delegation/list",
|
|
3888
|
+
GET: "tinycloud.delegation/get",
|
|
3889
|
+
CHECK: "tinycloud.delegation/check"
|
|
3890
|
+
};
|
|
3891
|
+
function createError(code, message, cause, meta) {
|
|
3464
3892
|
return {
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
|
|
3471
|
-
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
3893
|
+
code,
|
|
3894
|
+
message,
|
|
3895
|
+
service: "delegation",
|
|
3896
|
+
cause,
|
|
3897
|
+
meta
|
|
3472
3898
|
};
|
|
3473
3899
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3900
|
+
var DelegationManager = class {
|
|
3901
|
+
/**
|
|
3902
|
+
* Creates a new DelegationManager instance.
|
|
3903
|
+
*
|
|
3904
|
+
* @param config - Configuration including hosts, session, and invoke function
|
|
3905
|
+
*/
|
|
3906
|
+
constructor(config) {
|
|
3907
|
+
this.hosts = config.hosts;
|
|
3908
|
+
this.session = config.session;
|
|
3909
|
+
this.invoke = config.invoke;
|
|
3910
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3911
|
+
}
|
|
3912
|
+
/**
|
|
3913
|
+
* Updates the session (e.g., after re-authentication).
|
|
3914
|
+
*
|
|
3915
|
+
* @param session - New session to use for operations
|
|
3916
|
+
*/
|
|
3917
|
+
updateSession(session) {
|
|
3918
|
+
this.session = session;
|
|
3919
|
+
}
|
|
3920
|
+
/**
|
|
3921
|
+
* Gets the primary host URL.
|
|
3922
|
+
*/
|
|
3923
|
+
get host() {
|
|
3924
|
+
return this.hosts[0];
|
|
3925
|
+
}
|
|
3926
|
+
/**
|
|
3927
|
+
* Executes an invoke operation against the delegation API.
|
|
3928
|
+
*/
|
|
3929
|
+
async invokeOperation(path, action, body) {
|
|
3930
|
+
const headers = this.invoke(this.session, "delegation", path, action);
|
|
3931
|
+
return this.fetchFn(`${this.host}/invoke`, {
|
|
3932
|
+
method: "POST",
|
|
3933
|
+
headers,
|
|
3934
|
+
body
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3937
|
+
/**
|
|
3938
|
+
* Creates a new delegation.
|
|
3939
|
+
*
|
|
3940
|
+
* Delegates specific permissions to another DID for a given path.
|
|
3941
|
+
* The delegatee can then use these permissions to access resources
|
|
3942
|
+
* within the specified scope.
|
|
3943
|
+
*
|
|
3944
|
+
* @param params - Parameters for the delegation
|
|
3945
|
+
* @returns Result containing the created Delegation or an error
|
|
3946
|
+
*
|
|
3947
|
+
* @example
|
|
3948
|
+
* ```typescript
|
|
3949
|
+
* const result = await manager.create({
|
|
3950
|
+
* delegateDID: bob.did,
|
|
3951
|
+
* path: "documents/shared/",
|
|
3952
|
+
* actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
|
|
3953
|
+
* expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
|
|
3954
|
+
* });
|
|
3955
|
+
* ```
|
|
3956
|
+
*/
|
|
3957
|
+
async create(params) {
|
|
3958
|
+
if (!params.delegateDID) {
|
|
3959
|
+
return {
|
|
3960
|
+
ok: false,
|
|
3961
|
+
error: createError(
|
|
3962
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
3963
|
+
"delegateDID is required"
|
|
3964
|
+
)
|
|
3965
|
+
};
|
|
3482
3966
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3967
|
+
if (!params.path) {
|
|
3968
|
+
return {
|
|
3969
|
+
ok: false,
|
|
3970
|
+
error: createError(
|
|
3971
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
3972
|
+
"path is required"
|
|
3973
|
+
)
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
if (!params.actions || params.actions.length === 0) {
|
|
3977
|
+
return {
|
|
3978
|
+
ok: false,
|
|
3979
|
+
error: createError(
|
|
3980
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
3981
|
+
"at least one action is required"
|
|
3982
|
+
)
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
try {
|
|
3986
|
+
const body = JSON.stringify({
|
|
3987
|
+
delegateDID: params.delegateDID,
|
|
3988
|
+
path: params.path,
|
|
3989
|
+
actions: params.actions,
|
|
3990
|
+
expiry: params.expiry?.toISOString(),
|
|
3991
|
+
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
3992
|
+
statement: params.statement
|
|
3993
|
+
});
|
|
3994
|
+
const response = await this.invokeOperation(
|
|
3995
|
+
params.path,
|
|
3996
|
+
DelegationAction.CREATE,
|
|
3997
|
+
body
|
|
3998
|
+
);
|
|
3999
|
+
if (!response.ok) {
|
|
4000
|
+
const errorText = await response.text();
|
|
4001
|
+
return {
|
|
4002
|
+
ok: false,
|
|
4003
|
+
error: createError(
|
|
4004
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
4005
|
+
`Failed to create delegation: ${response.status} - ${errorText}`,
|
|
4006
|
+
void 0,
|
|
4007
|
+
{ status: response.status, path: params.path }
|
|
4008
|
+
)
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
const apiResponse = await response.json();
|
|
4012
|
+
const delegation = {
|
|
4013
|
+
cid: apiResponse.cid ?? "",
|
|
4014
|
+
delegateDID: params.delegateDID,
|
|
4015
|
+
spaceId: this.session.spaceId,
|
|
4016
|
+
path: params.path,
|
|
4017
|
+
actions: params.actions,
|
|
4018
|
+
expiry: params.expiry ?? new Date(Date.now() + EXPIRY.SHARE_MS),
|
|
4019
|
+
isRevoked: false,
|
|
4020
|
+
allowSubDelegation: !(params.disableSubDelegation ?? false),
|
|
4021
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
4022
|
+
};
|
|
4023
|
+
return { ok: true, data: delegation };
|
|
4024
|
+
} catch (error) {
|
|
4025
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
4026
|
+
return {
|
|
4027
|
+
ok: false,
|
|
4028
|
+
error: createError(
|
|
4029
|
+
DelegationErrorCodes.ABORTED,
|
|
4030
|
+
"Request aborted",
|
|
4031
|
+
error
|
|
4032
|
+
)
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
return {
|
|
4036
|
+
ok: false,
|
|
4037
|
+
error: createError(
|
|
4038
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4039
|
+
`Network error during delegation creation: ${String(error)}`,
|
|
4040
|
+
error instanceof Error ? error : void 0
|
|
4041
|
+
)
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
/**
|
|
4046
|
+
* Revokes an existing delegation.
|
|
4047
|
+
*
|
|
4048
|
+
* Once revoked, the delegation can no longer be used to access resources.
|
|
4049
|
+
* This also invalidates any sub-delegations derived from this delegation.
|
|
4050
|
+
*
|
|
4051
|
+
* @param cid - The CID of the delegation to revoke
|
|
4052
|
+
* @returns Result indicating success or an error
|
|
4053
|
+
*
|
|
4054
|
+
* @example
|
|
4055
|
+
* ```typescript
|
|
4056
|
+
* const result = await manager.revoke("bafy...");
|
|
4057
|
+
* if (result.ok) {
|
|
4058
|
+
* console.log("Delegation revoked successfully");
|
|
4059
|
+
* }
|
|
4060
|
+
* ```
|
|
4061
|
+
*/
|
|
4062
|
+
async revoke(cid) {
|
|
4063
|
+
if (!cid) {
|
|
4064
|
+
return {
|
|
4065
|
+
ok: false,
|
|
4066
|
+
error: createError(
|
|
4067
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
4068
|
+
"cid is required"
|
|
4069
|
+
)
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
try {
|
|
4073
|
+
const body = JSON.stringify({ cid });
|
|
4074
|
+
const response = await this.invokeOperation(
|
|
4075
|
+
cid,
|
|
4076
|
+
DelegationAction.REVOKE,
|
|
4077
|
+
body
|
|
4078
|
+
);
|
|
4079
|
+
if (!response.ok) {
|
|
4080
|
+
const errorText = await response.text();
|
|
4081
|
+
if (response.status === 404) {
|
|
4082
|
+
return {
|
|
4083
|
+
ok: false,
|
|
4084
|
+
error: createError(
|
|
4085
|
+
DelegationErrorCodes.NOT_FOUND,
|
|
4086
|
+
`Delegation not found: ${cid}`
|
|
4087
|
+
)
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
return {
|
|
4091
|
+
ok: false,
|
|
4092
|
+
error: createError(
|
|
4093
|
+
DelegationErrorCodes.REVOCATION_FAILED,
|
|
4094
|
+
`Failed to revoke delegation: ${response.status} - ${errorText}`,
|
|
4095
|
+
void 0,
|
|
4096
|
+
{ status: response.status, cid }
|
|
4097
|
+
)
|
|
4098
|
+
};
|
|
3488
4099
|
}
|
|
4100
|
+
return { ok: true, data: void 0 };
|
|
4101
|
+
} catch (error) {
|
|
4102
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
4103
|
+
return {
|
|
4104
|
+
ok: false,
|
|
4105
|
+
error: createError(
|
|
4106
|
+
DelegationErrorCodes.ABORTED,
|
|
4107
|
+
"Request aborted",
|
|
4108
|
+
error
|
|
4109
|
+
)
|
|
4110
|
+
};
|
|
4111
|
+
}
|
|
4112
|
+
return {
|
|
4113
|
+
ok: false,
|
|
4114
|
+
error: createError(
|
|
4115
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4116
|
+
`Network error during delegation revocation: ${String(error)}`,
|
|
4117
|
+
error instanceof Error ? error : void 0
|
|
4118
|
+
)
|
|
4119
|
+
};
|
|
3489
4120
|
}
|
|
3490
|
-
if (existing.description === void 0 && resource.description !== void 0) {
|
|
3491
|
-
existing.description = resource.description;
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
return [...byKey.values()];
|
|
3495
|
-
}
|
|
3496
|
-
function capabilitiesReadPermission(space) {
|
|
3497
|
-
return {
|
|
3498
|
-
service: "tinycloud.capabilities",
|
|
3499
|
-
space,
|
|
3500
|
-
path: "",
|
|
3501
|
-
actions: ["tinycloud.capabilities/read"]
|
|
3502
|
-
};
|
|
3503
|
-
}
|
|
3504
|
-
function withCapabilitiesReadForSpaces(resources) {
|
|
3505
|
-
if (resources.length === 0) {
|
|
3506
|
-
return [];
|
|
3507
|
-
}
|
|
3508
|
-
const spaces = new Set(
|
|
3509
|
-
resources.filter((resource) => resource.service !== ENCRYPTION_PERMISSION_SERVICE).map((resource) => resource.space)
|
|
3510
|
-
);
|
|
3511
|
-
return dedupeResources([
|
|
3512
|
-
...resources,
|
|
3513
|
-
...[...spaces].map(capabilitiesReadPermission)
|
|
3514
|
-
]);
|
|
3515
|
-
}
|
|
3516
|
-
function accountRegistryPermission() {
|
|
3517
|
-
return {
|
|
3518
|
-
service: "tinycloud.kv",
|
|
3519
|
-
space: ACCOUNT_REGISTRY_SPACE,
|
|
3520
|
-
path: ACCOUNT_REGISTRY_PATH,
|
|
3521
|
-
actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
|
|
3522
|
-
};
|
|
3523
|
-
}
|
|
3524
|
-
function composeManifestRequest(inputs, options = {}) {
|
|
3525
|
-
if (!Array.isArray(inputs) || inputs.length === 0) {
|
|
3526
|
-
throw new ManifestValidationError(
|
|
3527
|
-
"composeManifestRequest requires at least one manifest"
|
|
3528
|
-
);
|
|
3529
|
-
}
|
|
3530
|
-
const includeAccountRegistryPermissions = options.includeAccountRegistryPermissions ?? true;
|
|
3531
|
-
const manifests = inputs.map(validateManifest);
|
|
3532
|
-
const resolved = manifests.map(resolveManifest);
|
|
3533
|
-
const resources = resolved.flatMap((entry) => entry.resources);
|
|
3534
|
-
const delegationTargets = resolved.flatMap(
|
|
3535
|
-
(entry) => entry.additionalDelegates.map((delegate) => ({
|
|
3536
|
-
...delegate,
|
|
3537
|
-
permissions: dedupeResources(delegate.permissions)
|
|
3538
|
-
}))
|
|
3539
|
-
);
|
|
3540
|
-
if (includeAccountRegistryPermissions) {
|
|
3541
|
-
resources.push(accountRegistryPermission());
|
|
3542
4121
|
}
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
4122
|
+
/**
|
|
4123
|
+
* Lists all delegations for the current session's space.
|
|
4124
|
+
*
|
|
4125
|
+
* Returns both delegations created by the current user (as delegator)
|
|
4126
|
+
* and delegations granted to the current user (as delegatee).
|
|
4127
|
+
*
|
|
4128
|
+
* @returns Result containing an array of Delegations or an error
|
|
4129
|
+
*
|
|
4130
|
+
* @example
|
|
4131
|
+
* ```typescript
|
|
4132
|
+
* const result = await manager.list();
|
|
4133
|
+
* if (result.ok) {
|
|
4134
|
+
* for (const delegation of result.data) {
|
|
4135
|
+
* console.log(`${delegation.cid}: ${delegation.path} -> ${delegation.delegateDID}`);
|
|
4136
|
+
* }
|
|
4137
|
+
* }
|
|
4138
|
+
* ```
|
|
4139
|
+
*/
|
|
4140
|
+
async list() {
|
|
4141
|
+
try {
|
|
4142
|
+
const response = await this.invokeOperation("", DelegationAction.LIST);
|
|
4143
|
+
if (!response.ok) {
|
|
4144
|
+
const errorText = await response.text();
|
|
4145
|
+
return {
|
|
4146
|
+
ok: false,
|
|
4147
|
+
error: createError(
|
|
4148
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4149
|
+
`Failed to list delegations: ${response.status} - ${errorText}`,
|
|
4150
|
+
void 0,
|
|
4151
|
+
{ status: response.status }
|
|
4152
|
+
)
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
const data = await response.json();
|
|
4156
|
+
const delegations = data.map((item) => ({
|
|
4157
|
+
cid: item.cid,
|
|
4158
|
+
delegateDID: item.delegateDID,
|
|
4159
|
+
delegatorDID: item.delegatorDID,
|
|
4160
|
+
spaceId: item.spaceId,
|
|
4161
|
+
path: item.path,
|
|
4162
|
+
actions: item.actions,
|
|
4163
|
+
expiry: new Date(item.expiry),
|
|
4164
|
+
isRevoked: item.isRevoked,
|
|
4165
|
+
createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
|
|
4166
|
+
parentCid: item.parentCid,
|
|
4167
|
+
allowSubDelegation: item.allowSubDelegation
|
|
4168
|
+
}));
|
|
4169
|
+
return { ok: true, data: delegations };
|
|
4170
|
+
} catch (error) {
|
|
4171
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
4172
|
+
return {
|
|
4173
|
+
ok: false,
|
|
4174
|
+
error: createError(
|
|
4175
|
+
DelegationErrorCodes.ABORTED,
|
|
4176
|
+
"Request aborted",
|
|
4177
|
+
error
|
|
4178
|
+
)
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
return {
|
|
4182
|
+
ok: false,
|
|
4183
|
+
error: createError(
|
|
4184
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4185
|
+
`Network error during delegation list: ${String(error)}`,
|
|
4186
|
+
error instanceof Error ? error : void 0
|
|
4187
|
+
)
|
|
4188
|
+
};
|
|
3551
4189
|
}
|
|
3552
4190
|
}
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
}
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
if (
|
|
3575
|
-
|
|
3576
|
-
|
|
4191
|
+
/**
|
|
4192
|
+
* Gets the full delegation chain for a given delegation.
|
|
4193
|
+
*
|
|
4194
|
+
* Returns the chain of delegations from the root (original delegator)
|
|
4195
|
+
* to the specified delegation, including all intermediate sub-delegations.
|
|
4196
|
+
*
|
|
4197
|
+
* @param cid - The CID of the delegation to get the chain for
|
|
4198
|
+
* @returns Result containing the DelegationChain or an error
|
|
4199
|
+
*
|
|
4200
|
+
* @example
|
|
4201
|
+
* ```typescript
|
|
4202
|
+
* const result = await manager.getChain("bafy...");
|
|
4203
|
+
* if (result.ok) {
|
|
4204
|
+
* console.log("Chain length:", result.data.length);
|
|
4205
|
+
* for (const delegation of result.data) {
|
|
4206
|
+
* console.log(`- ${delegation.delegatorDID} -> ${delegation.delegateDID}`);
|
|
4207
|
+
* }
|
|
4208
|
+
* }
|
|
4209
|
+
* ```
|
|
4210
|
+
*/
|
|
4211
|
+
async getChain(cid) {
|
|
4212
|
+
if (!cid) {
|
|
4213
|
+
return {
|
|
4214
|
+
ok: false,
|
|
4215
|
+
error: createError(
|
|
4216
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
4217
|
+
"cid is required"
|
|
4218
|
+
)
|
|
4219
|
+
};
|
|
4220
|
+
}
|
|
4221
|
+
try {
|
|
4222
|
+
const body = JSON.stringify({ cid, includeChain: true });
|
|
4223
|
+
const response = await this.invokeOperation(
|
|
4224
|
+
cid,
|
|
4225
|
+
DelegationAction.GET,
|
|
4226
|
+
body
|
|
3577
4227
|
);
|
|
4228
|
+
if (!response.ok) {
|
|
4229
|
+
const errorText = await response.text();
|
|
4230
|
+
if (response.status === 404) {
|
|
4231
|
+
return {
|
|
4232
|
+
ok: false,
|
|
4233
|
+
error: createError(
|
|
4234
|
+
DelegationErrorCodes.NOT_FOUND,
|
|
4235
|
+
`Delegation not found: ${cid}`
|
|
4236
|
+
)
|
|
4237
|
+
};
|
|
4238
|
+
}
|
|
4239
|
+
return {
|
|
4240
|
+
ok: false,
|
|
4241
|
+
error: createError(
|
|
4242
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4243
|
+
`Failed to get delegation chain: ${response.status} - ${errorText}`,
|
|
4244
|
+
void 0,
|
|
4245
|
+
{ status: response.status, cid }
|
|
4246
|
+
)
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
const data = await response.json();
|
|
4250
|
+
const chain = data.chain.map((item) => ({
|
|
4251
|
+
cid: item.cid,
|
|
4252
|
+
delegateDID: item.delegateDID,
|
|
4253
|
+
delegatorDID: item.delegatorDID,
|
|
4254
|
+
spaceId: item.spaceId,
|
|
4255
|
+
path: item.path,
|
|
4256
|
+
actions: item.actions,
|
|
4257
|
+
expiry: new Date(item.expiry),
|
|
4258
|
+
isRevoked: item.isRevoked,
|
|
4259
|
+
createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
|
|
4260
|
+
parentCid: item.parentCid,
|
|
4261
|
+
allowSubDelegation: item.allowSubDelegation
|
|
4262
|
+
}));
|
|
4263
|
+
return { ok: true, data: chain };
|
|
4264
|
+
} catch (error) {
|
|
4265
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
4266
|
+
return {
|
|
4267
|
+
ok: false,
|
|
4268
|
+
error: createError(
|
|
4269
|
+
DelegationErrorCodes.ABORTED,
|
|
4270
|
+
"Request aborted",
|
|
4271
|
+
error
|
|
4272
|
+
)
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
return {
|
|
4276
|
+
ok: false,
|
|
4277
|
+
error: createError(
|
|
4278
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4279
|
+
`Network error during chain retrieval: ${String(error)}`,
|
|
4280
|
+
error instanceof Error ? error : void 0
|
|
4281
|
+
)
|
|
4282
|
+
};
|
|
3578
4283
|
}
|
|
3579
|
-
|
|
3580
|
-
|
|
4284
|
+
}
|
|
4285
|
+
/**
|
|
4286
|
+
* Checks if the current session has permission for a given path and action.
|
|
4287
|
+
*
|
|
4288
|
+
* This can be used to verify permissions before attempting an operation,
|
|
4289
|
+
* or to implement custom access control logic.
|
|
4290
|
+
*
|
|
4291
|
+
* @param path - The resource path to check
|
|
4292
|
+
* @param action - The action to check (e.g., "tinycloud.kv/get")
|
|
4293
|
+
* @returns Result containing a boolean indicating permission or an error
|
|
4294
|
+
*
|
|
4295
|
+
* @example
|
|
4296
|
+
* ```typescript
|
|
4297
|
+
* const result = await manager.checkPermission("documents/private/", "tinycloud.kv/put");
|
|
4298
|
+
* if (result.ok && result.data) {
|
|
4299
|
+
* console.log("Permission granted");
|
|
4300
|
+
* } else {
|
|
4301
|
+
* console.log("Permission denied");
|
|
4302
|
+
* }
|
|
4303
|
+
* ```
|
|
4304
|
+
*/
|
|
4305
|
+
async checkPermission(path, action) {
|
|
4306
|
+
if (!path) {
|
|
4307
|
+
return {
|
|
4308
|
+
ok: false,
|
|
4309
|
+
error: createError(
|
|
4310
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
4311
|
+
"path is required"
|
|
4312
|
+
)
|
|
4313
|
+
};
|
|
3581
4314
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
4315
|
+
if (!action) {
|
|
4316
|
+
return {
|
|
4317
|
+
ok: false,
|
|
4318
|
+
error: createError(
|
|
4319
|
+
DelegationErrorCodes.INVALID_INPUT,
|
|
4320
|
+
"action is required"
|
|
4321
|
+
)
|
|
4322
|
+
};
|
|
4323
|
+
}
|
|
4324
|
+
try {
|
|
4325
|
+
const body = JSON.stringify({ path, action });
|
|
4326
|
+
const response = await this.invokeOperation(
|
|
4327
|
+
path,
|
|
4328
|
+
DelegationAction.CHECK,
|
|
4329
|
+
body
|
|
4330
|
+
);
|
|
4331
|
+
if (!response.ok) {
|
|
4332
|
+
if (response.status === 403) {
|
|
4333
|
+
return { ok: true, data: false };
|
|
3592
4334
|
}
|
|
4335
|
+
const errorText = await response.text();
|
|
4336
|
+
return {
|
|
4337
|
+
ok: false,
|
|
4338
|
+
error: createError(
|
|
4339
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4340
|
+
`Failed to check permission: ${response.status} - ${errorText}`,
|
|
4341
|
+
void 0,
|
|
4342
|
+
{ status: response.status, path, action }
|
|
4343
|
+
)
|
|
4344
|
+
};
|
|
3593
4345
|
}
|
|
4346
|
+
const data = await response.json();
|
|
4347
|
+
return { ok: true, data: data.allowed };
|
|
4348
|
+
} catch (error) {
|
|
4349
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
4350
|
+
return {
|
|
4351
|
+
ok: false,
|
|
4352
|
+
error: createError(
|
|
4353
|
+
DelegationErrorCodes.ABORTED,
|
|
4354
|
+
"Request aborted",
|
|
4355
|
+
error
|
|
4356
|
+
)
|
|
4357
|
+
};
|
|
4358
|
+
}
|
|
4359
|
+
return {
|
|
4360
|
+
ok: false,
|
|
4361
|
+
error: createError(
|
|
4362
|
+
DelegationErrorCodes.NETWORK_ERROR,
|
|
4363
|
+
`Network error during permission check: ${String(error)}`,
|
|
4364
|
+
error instanceof Error ? error : void 0
|
|
4365
|
+
)
|
|
4366
|
+
};
|
|
3594
4367
|
}
|
|
3595
4368
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
4369
|
+
};
|
|
4370
|
+
|
|
4371
|
+
// src/delegations/SharingService.schema.ts
|
|
4372
|
+
import { z as z5 } from "zod";
|
|
4373
|
+
var EncodedShareDataSchema = z5.object({
|
|
4374
|
+
/** Private key in JWK format (must include d parameter) */
|
|
4375
|
+
key: JWKSchema.refine(
|
|
4376
|
+
(jwk) => typeof jwk.d === "string" && jwk.d.length > 0,
|
|
4377
|
+
{ message: "JWK must include private key (d parameter)" }
|
|
4378
|
+
),
|
|
4379
|
+
/** DID of the key */
|
|
4380
|
+
keyDid: z5.string().min(1, "keyDid is required"),
|
|
4381
|
+
/** The delegation granting access */
|
|
4382
|
+
delegation: DelegationSchema,
|
|
4383
|
+
/** Resource path this link grants access to */
|
|
4384
|
+
path: z5.string().min(1, "path is required"),
|
|
4385
|
+
/** TinyCloud host URL */
|
|
4386
|
+
host: z5.string().url("host must be a valid URL"),
|
|
4387
|
+
/** Space ID */
|
|
4388
|
+
spaceId: z5.string().min(1, "spaceId is required"),
|
|
4389
|
+
/** Schema version (must be 1) */
|
|
4390
|
+
version: z5.literal(1)
|
|
4391
|
+
});
|
|
4392
|
+
var ReceiveOptionsSchema = z5.object({
|
|
4393
|
+
/**
|
|
4394
|
+
* Whether to automatically create a sub-delegation to the current session key.
|
|
4395
|
+
* Default: true
|
|
4396
|
+
*/
|
|
4397
|
+
autoSubdelegate: z5.boolean().optional(),
|
|
4398
|
+
/**
|
|
4399
|
+
* Whether to use the current session key for operations (requires autoSubdelegate).
|
|
4400
|
+
* Default: true
|
|
4401
|
+
*/
|
|
4402
|
+
useSessionKey: z5.boolean().optional(),
|
|
4403
|
+
/**
|
|
4404
|
+
* Ingestion options passed to CapabilityKeyRegistry.
|
|
4405
|
+
*/
|
|
4406
|
+
ingestOptions: IngestOptionsSchema.optional()
|
|
4407
|
+
});
|
|
4408
|
+
var SharingServiceConfigSchema = z5.object({
|
|
4409
|
+
/** TinyCloud host URLs */
|
|
4410
|
+
hosts: z5.array(z5.string().url()).min(1, "At least one host URL is required"),
|
|
4411
|
+
/**
|
|
4412
|
+
* Active session for authentication.
|
|
4413
|
+
* Required for generate(), optional for receive().
|
|
4414
|
+
*/
|
|
4415
|
+
session: z5.unknown().refine(
|
|
4416
|
+
(val) => val === void 0 || val !== null && typeof val === "object",
|
|
4417
|
+
{ message: "Expected a ServiceSession object or undefined" }
|
|
4418
|
+
).optional(),
|
|
4419
|
+
/** Platform-specific invoke function */
|
|
4420
|
+
invoke: z5.unknown().refine((val) => typeof val === "function", {
|
|
4421
|
+
message: "Expected an invoke function"
|
|
4422
|
+
}),
|
|
4423
|
+
/** Optional custom fetch implementation */
|
|
4424
|
+
fetch: z5.unknown().refine(
|
|
4425
|
+
(val) => val === void 0 || typeof val === "function",
|
|
4426
|
+
{ message: "Expected a fetch function or undefined" }
|
|
4427
|
+
).optional(),
|
|
4428
|
+
/** Key provider for cryptographic operations */
|
|
4429
|
+
keyProvider: KeyProviderSchema,
|
|
4430
|
+
/** Capability key registry for key/delegation management */
|
|
4431
|
+
registry: z5.unknown().refine(
|
|
4432
|
+
(val) => val !== null && typeof val === "object",
|
|
4433
|
+
{ message: "Expected an ICapabilityKeyRegistry object" }
|
|
4434
|
+
),
|
|
4435
|
+
/**
|
|
4436
|
+
* Delegation manager for creating delegations.
|
|
4437
|
+
* Required for generate(), optional for receive().
|
|
4438
|
+
*/
|
|
4439
|
+
delegationManager: z5.unknown().refine(
|
|
4440
|
+
(val) => val === void 0 || val !== null && typeof val === "object",
|
|
4441
|
+
{ message: "Expected a DelegationManager object or undefined" }
|
|
4442
|
+
).optional(),
|
|
4443
|
+
/** Factory for creating KV service instances */
|
|
4444
|
+
createKVService: z5.unknown().refine(
|
|
4445
|
+
(val) => typeof val === "function",
|
|
4446
|
+
{ message: "Expected a createKVService factory function" }
|
|
4447
|
+
),
|
|
4448
|
+
/** Base URL for sharing links (e.g., "https://share.myapp.com") */
|
|
4449
|
+
baseUrl: z5.string().optional(),
|
|
4450
|
+
/**
|
|
4451
|
+
* Custom delegation creation function.
|
|
4452
|
+
*/
|
|
4453
|
+
createDelegation: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
4454
|
+
message: "Expected a createDelegation function or undefined"
|
|
4455
|
+
}).optional(),
|
|
4456
|
+
/**
|
|
4457
|
+
* WASM function for client-side delegation creation.
|
|
4458
|
+
*/
|
|
4459
|
+
createDelegationWasm: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
4460
|
+
message: "Expected a createDelegationWasm function or undefined"
|
|
4461
|
+
}).optional(),
|
|
4462
|
+
/**
|
|
4463
|
+
* Path prefix for KV operations.
|
|
4464
|
+
*/
|
|
4465
|
+
pathPrefix: z5.string().optional(),
|
|
4466
|
+
/**
|
|
4467
|
+
* Session expiry time.
|
|
4468
|
+
*/
|
|
4469
|
+
sessionExpiry: z5.date().optional(),
|
|
4470
|
+
/**
|
|
4471
|
+
* Callback to create a DIRECT delegation from wallet to share key.
|
|
4472
|
+
* This is the preferred method for long-lived share links because it
|
|
4473
|
+
* bypasses the session delegation chain entirely.
|
|
4474
|
+
*/
|
|
4475
|
+
onRootDelegationNeeded: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
|
|
4476
|
+
message: "Expected an onRootDelegationNeeded function or undefined"
|
|
4477
|
+
}).optional()
|
|
4478
|
+
});
|
|
4479
|
+
function validateEncodedShareData(data) {
|
|
4480
|
+
const result = EncodedShareDataSchema.safeParse(data);
|
|
4481
|
+
if (!result.success) {
|
|
4482
|
+
return {
|
|
4483
|
+
ok: false,
|
|
4484
|
+
error: {
|
|
4485
|
+
code: DelegationErrorCodes.VALIDATION_ERROR,
|
|
4486
|
+
message: `Invalid share data: ${result.error.message}`,
|
|
4487
|
+
service: "delegation",
|
|
4488
|
+
meta: { issues: result.error.issues }
|
|
4489
|
+
}
|
|
4490
|
+
};
|
|
3620
4491
|
}
|
|
3621
|
-
return
|
|
4492
|
+
return { ok: true, data: result.data };
|
|
3622
4493
|
}
|
|
3623
4494
|
|
|
3624
4495
|
// src/delegations/SharingService.ts
|
|
@@ -3799,13 +4670,13 @@ var SharingService = class {
|
|
|
3799
4670
|
)
|
|
3800
4671
|
};
|
|
3801
4672
|
}
|
|
3802
|
-
} catch (
|
|
4673
|
+
} catch (err6) {
|
|
3803
4674
|
return {
|
|
3804
4675
|
ok: false,
|
|
3805
4676
|
error: createError2(
|
|
3806
4677
|
DelegationErrorCodes.CREATION_FAILED,
|
|
3807
|
-
`Failed to generate session key for share: ${
|
|
3808
|
-
|
|
4678
|
+
`Failed to generate session key for share: ${err6 instanceof Error ? err6.message : String(err6)}`,
|
|
4679
|
+
err6 instanceof Error ? err6 : void 0
|
|
3809
4680
|
)
|
|
3810
4681
|
};
|
|
3811
4682
|
}
|
|
@@ -3851,7 +4722,7 @@ var SharingService = class {
|
|
|
3851
4722
|
}
|
|
3852
4723
|
delegation = parsed;
|
|
3853
4724
|
}
|
|
3854
|
-
} catch (
|
|
4725
|
+
} catch (err6) {
|
|
3855
4726
|
const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
|
|
3856
4727
|
expiry = fallbackResult.expiry;
|
|
3857
4728
|
const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
|
|
@@ -4049,13 +4920,13 @@ var SharingService = class {
|
|
|
4049
4920
|
allowSubDelegation: true,
|
|
4050
4921
|
createdAt: /* @__PURE__ */ new Date()
|
|
4051
4922
|
};
|
|
4052
|
-
} catch (
|
|
4923
|
+
} catch (err6) {
|
|
4053
4924
|
return {
|
|
4054
4925
|
ok: false,
|
|
4055
4926
|
error: createError2(
|
|
4056
4927
|
DelegationErrorCodes.CREATION_FAILED,
|
|
4057
|
-
`Failed to create delegation via WASM: ${
|
|
4058
|
-
|
|
4928
|
+
`Failed to create delegation via WASM: ${err6 instanceof Error ? err6.message : String(err6)}`,
|
|
4929
|
+
err6 instanceof Error ? err6 : void 0
|
|
4059
4930
|
)
|
|
4060
4931
|
};
|
|
4061
4932
|
}
|
|
@@ -4136,8 +5007,8 @@ var SharingService = class {
|
|
|
4136
5007
|
let activeKey = keyInfo;
|
|
4137
5008
|
if (autoSubdelegate && useSessionKey && this.session) {
|
|
4138
5009
|
try {
|
|
4139
|
-
} catch (
|
|
4140
|
-
console.warn("Auto-subdelegation failed, using ingested key directly:",
|
|
5010
|
+
} catch (err6) {
|
|
5011
|
+
console.warn("Auto-subdelegation failed, using ingested key directly:", err6);
|
|
4141
5012
|
}
|
|
4142
5013
|
}
|
|
4143
5014
|
const authHeader = shareData.delegation.authHeader ?? `Bearer ${shareData.delegation.cid}`;
|
|
@@ -4235,26 +5106,26 @@ var SharingService = class {
|
|
|
4235
5106
|
let jsonString;
|
|
4236
5107
|
try {
|
|
4237
5108
|
jsonString = base64UrlDecode(base64Data);
|
|
4238
|
-
} catch (
|
|
5109
|
+
} catch (err6) {
|
|
4239
5110
|
return {
|
|
4240
5111
|
ok: false,
|
|
4241
5112
|
error: createError2(
|
|
4242
5113
|
DelegationErrorCodes.INVALID_TOKEN,
|
|
4243
|
-
`Failed to decode base64 data: ${
|
|
4244
|
-
|
|
5114
|
+
`Failed to decode base64 data: ${err6 instanceof Error ? err6.message : String(err6)}`,
|
|
5115
|
+
err6 instanceof Error ? err6 : void 0
|
|
4245
5116
|
)
|
|
4246
5117
|
};
|
|
4247
5118
|
}
|
|
4248
5119
|
let parsed;
|
|
4249
5120
|
try {
|
|
4250
5121
|
parsed = JSON.parse(jsonString);
|
|
4251
|
-
} catch (
|
|
5122
|
+
} catch (err6) {
|
|
4252
5123
|
return {
|
|
4253
5124
|
ok: false,
|
|
4254
5125
|
error: createError2(
|
|
4255
5126
|
DelegationErrorCodes.INVALID_TOKEN,
|
|
4256
|
-
`Failed to parse share data JSON: ${
|
|
4257
|
-
|
|
5127
|
+
`Failed to parse share data JSON: ${err6 instanceof Error ? err6.message : String(err6)}`,
|
|
5128
|
+
err6 instanceof Error ? err6 : void 0
|
|
4258
5129
|
)
|
|
4259
5130
|
};
|
|
4260
5131
|
}
|
|
@@ -4281,8 +5152,8 @@ function createSharingService(config) {
|
|
|
4281
5152
|
}
|
|
4282
5153
|
|
|
4283
5154
|
// src/authorization/CapabilityKeyRegistry.ts
|
|
4284
|
-
import { ok as
|
|
4285
|
-
var
|
|
5155
|
+
import { ok as ok4, err as err4, serviceError as serviceError4 } from "@tinycloud/sdk-services";
|
|
5156
|
+
var SERVICE_NAME3 = "capability-key-registry";
|
|
4286
5157
|
var CapabilityKeyRegistryErrorCodes = {
|
|
4287
5158
|
/** Key not found in registry */
|
|
4288
5159
|
KEY_NOT_FOUND: "KEY_NOT_FOUND",
|
|
@@ -4499,11 +5370,11 @@ var CapabilityKeyRegistry = class {
|
|
|
4499
5370
|
revokeDelegation(cid) {
|
|
4500
5371
|
const stored = this.store.byCid.get(cid);
|
|
4501
5372
|
if (!stored) {
|
|
4502
|
-
return
|
|
4503
|
-
|
|
5373
|
+
return err4(
|
|
5374
|
+
serviceError4(
|
|
4504
5375
|
CapabilityKeyRegistryErrorCodes.KEY_NOT_FOUND,
|
|
4505
5376
|
`Delegation not found: ${cid}`,
|
|
4506
|
-
|
|
5377
|
+
SERVICE_NAME3
|
|
4507
5378
|
)
|
|
4508
5379
|
);
|
|
4509
5380
|
}
|
|
@@ -4522,7 +5393,7 @@ var CapabilityKeyRegistry = class {
|
|
|
4522
5393
|
}
|
|
4523
5394
|
}
|
|
4524
5395
|
}
|
|
4525
|
-
return
|
|
5396
|
+
return ok4(void 0);
|
|
4526
5397
|
}
|
|
4527
5398
|
// ===========================================================================
|
|
4528
5399
|
// Search
|
|
@@ -4751,8 +5622,8 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
|
|
|
4751
5622
|
response = await fetchFn(`${host}/info`, {
|
|
4752
5623
|
signal: AbortSignal.timeout(5e3)
|
|
4753
5624
|
});
|
|
4754
|
-
} catch (
|
|
4755
|
-
throw new VersionCheckError(host,
|
|
5625
|
+
} catch (err6) {
|
|
5626
|
+
throw new VersionCheckError(host, err6);
|
|
4756
5627
|
}
|
|
4757
5628
|
if (!response.ok) {
|
|
4758
5629
|
throw new VersionCheckError(host);
|
|
@@ -5284,6 +6155,7 @@ function parseRecapCapabilities(parseWasm, siwe) {
|
|
|
5284
6155
|
export {
|
|
5285
6156
|
ACCOUNT_REGISTRY_PATH,
|
|
5286
6157
|
ACCOUNT_REGISTRY_SPACE,
|
|
6158
|
+
AccountService,
|
|
5287
6159
|
AutoApproveSpaceCreationHandler,
|
|
5288
6160
|
CapabilityKeyRegistry,
|
|
5289
6161
|
CapabilityKeyRegistryErrorCodes,
|
|
@@ -5389,7 +6261,7 @@ export {
|
|
|
5389
6261
|
utf8Decode as encryptionUtf8Decode,
|
|
5390
6262
|
utf8Encode as encryptionUtf8Encode,
|
|
5391
6263
|
ensureNetworkUsableForDecrypt,
|
|
5392
|
-
|
|
6264
|
+
err5 as err,
|
|
5393
6265
|
expandActionShortNames,
|
|
5394
6266
|
expandPermissionEntries,
|
|
5395
6267
|
expandPermissionEntry,
|
|
@@ -5410,7 +6282,7 @@ export {
|
|
|
5410
6282
|
multiaddrToHttpUrl,
|
|
5411
6283
|
networkDiscoveryKey,
|
|
5412
6284
|
normalizeDefaults,
|
|
5413
|
-
|
|
6285
|
+
ok5 as ok,
|
|
5414
6286
|
openWrappedKey,
|
|
5415
6287
|
parseCanonicalNetworkId,
|
|
5416
6288
|
parseExpiry,
|
|
@@ -5428,7 +6300,7 @@ export {
|
|
|
5428
6300
|
resolveTinyCloudHosts,
|
|
5429
6301
|
resourceCapabilitiesToAbilitiesMap,
|
|
5430
6302
|
resourceCapabilitiesToSpaceAbilitiesMap,
|
|
5431
|
-
|
|
6303
|
+
serviceError5 as serviceError,
|
|
5432
6304
|
signLocationRecord,
|
|
5433
6305
|
submitHostDelegation,
|
|
5434
6306
|
validateClientSession,
|