@tinycloud/node-sdk 2.11.0-beta.9 → 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{core-Bkptd8qZ.d.cts → core-C6Czyr52.d.cts} +233 -8
- package/dist/{core-Bkptd8qZ.d.ts → core-C6Czyr52.d.ts} +233 -8
- package/dist/core.cjs +1120 -97
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1139 -105
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +903 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -3
- package/dist/index.d.ts +85 -3
- package/dist/index.js +917 -108
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -17179,12 +17179,14 @@ import {
|
|
|
17179
17179
|
SharingService,
|
|
17180
17180
|
UnsupportedFeatureError,
|
|
17181
17181
|
makePublicSpaceId,
|
|
17182
|
-
ACCOUNT_REGISTRY_SPACE,
|
|
17182
|
+
ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
|
|
17183
|
+
BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST,
|
|
17183
17184
|
BOOTSTRAP_SESSION_REQUESTS,
|
|
17184
17185
|
SECRET_RECORDS_SCHEMA,
|
|
17185
17186
|
SECRETS_SPACE as SECRETS_SPACE2,
|
|
17186
17187
|
TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
|
|
17187
17188
|
bootstrapSteps,
|
|
17189
|
+
ErrorCodes as ErrorCodes2,
|
|
17188
17190
|
ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
|
|
17189
17191
|
CaveatedDelegationUnsupportedError,
|
|
17190
17192
|
PermissionNotInManifestError,
|
|
@@ -17211,13 +17213,17 @@ import {
|
|
|
17211
17213
|
parseNetworkId as parseNetworkId2,
|
|
17212
17214
|
resolveSecretPath as resolveSecretPath2,
|
|
17213
17215
|
validateOwnerSharePolicyRegistrationBytes,
|
|
17214
|
-
validateShareDeliveryAuthorizationBytes
|
|
17216
|
+
validateShareDeliveryAuthorizationBytes,
|
|
17217
|
+
verifyEip191MessageSignature,
|
|
17218
|
+
signCompactUcanRootAuthorization
|
|
17215
17219
|
} from "@tinycloud/sdk-core";
|
|
17216
17220
|
import {
|
|
17217
17221
|
parsePermissionHint
|
|
17218
17222
|
} from "@tinycloud/sdk-services";
|
|
17219
17223
|
|
|
17220
17224
|
// src/authorization/NodeUserAuthorization.ts
|
|
17225
|
+
import { createHash } from "crypto";
|
|
17226
|
+
import { SiweMessage } from "siwe";
|
|
17221
17227
|
import {
|
|
17222
17228
|
fetchPeerId,
|
|
17223
17229
|
submitHostDelegation,
|
|
@@ -17236,6 +17242,13 @@ import {
|
|
|
17236
17242
|
canonicalizeAddress,
|
|
17237
17243
|
makePkhSpaceId,
|
|
17238
17244
|
pkhDid,
|
|
17245
|
+
extractImmutableSiweFields,
|
|
17246
|
+
diffImmutableSiweFields,
|
|
17247
|
+
extractRecapAttenuations,
|
|
17248
|
+
unauthorizedRecapCapabilities,
|
|
17249
|
+
parseCanonicalRecapResource,
|
|
17250
|
+
ACCOUNT_REGISTRY_SPACE,
|
|
17251
|
+
ACCOUNT_MANIFEST_PERMISSIONS,
|
|
17239
17252
|
KV,
|
|
17240
17253
|
SQL,
|
|
17241
17254
|
DUCKDB,
|
|
@@ -17322,6 +17335,27 @@ var MemorySessionStorage = class {
|
|
|
17322
17335
|
// src/authorization/NodeUserAuthorization.ts
|
|
17323
17336
|
var DECRYPT_ACTION = ENCRYPTION.DECRYPT;
|
|
17324
17337
|
var NETWORK_CREATE_ACTION = ENCRYPTION.NETWORK_CREATE;
|
|
17338
|
+
function canonicalizeOpenKeyManifestJson(value) {
|
|
17339
|
+
const json = JSON.stringify(value);
|
|
17340
|
+
if (json === void 0) {
|
|
17341
|
+
throw new TypeError("manifest is not JSON-serializable");
|
|
17342
|
+
}
|
|
17343
|
+
return canonicalizeJsonValue(JSON.parse(json));
|
|
17344
|
+
}
|
|
17345
|
+
function canonicalizeJsonValue(value) {
|
|
17346
|
+
if (value === null || typeof value !== "object") {
|
|
17347
|
+
return JSON.stringify(value);
|
|
17348
|
+
}
|
|
17349
|
+
if (Array.isArray(value)) {
|
|
17350
|
+
return `[${value.map(canonicalizeJsonValue).join(",")}]`;
|
|
17351
|
+
}
|
|
17352
|
+
return `{${Object.keys(value).sort().map(
|
|
17353
|
+
(key2) => `${JSON.stringify(key2)}:${canonicalizeJsonValue(value[key2])}`
|
|
17354
|
+
).join(",")}}`;
|
|
17355
|
+
}
|
|
17356
|
+
function canonicalOpenKeyManifestSha256Hex(value) {
|
|
17357
|
+
return createHash("sha256").update(canonicalizeOpenKeyManifestJson(value)).digest("hex");
|
|
17358
|
+
}
|
|
17325
17359
|
function didPrincipalMatches(actual, expected) {
|
|
17326
17360
|
try {
|
|
17327
17361
|
return principalDidEquals(actual, expected);
|
|
@@ -17339,6 +17373,16 @@ function addRawAbility(rawAbilities, resource, action) {
|
|
|
17339
17373
|
actions.push(action);
|
|
17340
17374
|
}
|
|
17341
17375
|
}
|
|
17376
|
+
function cloneAbilitiesMap(actions) {
|
|
17377
|
+
return Object.fromEntries(
|
|
17378
|
+
Object.entries(actions).map(([service, paths]) => [
|
|
17379
|
+
service,
|
|
17380
|
+
Object.fromEntries(
|
|
17381
|
+
Object.entries(paths).map(([path, pathActions]) => [path, [...pathActions]])
|
|
17382
|
+
)
|
|
17383
|
+
])
|
|
17384
|
+
);
|
|
17385
|
+
}
|
|
17342
17386
|
var NodeUserAuthorization = class {
|
|
17343
17387
|
constructor(config) {
|
|
17344
17388
|
this.activeSessionKeyId = "default";
|
|
@@ -17610,23 +17654,41 @@ var NodeUserAuthorization = class {
|
|
|
17610
17654
|
const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
|
|
17611
17655
|
const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
|
|
17612
17656
|
const secretsSpaceId = makePkhSpaceId(address, chainId, "secrets");
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17622
|
-
|
|
17623
|
-
|
|
17624
|
-
|
|
17625
|
-
|
|
17626
|
-
|
|
17657
|
+
const primaryActions = this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE ? cloneAbilitiesMap(this.defaultActions) : this.defaultActions;
|
|
17658
|
+
const spaceAbilities2 = {
|
|
17659
|
+
[primarySpaceId2]: primaryActions,
|
|
17660
|
+
[secretsSpaceId]: {
|
|
17661
|
+
kv: {
|
|
17662
|
+
"vault/secrets/": [
|
|
17663
|
+
KV.GET,
|
|
17664
|
+
KV.PUT,
|
|
17665
|
+
KV.DEL,
|
|
17666
|
+
KV.LIST,
|
|
17667
|
+
KV.METADATA
|
|
17668
|
+
]
|
|
17669
|
+
}
|
|
17670
|
+
}
|
|
17671
|
+
};
|
|
17672
|
+
if (this.includeAccountRegistryPermissions && this.spacePrefix !== ACCOUNT_REGISTRY_SPACE) {
|
|
17673
|
+
spaceAbilities2[makePkhSpaceId(address, chainId, ACCOUNT_REGISTRY_SPACE)] = resourceCapabilitiesToAbilitiesMap(ACCOUNT_MANIFEST_PERMISSIONS);
|
|
17674
|
+
} else if (this.includeAccountRegistryPermissions) {
|
|
17675
|
+
const accountAbilities = resourceCapabilitiesToAbilitiesMap(
|
|
17676
|
+
ACCOUNT_MANIFEST_PERMISSIONS
|
|
17677
|
+
);
|
|
17678
|
+
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
17679
|
+
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
17680
|
+
for (const [path, actions] of Object.entries(paths)) {
|
|
17681
|
+
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
17682
|
+
for (const action of actions) {
|
|
17683
|
+
if (!existingActions.includes(action)) existingActions.push(action);
|
|
17627
17684
|
}
|
|
17628
17685
|
}
|
|
17629
|
-
}
|
|
17686
|
+
}
|
|
17687
|
+
}
|
|
17688
|
+
return {
|
|
17689
|
+
abilities: primaryActions,
|
|
17690
|
+
spaceId: primarySpaceId2,
|
|
17691
|
+
spaceAbilities: spaceAbilities2,
|
|
17630
17692
|
rawAbilities: {
|
|
17631
17693
|
[defaultNetworkId]: [DECRYPT_ACTION, NETWORK_CREATE_ACTION]
|
|
17632
17694
|
}
|
|
@@ -18207,6 +18269,398 @@ var NodeUserAuthorization = class {
|
|
|
18207
18269
|
await this.ensureSpaceExists();
|
|
18208
18270
|
return clientSession;
|
|
18209
18271
|
}
|
|
18272
|
+
/**
|
|
18273
|
+
* End-to-end sign-in via OpenKey `authorizeTinyCloud()` (Sol CRITICAL-4).
|
|
18274
|
+
*
|
|
18275
|
+
* This is the production entry point that wires
|
|
18276
|
+
* {@link prepareSessionForSigning} → OpenKey `authorizeTinyCloud()` →
|
|
18277
|
+
* {@link signInWithOpenKeyResult} into one call. Consumers pass in a
|
|
18278
|
+
* caller-provided `authorizeFn` (typically a thin wrapper around
|
|
18279
|
+
* `openkey.authorizeTinyCloud()`) so this method has no direct
|
|
18280
|
+
* dependency on the OpenKey SDK — it works with any transport that
|
|
18281
|
+
* conforms to the versioned protocol.
|
|
18282
|
+
*
|
|
18283
|
+
* The `authorizeFn` MUST:
|
|
18284
|
+
* 1. Send the prepared SIWE bytes to OpenKey.
|
|
18285
|
+
* 2. Return the exact `TinyCloudAuthorizationResultV1` shape (with
|
|
18286
|
+
* `signedMessage` — the bytes the signature actually verifies
|
|
18287
|
+
* against — plus `selectedActionKeys` and `permissions`).
|
|
18288
|
+
*
|
|
18289
|
+
* NodeUserAuthorization then completes the session with `signedMessage`
|
|
18290
|
+
* (never the prepared bytes), verifying the signature, the immutable
|
|
18291
|
+
* SIWE fields, and every subset invariant enforced by
|
|
18292
|
+
* {@link signInWithOpenKeyResult}. This preserves the exact-byte
|
|
18293
|
+
* guarantee: if the user did NOT narrow, the returned `signedMessage`
|
|
18294
|
+
* equals the prepared SIWE byte-for-byte; if the user narrowed, only
|
|
18295
|
+
* the ReCap block differs and every immutable header field survives.
|
|
18296
|
+
*
|
|
18297
|
+
* Legacy exact-byte {@link signIn} callers are unaffected — this method
|
|
18298
|
+
* is opt-in.
|
|
18299
|
+
*
|
|
18300
|
+
* @param authorizeFn Callback that invokes the versioned OpenKey
|
|
18301
|
+
* `authorizeTinyCloud()` protocol and returns the result. Receives
|
|
18302
|
+
* the prepared SIWE bytes plus a `host` hint the widget should bind
|
|
18303
|
+
* into its /authorize-sign-prepare context.
|
|
18304
|
+
* @param options Optional overrides for the OpenKey request.
|
|
18305
|
+
*/
|
|
18306
|
+
async signInWithOpenKey(authorizeFn, options) {
|
|
18307
|
+
if (options?.host !== void 0) {
|
|
18308
|
+
const overrideHost = options.host.trim();
|
|
18309
|
+
if (!overrideHost) {
|
|
18310
|
+
throw new Error("OpenKey authorization host must not be empty");
|
|
18311
|
+
}
|
|
18312
|
+
this.tinycloudHosts = [overrideHost];
|
|
18313
|
+
}
|
|
18314
|
+
const signingAddress = canonicalizeAddress(await this.signer.getAddress());
|
|
18315
|
+
const signingChainId = await this.signer.getChainId();
|
|
18316
|
+
await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
|
|
18317
|
+
const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
|
|
18318
|
+
const presentation = this.buildPresentationEnvelope(options?.reason);
|
|
18319
|
+
const hostHint = this.primaryTinyCloudHost;
|
|
18320
|
+
const result = await authorizeFn({
|
|
18321
|
+
protocolVersion: 1,
|
|
18322
|
+
siwe: prepared.siwe,
|
|
18323
|
+
jwk,
|
|
18324
|
+
host: hostHint,
|
|
18325
|
+
keyId: options?.openkeyKeyId,
|
|
18326
|
+
presentation
|
|
18327
|
+
});
|
|
18328
|
+
return this.signInWithOpenKeyResult(result, prepared, keyId, jwk);
|
|
18329
|
+
}
|
|
18330
|
+
/**
|
|
18331
|
+
* Build a `CapabilityPresentationEnvelopeV1` from `this._manifest`. The
|
|
18332
|
+
* envelope is display-only — the OpenKey widget renders it under an
|
|
18333
|
+
* honest trust label, and the server /authorize-sign-prepare route
|
|
18334
|
+
* decides whether to upgrade the label to `origin-bound` by fetching
|
|
18335
|
+
* the well-known manifest and comparing its digest to
|
|
18336
|
+
* `manifestDigest`.
|
|
18337
|
+
*
|
|
18338
|
+
* Returns `undefined` when no manifest is set — the widget renders as
|
|
18339
|
+
* "no manifest supplied" (unsigned).
|
|
18340
|
+
*
|
|
18341
|
+
* The digest is a deterministic SHA-256 over canonical JSON of the
|
|
18342
|
+
* first manifest payload; OpenKey parses the fetched well-known manifest
|
|
18343
|
+
* and applies the same canonicalization before comparing digests.
|
|
18344
|
+
*/
|
|
18345
|
+
buildPresentationEnvelope(reason) {
|
|
18346
|
+
const manifests = Array.isArray(this._manifest) ? this._manifest : this._manifest ? [this._manifest] : [];
|
|
18347
|
+
if (manifests.length === 0 && !reason) {
|
|
18348
|
+
return void 0;
|
|
18349
|
+
}
|
|
18350
|
+
const primary = manifests[0];
|
|
18351
|
+
let displayName;
|
|
18352
|
+
let manifestId;
|
|
18353
|
+
let manifestDigest;
|
|
18354
|
+
const envelopeManifests = [];
|
|
18355
|
+
for (const m of manifests) {
|
|
18356
|
+
envelopeManifests.push({
|
|
18357
|
+
name: m.name,
|
|
18358
|
+
appId: m.app_id,
|
|
18359
|
+
// Deep-copy to prevent later mutations from affecting the digest.
|
|
18360
|
+
payload: JSON.parse(JSON.stringify(m))
|
|
18361
|
+
});
|
|
18362
|
+
}
|
|
18363
|
+
if (primary) {
|
|
18364
|
+
displayName = primary.name;
|
|
18365
|
+
manifestId = primary.app_id;
|
|
18366
|
+
manifestDigest = canonicalOpenKeyManifestSha256Hex(primary);
|
|
18367
|
+
}
|
|
18368
|
+
return {
|
|
18369
|
+
protocolVersion: 1,
|
|
18370
|
+
displayName,
|
|
18371
|
+
reason: reason && reason.length > 0 ? reason : void 0,
|
|
18372
|
+
manifestId,
|
|
18373
|
+
manifestDigest,
|
|
18374
|
+
manifests: envelopeManifests.length > 0 ? envelopeManifests : void 0
|
|
18375
|
+
};
|
|
18376
|
+
}
|
|
18377
|
+
/**
|
|
18378
|
+
* Complete sign-in with a versioned OpenKey authorization result.
|
|
18379
|
+
*
|
|
18380
|
+
* Where `signInWithPreparedSession` completes with a signature over the
|
|
18381
|
+
* ORIGINAL prepared SIWE, this method completes with the SIWE bytes the
|
|
18382
|
+
* OpenKey widget actually presented and signed (`signedMessage`). It
|
|
18383
|
+
* exists because the versioned authorizeTinyCloud protocol lets the user
|
|
18384
|
+
* narrow capabilities; the returned bytes may differ from the original
|
|
18385
|
+
* request and are the only ones the signature actually verifies against.
|
|
18386
|
+
*
|
|
18387
|
+
* The `result` parameter is validated at the trust boundary via
|
|
18388
|
+
* `validateAuthorizationResultV1` (in sdk-core). Callers should validate
|
|
18389
|
+
* BEFORE calling this method so unknown/missing fields are rejected
|
|
18390
|
+
* before any session state is created.
|
|
18391
|
+
*
|
|
18392
|
+
* Validation performed here (defence-in-depth: even a "valid" v1 payload
|
|
18393
|
+
* must clear these checks before any session state is created):
|
|
18394
|
+
* 1. `result.address` matches the local signer's address.
|
|
18395
|
+
* 2. `result.signature` recovers to `result.address` when verified against
|
|
18396
|
+
* `result.signedMessage`.
|
|
18397
|
+
* 3. Every immutable SIWE header field (domain, address, URI, version,
|
|
18398
|
+
* chainId, nonce, issuedAt) in the original prepared SIWE is preserved
|
|
18399
|
+
* byte-for-byte in `result.signedMessage`. `statement` is NOT in this
|
|
18400
|
+
* list because the WASM renders it from the ReCap contents; it MUST
|
|
18401
|
+
* change when the ReCap is narrowed.
|
|
18402
|
+
* 4. Every capability in `result.signedMessage`'s ReCap block was present
|
|
18403
|
+
* in the original prepared SIWE — narrowing OK, broadening rejected.
|
|
18404
|
+
* 5. Every `result.selectedActionKeys` entry corresponds to a
|
|
18405
|
+
* (resource, action) pair in the returned SIWE.
|
|
18406
|
+
*/
|
|
18407
|
+
async signInWithOpenKeyResult(result, prepared, keyId, jwk) {
|
|
18408
|
+
if (result.protocolVersion !== 1) {
|
|
18409
|
+
throw new Error(`Unsupported OpenKey protocol version ${result.protocolVersion}`);
|
|
18410
|
+
}
|
|
18411
|
+
if (!result.signedMessage) {
|
|
18412
|
+
throw new Error("OpenKey result must include signedMessage");
|
|
18413
|
+
}
|
|
18414
|
+
if (!prepared.siwe) {
|
|
18415
|
+
throw new Error(
|
|
18416
|
+
"signInWithOpenKeyResult requires prepared.siwe (the SDK-generated SIWE the sign-in was proposed with)"
|
|
18417
|
+
);
|
|
18418
|
+
}
|
|
18419
|
+
const expectedAddress = canonicalizeAddress(await this.signer.getAddress());
|
|
18420
|
+
if (canonicalizeAddress(result.address) !== expectedAddress) {
|
|
18421
|
+
throw new Error(
|
|
18422
|
+
`OpenKey returned signature from ${result.address} but expected ${expectedAddress}`
|
|
18423
|
+
);
|
|
18424
|
+
}
|
|
18425
|
+
let parsedSignedSiwe;
|
|
18426
|
+
try {
|
|
18427
|
+
parsedSignedSiwe = new SiweMessage(result.signedMessage);
|
|
18428
|
+
} catch (err) {
|
|
18429
|
+
throw new Error(
|
|
18430
|
+
`OpenKey signedMessage is not a parseable SIWE: ${err.message}`
|
|
18431
|
+
);
|
|
18432
|
+
}
|
|
18433
|
+
const verifyResult = await parsedSignedSiwe.verify(
|
|
18434
|
+
{ signature: result.signature },
|
|
18435
|
+
{ suppressExceptions: true }
|
|
18436
|
+
);
|
|
18437
|
+
if (!verifyResult.success) {
|
|
18438
|
+
const errData = verifyResult.error;
|
|
18439
|
+
const type = errData && typeof errData === "object" && "type" in errData ? errData.type : void 0;
|
|
18440
|
+
const details = errData && typeof errData === "object" && "expected" in errData ? ` (expected=${errData.expected}, received=${errData.received})` : "";
|
|
18441
|
+
throw new Error(
|
|
18442
|
+
`OpenKey signature verification failed${type ? `: ${type}` : ""}${details}`
|
|
18443
|
+
);
|
|
18444
|
+
}
|
|
18445
|
+
if (canonicalizeAddress(parsedSignedSiwe.address) !== expectedAddress) {
|
|
18446
|
+
throw new Error(
|
|
18447
|
+
`OpenKey signedMessage.address (${parsedSignedSiwe.address}) does not match local signer address (${expectedAddress})`
|
|
18448
|
+
);
|
|
18449
|
+
}
|
|
18450
|
+
const originalHasRecap = /(?:^|\n)-\s+urn:recap:/i.test(prepared.siwe);
|
|
18451
|
+
const originalFields = extractImmutableSiweFields(prepared.siwe);
|
|
18452
|
+
const signedFields = extractImmutableSiweFields(result.signedMessage);
|
|
18453
|
+
const changedFields = diffImmutableSiweFields(originalFields, signedFields, {
|
|
18454
|
+
originalHasRecap
|
|
18455
|
+
});
|
|
18456
|
+
if (changedFields.length > 0) {
|
|
18457
|
+
throw new Error(
|
|
18458
|
+
`OpenKey signedMessage altered immutable SIWE fields: ${changedFields.join(", ")}`
|
|
18459
|
+
);
|
|
18460
|
+
}
|
|
18461
|
+
let originalCaps;
|
|
18462
|
+
let signedCaps;
|
|
18463
|
+
try {
|
|
18464
|
+
originalCaps = extractRecapAttenuations(prepared.siwe);
|
|
18465
|
+
} catch (err) {
|
|
18466
|
+
throw new Error(
|
|
18467
|
+
`Failed to decode prepared SIWE recap: ${err.message}`
|
|
18468
|
+
);
|
|
18469
|
+
}
|
|
18470
|
+
try {
|
|
18471
|
+
signedCaps = extractRecapAttenuations(result.signedMessage);
|
|
18472
|
+
} catch (err) {
|
|
18473
|
+
throw new Error(
|
|
18474
|
+
`Failed to decode OpenKey signedMessage recap: ${err.message}`
|
|
18475
|
+
);
|
|
18476
|
+
}
|
|
18477
|
+
const unauthorized = unauthorizedRecapCapabilities(signedCaps, originalCaps);
|
|
18478
|
+
if (unauthorized.length > 0) {
|
|
18479
|
+
const previewLimit = 5;
|
|
18480
|
+
const preview = unauthorized.slice(0, previewLimit).map((u) => `${u.resource}::${u.action}`).join(", ");
|
|
18481
|
+
const overflow = unauthorized.length > previewLimit ? ` (and ${unauthorized.length - previewLimit} more)` : "";
|
|
18482
|
+
throw new Error(
|
|
18483
|
+
`OpenKey signedMessage broadened authorization beyond the original request: ${preview}${overflow}`
|
|
18484
|
+
);
|
|
18485
|
+
}
|
|
18486
|
+
if (!result.signedMessage.includes(prepared.spaceId)) {
|
|
18487
|
+
throw new Error(
|
|
18488
|
+
`OpenKey signedMessage does not reference the expected spaceId ${prepared.spaceId}`
|
|
18489
|
+
);
|
|
18490
|
+
}
|
|
18491
|
+
const grantedPairs = /* @__PURE__ */ new Set();
|
|
18492
|
+
const grantedFourPartIndex = /* @__PURE__ */ new Map();
|
|
18493
|
+
for (const [resource, actions] of Object.entries(signedCaps)) {
|
|
18494
|
+
const { space, path } = parseCanonicalRecapResource(resource);
|
|
18495
|
+
for (const action of Object.keys(actions)) {
|
|
18496
|
+
grantedPairs.add(`${resource}\0${action}`);
|
|
18497
|
+
const slashIdx = action.indexOf("/");
|
|
18498
|
+
const service = slashIdx > 0 ? action.slice(0, slashIdx) : "";
|
|
18499
|
+
if (service) {
|
|
18500
|
+
grantedFourPartIndex.set(
|
|
18501
|
+
`${service}\0${space}\0${path}\0${action}`,
|
|
18502
|
+
`${resource}\0${action}`
|
|
18503
|
+
);
|
|
18504
|
+
}
|
|
18505
|
+
}
|
|
18506
|
+
}
|
|
18507
|
+
const isStructurallyRequired = (action) => action === "tinycloud.capabilities/read" || action === "capabilities/read";
|
|
18508
|
+
const nonRequiredGrantedPairs = /* @__PURE__ */ new Set();
|
|
18509
|
+
for (const pair of grantedPairs) {
|
|
18510
|
+
const idx = pair.indexOf("\0");
|
|
18511
|
+
const action = idx >= 0 ? pair.slice(idx + 1) : pair;
|
|
18512
|
+
if (!isStructurallyRequired(action)) {
|
|
18513
|
+
nonRequiredGrantedPairs.add(pair);
|
|
18514
|
+
}
|
|
18515
|
+
}
|
|
18516
|
+
const allSignedGrantedPairs = new Set(grantedPairs);
|
|
18517
|
+
const seenRawKeys = /* @__PURE__ */ new Set();
|
|
18518
|
+
for (const rawKey of result.selectedActionKeys) {
|
|
18519
|
+
if (seenRawKeys.has(rawKey)) {
|
|
18520
|
+
throw new Error(
|
|
18521
|
+
`OpenKey selectedActionKeys contains duplicate entry ${rawKey}`
|
|
18522
|
+
);
|
|
18523
|
+
}
|
|
18524
|
+
seenRawKeys.add(rawKey);
|
|
18525
|
+
}
|
|
18526
|
+
const selectedResourceActionPairs = /* @__PURE__ */ new Set();
|
|
18527
|
+
for (const rawKey of result.selectedActionKeys) {
|
|
18528
|
+
const parts = rawKey.split("\0");
|
|
18529
|
+
if (parts.length !== 4) {
|
|
18530
|
+
throw new Error(
|
|
18531
|
+
`OpenKey selectedActionKeys entry is malformed (expected canonical 4-part service\\0space\\0path\\0ability): ${rawKey}`
|
|
18532
|
+
);
|
|
18533
|
+
}
|
|
18534
|
+
const canonical = grantedFourPartIndex.get(rawKey);
|
|
18535
|
+
if (!canonical) {
|
|
18536
|
+
throw new Error(
|
|
18537
|
+
`OpenKey selectedActionKeys entry ${rawKey} is not covered by any granted capability`
|
|
18538
|
+
);
|
|
18539
|
+
}
|
|
18540
|
+
selectedResourceActionPairs.add(canonical);
|
|
18541
|
+
}
|
|
18542
|
+
if (nonRequiredGrantedPairs.size > 0) {
|
|
18543
|
+
const missingPairs = [];
|
|
18544
|
+
for (const pair of nonRequiredGrantedPairs) {
|
|
18545
|
+
if (!selectedResourceActionPairs.has(pair)) {
|
|
18546
|
+
missingPairs.push(pair);
|
|
18547
|
+
}
|
|
18548
|
+
}
|
|
18549
|
+
if (missingPairs.length > 0) {
|
|
18550
|
+
const previewLimit = 5;
|
|
18551
|
+
const preview = missingPairs.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
|
|
18552
|
+
const overflow = missingPairs.length > previewLimit ? ` (and ${missingPairs.length - previewLimit} more)` : "";
|
|
18553
|
+
throw new Error(
|
|
18554
|
+
`OpenKey selectedActionKeys is missing entries for signedMessage capabilities: ${preview}${overflow}`
|
|
18555
|
+
);
|
|
18556
|
+
}
|
|
18557
|
+
}
|
|
18558
|
+
if (allSignedGrantedPairs.size > 0 && result.permissions.length === 0) {
|
|
18559
|
+
throw new Error(
|
|
18560
|
+
"OpenKey permissions is empty but signedMessage carries capabilities \u2014 refusing to accept a session without an authoritative grant list"
|
|
18561
|
+
);
|
|
18562
|
+
}
|
|
18563
|
+
const permissionCanonicalKeys = /* @__PURE__ */ new Set();
|
|
18564
|
+
const permissionPairsCovered = /* @__PURE__ */ new Set();
|
|
18565
|
+
for (const perm of result.permissions) {
|
|
18566
|
+
const { service, space, path, actions } = perm;
|
|
18567
|
+
if (typeof service !== "string" || typeof space !== "string" || typeof path !== "string") {
|
|
18568
|
+
throw new Error(
|
|
18569
|
+
`OpenKey permissions entry has non-string service/space/path (service=${service}, space=${space}, path=${path})`
|
|
18570
|
+
);
|
|
18571
|
+
}
|
|
18572
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
18573
|
+
throw new Error(
|
|
18574
|
+
`OpenKey permissions entry (${service} ${space} ${path}) has empty actions \u2014 a capability-bearing entry must list at least one action`
|
|
18575
|
+
);
|
|
18576
|
+
}
|
|
18577
|
+
const canonicalKey = `${service}\0${space}\0${path}`;
|
|
18578
|
+
if (permissionCanonicalKeys.has(canonicalKey)) {
|
|
18579
|
+
throw new Error(
|
|
18580
|
+
`OpenKey permissions has duplicate entry for (${service} ${space} ${path})`
|
|
18581
|
+
);
|
|
18582
|
+
}
|
|
18583
|
+
permissionCanonicalKeys.add(canonicalKey);
|
|
18584
|
+
let expectedResource;
|
|
18585
|
+
if (space.startsWith("tinycloud:")) {
|
|
18586
|
+
const shortService = service.startsWith("tinycloud.") ? service.slice("tinycloud.".length) : service;
|
|
18587
|
+
expectedResource = path ? `${space}/${shortService}/${path}` : `${space}/${shortService}`;
|
|
18588
|
+
} else {
|
|
18589
|
+
if (path) {
|
|
18590
|
+
throw new Error(
|
|
18591
|
+
`OpenKey permissions entry (${service} ${space} ${path}) carries a path on a non-tinycloud: resource URI, which is not a recognized encoding`
|
|
18592
|
+
);
|
|
18593
|
+
}
|
|
18594
|
+
expectedResource = space;
|
|
18595
|
+
}
|
|
18596
|
+
let matchedResource;
|
|
18597
|
+
if (signedCaps[expectedResource] !== void 0) {
|
|
18598
|
+
matchedResource = expectedResource;
|
|
18599
|
+
} else {
|
|
18600
|
+
throw new Error(
|
|
18601
|
+
`OpenKey permissions entry (${service} ${space} ${path}) claims grants not confirmed in signedMessage capabilities`
|
|
18602
|
+
);
|
|
18603
|
+
}
|
|
18604
|
+
const grantedActionsForResource = new Set(
|
|
18605
|
+
Object.keys(signedCaps[matchedResource])
|
|
18606
|
+
);
|
|
18607
|
+
for (const action of actions) {
|
|
18608
|
+
if (typeof action !== "string" || !action) {
|
|
18609
|
+
throw new Error(
|
|
18610
|
+
`OpenKey permissions entry (${service} ${space} ${path}) has non-string action ${String(action)}`
|
|
18611
|
+
);
|
|
18612
|
+
}
|
|
18613
|
+
if (!grantedActionsForResource.has(action)) {
|
|
18614
|
+
throw new Error(
|
|
18615
|
+
`OpenKey permissions entry (${service} ${space} ${path}) claims action ${action} not present in signedMessage capabilities`
|
|
18616
|
+
);
|
|
18617
|
+
}
|
|
18618
|
+
permissionPairsCovered.add(`${matchedResource}\0${action}`);
|
|
18619
|
+
}
|
|
18620
|
+
}
|
|
18621
|
+
if (allSignedGrantedPairs.size > 0) {
|
|
18622
|
+
const missingFromPerms = [];
|
|
18623
|
+
for (const pair of allSignedGrantedPairs) {
|
|
18624
|
+
if (!permissionPairsCovered.has(pair)) {
|
|
18625
|
+
missingFromPerms.push(pair);
|
|
18626
|
+
}
|
|
18627
|
+
}
|
|
18628
|
+
if (missingFromPerms.length > 0) {
|
|
18629
|
+
const previewLimit = 5;
|
|
18630
|
+
const preview = missingFromPerms.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
|
|
18631
|
+
const overflow = missingFromPerms.length > previewLimit ? ` (and ${missingFromPerms.length - previewLimit} more)` : "";
|
|
18632
|
+
throw new Error(
|
|
18633
|
+
`OpenKey permissions is missing entries for signedMessage capabilities: ${preview}${overflow}`
|
|
18634
|
+
);
|
|
18635
|
+
}
|
|
18636
|
+
const extrasInPerms = [];
|
|
18637
|
+
for (const pair of permissionPairsCovered) {
|
|
18638
|
+
if (!allSignedGrantedPairs.has(pair)) {
|
|
18639
|
+
extrasInPerms.push(pair);
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
if (extrasInPerms.length > 0) {
|
|
18643
|
+
const previewLimit = 5;
|
|
18644
|
+
const preview = extrasInPerms.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
|
|
18645
|
+
const overflow = extrasInPerms.length > previewLimit ? ` (and ${extrasInPerms.length - previewLimit} more)` : "";
|
|
18646
|
+
throw new Error(
|
|
18647
|
+
`OpenKey permissions contains entries not present in the signed authority: ${preview}${overflow}`
|
|
18648
|
+
);
|
|
18649
|
+
}
|
|
18650
|
+
}
|
|
18651
|
+
void nonRequiredGrantedPairs;
|
|
18652
|
+
return this.signInWithPreparedSession(
|
|
18653
|
+
{
|
|
18654
|
+
siwe: result.signedMessage,
|
|
18655
|
+
jwk: prepared.jwk,
|
|
18656
|
+
spaceId: prepared.spaceId,
|
|
18657
|
+
verificationMethod: prepared.verificationMethod
|
|
18658
|
+
},
|
|
18659
|
+
result.signature,
|
|
18660
|
+
keyId,
|
|
18661
|
+
jwk
|
|
18662
|
+
);
|
|
18663
|
+
}
|
|
18210
18664
|
/**
|
|
18211
18665
|
* Clear persisted session data.
|
|
18212
18666
|
*/
|
|
@@ -18462,7 +18916,7 @@ function createWasmKeyProvider(sessionManager) {
|
|
|
18462
18916
|
// src/delegateToHelpers.ts
|
|
18463
18917
|
import {
|
|
18464
18918
|
parseExpiry,
|
|
18465
|
-
SiweMessage,
|
|
18919
|
+
SiweMessage as SiweMessage2,
|
|
18466
18920
|
EXPIRY as EXPIRY2
|
|
18467
18921
|
} from "@tinycloud/sdk-core";
|
|
18468
18922
|
function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
|
|
@@ -18511,7 +18965,7 @@ function resolveExpiryMs(expiry) {
|
|
|
18511
18965
|
return parseExpiry(expiry);
|
|
18512
18966
|
}
|
|
18513
18967
|
function extractSiweExpiration(siwe) {
|
|
18514
|
-
const parsed = new
|
|
18968
|
+
const parsed = new SiweMessage2(siwe);
|
|
18515
18969
|
if (parsed.expirationTime === void 0 || parsed.expirationTime === null) {
|
|
18516
18970
|
return void 0;
|
|
18517
18971
|
}
|
|
@@ -18713,6 +19167,9 @@ var DEFAULT_HOST = "https://node.tinycloud.xyz";
|
|
|
18713
19167
|
var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
|
|
18714
19168
|
var NETWORK_CREATE_ACTION2 = ENCRYPTION2.NETWORK_CREATE;
|
|
18715
19169
|
var DECRYPT_ACTION2 = ENCRYPTION2.DECRYPT;
|
|
19170
|
+
var BOOTSTRAP_COMPLETION_MARKER_KEY = "system/bootstrap/complete";
|
|
19171
|
+
var BOOTSTRAP_COMPLETION_MARKER_VERSION = 1;
|
|
19172
|
+
var ACCEPTED_MARKER_VERSIONS = [BOOTSTRAP_COMPLETION_MARKER_VERSION];
|
|
18716
19173
|
var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
|
|
18717
19174
|
function isSecretPayload(value) {
|
|
18718
19175
|
return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "createdAt" in value && typeof value.createdAt === "string" && "updatedAt" in value && typeof value.updatedAt === "string";
|
|
@@ -18736,6 +19193,18 @@ var ROOT_DELEGATION_ACTIONS = [
|
|
|
18736
19193
|
DUCKDB2.ALL
|
|
18737
19194
|
];
|
|
18738
19195
|
var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
|
|
19196
|
+
function unifiedRootAttenuation(capabilities) {
|
|
19197
|
+
const attenuation = {};
|
|
19198
|
+
for (const capability of capabilities) {
|
|
19199
|
+
if (attenuation[capability.resource] !== void 0) throw new Error("unified owner root contains a duplicate resource");
|
|
19200
|
+
attenuation[capability.resource] = capability.kind === "encryption" ? { [capability.action]: [{}] } : Object.fromEntries(capability.actions.map((action) => [action, [{
|
|
19201
|
+
type: "xyz.tinycloud.resource/selector",
|
|
19202
|
+
kind: capability.selector,
|
|
19203
|
+
value: capability.resource
|
|
19204
|
+
}]]));
|
|
19205
|
+
}
|
|
19206
|
+
return attenuation;
|
|
19207
|
+
}
|
|
18739
19208
|
function decodeAuthorizationBytes(authorization) {
|
|
18740
19209
|
const encoded = authorization.replace(/^Bearer /i, "");
|
|
18741
19210
|
const match = /^([A-Za-z0-9_-]+)(={1,2})?$/.exec(encoded);
|
|
@@ -18755,6 +19224,9 @@ function decodeAuthorizationBytes(authorization) {
|
|
|
18755
19224
|
function isOpenKeyAutoSignStrategy(strategy) {
|
|
18756
19225
|
return strategy?.openKeyAutoSign === true;
|
|
18757
19226
|
}
|
|
19227
|
+
function openKeyApproval(strategy) {
|
|
19228
|
+
return strategy?.openKeyRequestApproval;
|
|
19229
|
+
}
|
|
18758
19230
|
function isInteractiveSigner(config) {
|
|
18759
19231
|
if (config.privateKey) {
|
|
18760
19232
|
return false;
|
|
@@ -19112,6 +19584,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19112
19584
|
* hosting is confirmed against the session that was active at the time.
|
|
19113
19585
|
*/
|
|
19114
19586
|
this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
|
|
19587
|
+
/** Serializes account-registry writes for the active node instance. */
|
|
19588
|
+
this.accountRegistryTail = Promise.resolve();
|
|
19115
19589
|
/**
|
|
19116
19590
|
* True when the last signIn() detected an interactive signer and skipped
|
|
19117
19591
|
* client-side bootstrap. Apps can read this to know whether bootstrap was
|
|
@@ -19120,9 +19594,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19120
19594
|
this._bootstrapSkipped = false;
|
|
19121
19595
|
/**
|
|
19122
19596
|
* Outcome of the last signIn()'s account-bootstrap attempt. `skipped` is
|
|
19123
|
-
* true when bootstrap did not complete (interactive signer,
|
|
19124
|
-
*
|
|
19125
|
-
*
|
|
19597
|
+
* true when bootstrap did not complete in this invocation (interactive signer,
|
|
19598
|
+
* auto-bootstrap disabled, already provisioned, or a decision/provisioning
|
|
19599
|
+
* step failed). `already-provisioned` means the bootstrap was not run here;
|
|
19600
|
+
* it does not mean a prior provisioning attempt failed to complete.
|
|
19601
|
+
* `reason` carries the cause so apps can surface a "finish account setup"
|
|
19602
|
+
* call-to-action. `warnings` is
|
|
19126
19603
|
* present when bootstrap completed, but one or more steps recovered from
|
|
19127
19604
|
* an ambiguous failure (e.g. a KV batch write reconciled via per-space
|
|
19128
19605
|
* fallback) — clean vs. recovered runs are otherwise byte-identical.
|
|
@@ -19305,7 +19782,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19305
19782
|
nonce: config.nonce,
|
|
19306
19783
|
siweConfig: config.siweConfig,
|
|
19307
19784
|
manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
|
|
19308
|
-
capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
|
|
19785
|
+
capabilityRequest: useBootstrapSignInRequest ? config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
|
|
19309
19786
|
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
|
|
19310
19787
|
});
|
|
19311
19788
|
this.tc = new TinyCloud(this.auth, {
|
|
@@ -19395,6 +19872,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19395
19872
|
get sessionDid() {
|
|
19396
19873
|
return this.sessionManager.getDID(this.sessionKeyId);
|
|
19397
19874
|
}
|
|
19875
|
+
/** Bare did:key principal used as the credential subject and holder binding. */
|
|
19876
|
+
get credentialHolderDid() {
|
|
19877
|
+
return this.sessionDid.split("#", 1)[0];
|
|
19878
|
+
}
|
|
19879
|
+
/** Canonical verification method for the active credential holder key. */
|
|
19880
|
+
get credentialHolderKid() {
|
|
19881
|
+
const holder = this.credentialHolderDid;
|
|
19882
|
+
return `${holder}#${holder.slice("did:key:".length)}`;
|
|
19883
|
+
}
|
|
19398
19884
|
/**
|
|
19399
19885
|
* Return the current session's signed ReCap capabilities after the session
|
|
19400
19886
|
* has been authenticated or restored. This is intentionally distinct from
|
|
@@ -19445,7 +19931,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19445
19931
|
if (!this._address) {
|
|
19446
19932
|
return void 0;
|
|
19447
19933
|
}
|
|
19448
|
-
return this.wasmBindings.makeSpaceId(this._address, this._chainId,
|
|
19934
|
+
return this.wasmBindings.makeSpaceId(this._address, this._chainId, ACCOUNT_REGISTRY_SPACE2);
|
|
19449
19935
|
}
|
|
19450
19936
|
/**
|
|
19451
19937
|
* Account-level application and delegation helpers.
|
|
@@ -19496,6 +19982,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19496
19982
|
"Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
|
|
19497
19983
|
);
|
|
19498
19984
|
}
|
|
19985
|
+
await this.accountRegistryTail;
|
|
19499
19986
|
await this.wasmBindings.ensureInitialized?.();
|
|
19500
19987
|
this._address = canonicalizeAddress2(await this.signer.getAddress());
|
|
19501
19988
|
this._chainId = await this.signer.getChainId();
|
|
@@ -19548,6 +20035,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19548
20035
|
this._bootstrapSkipped = false;
|
|
19549
20036
|
this._bootstrapStatus = { skipped: false };
|
|
19550
20037
|
if (this.config.autoBootstrapAccount === false) {
|
|
20038
|
+
this._bootstrapStatus = { skipped: true, reason: "auto-bootstrap-disabled" };
|
|
19551
20039
|
return false;
|
|
19552
20040
|
}
|
|
19553
20041
|
if (!this.auth || !this._address) {
|
|
@@ -19562,11 +20050,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19562
20050
|
return false;
|
|
19563
20051
|
}
|
|
19564
20052
|
const steps = bootstrapSteps(this._address, this._chainId);
|
|
19565
|
-
if (!await this.isFreshBootstrapAccount(steps)) {
|
|
19566
|
-
return false;
|
|
19567
|
-
}
|
|
19568
20053
|
try {
|
|
19569
|
-
const
|
|
20054
|
+
const decision = await this.resolveBootstrapDecision(steps);
|
|
20055
|
+
if (decision.action === "skip") {
|
|
20056
|
+
this._bootstrapStatus = { skipped: true, reason: "already-provisioned" };
|
|
20057
|
+
return false;
|
|
20058
|
+
}
|
|
20059
|
+
const warnings = await this.runAccountBootstrap(steps, { mode: decision.mode });
|
|
20060
|
+
await this.writeBootstrapCompletionMarker(steps);
|
|
19570
20061
|
if (warnings.length > 0) {
|
|
19571
20062
|
this._bootstrapStatus = { skipped: false, warnings };
|
|
19572
20063
|
this.notificationHandler.warning(
|
|
@@ -19585,32 +20076,92 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19585
20076
|
}
|
|
19586
20077
|
return true;
|
|
19587
20078
|
}
|
|
19588
|
-
async
|
|
20079
|
+
async resolveBootstrapDecision(steps) {
|
|
19589
20080
|
const enshrinedSpaceIds = /* @__PURE__ */ new Set();
|
|
19590
20081
|
for (const step of steps) {
|
|
19591
20082
|
if (step.kind === "session") {
|
|
19592
20083
|
enshrinedSpaceIds.add(step.spaceId);
|
|
19593
20084
|
}
|
|
19594
20085
|
}
|
|
19595
|
-
const
|
|
20086
|
+
const auth = this.auth;
|
|
20087
|
+
if (!auth) {
|
|
20088
|
+
throw new Error("Account bootstrap requires an active wallet session");
|
|
20089
|
+
}
|
|
20090
|
+
const skipped = auth.lastActivationSkippedSpaceIds;
|
|
19596
20091
|
if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
|
|
19597
|
-
return
|
|
20092
|
+
return { action: "run", mode: "fresh" };
|
|
19598
20093
|
}
|
|
19599
20094
|
try {
|
|
19600
|
-
const
|
|
19601
|
-
|
|
19602
|
-
|
|
20095
|
+
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
20096
|
+
const markerPermission = {
|
|
20097
|
+
service: "tinycloud.kv",
|
|
20098
|
+
space: accountSpaceId,
|
|
20099
|
+
path: BOOTSTRAP_COMPLETION_MARKER_KEY,
|
|
20100
|
+
actions: ["get"]
|
|
20101
|
+
};
|
|
20102
|
+
if (!this.hasRuntimePermissions([markerPermission])) {
|
|
20103
|
+
return { action: "run", mode: "repair" };
|
|
19603
20104
|
}
|
|
19604
|
-
|
|
20105
|
+
const marker = await this.readBootstrapCompletionMarker();
|
|
20106
|
+
if (marker.ok) {
|
|
20107
|
+
if (this.isAcceptedBootstrapCompletionMarker(marker.data.data)) {
|
|
20108
|
+
return { action: "skip" };
|
|
20109
|
+
}
|
|
20110
|
+
return { action: "run", mode: "repair" };
|
|
20111
|
+
}
|
|
20112
|
+
if (marker.error.code === ErrorCodes2.KV_NOT_FOUND && this.markerReadIsUnhostedSpace(marker.error.meta)) {
|
|
20113
|
+
return { action: "run", mode: "fresh" };
|
|
20114
|
+
}
|
|
20115
|
+
} catch (err) {
|
|
20116
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
20117
|
+
this.notificationHandler.warning(
|
|
20118
|
+
`Account bootstrap decision failed; attempting one bounded repair: ${reason}`
|
|
20119
|
+
);
|
|
20120
|
+
console.warn(`[TinyCloudNode] account bootstrap decision failed: ${reason}`);
|
|
20121
|
+
return { action: "run", mode: "repair" };
|
|
19605
20122
|
}
|
|
19606
|
-
|
|
19607
|
-
|
|
19608
|
-
|
|
19609
|
-
|
|
19610
|
-
|
|
20123
|
+
return { action: "run", mode: "repair" };
|
|
20124
|
+
}
|
|
20125
|
+
async readBootstrapCompletionMarker() {
|
|
20126
|
+
return this.kvForSpace(this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2)).get(
|
|
20127
|
+
BOOTSTRAP_COMPLETION_MARKER_KEY
|
|
20128
|
+
);
|
|
20129
|
+
}
|
|
20130
|
+
async writeBootstrapCompletionMarker(steps) {
|
|
20131
|
+
if (!this._address) {
|
|
20132
|
+
throw new Error("Bootstrap completion marker requires an active wallet session");
|
|
19611
20133
|
}
|
|
20134
|
+
const canonicalStepIds = new Set(
|
|
20135
|
+
bootstrapSteps(this._address, this._chainId).map((step) => step.id)
|
|
20136
|
+
);
|
|
20137
|
+
const providedStepIds = new Set(steps.map((step) => step.id));
|
|
20138
|
+
if (canonicalStepIds.size !== providedStepIds.size || [...canonicalStepIds].some((stepId) => !providedStepIds.has(stepId))) {
|
|
20139
|
+
throw new Error("Bootstrap completion marker requires the canonical bootstrap step set");
|
|
20140
|
+
}
|
|
20141
|
+
const marker = {
|
|
20142
|
+
v: BOOTSTRAP_COMPLETION_MARKER_VERSION,
|
|
20143
|
+
stepIds: [...canonicalStepIds],
|
|
20144
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20145
|
+
};
|
|
20146
|
+
const written = await this.kvForSpace(this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2)).put(
|
|
20147
|
+
BOOTSTRAP_COMPLETION_MARKER_KEY,
|
|
20148
|
+
marker
|
|
20149
|
+
);
|
|
20150
|
+
if (!written.ok) {
|
|
20151
|
+
throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
|
|
20152
|
+
}
|
|
20153
|
+
}
|
|
20154
|
+
isAcceptedBootstrapCompletionMarker(value) {
|
|
20155
|
+
if (typeof value !== "object" || value === null) return false;
|
|
20156
|
+
const entries = Object.entries(value);
|
|
20157
|
+
const markerValue = (name) => entries.find(([key2]) => key2 === name)?.[1];
|
|
20158
|
+
const version23 = markerValue("v");
|
|
20159
|
+
return typeof version23 === "number" && Number.isInteger(version23) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version23);
|
|
20160
|
+
}
|
|
20161
|
+
markerReadIsUnhostedSpace(meta) {
|
|
20162
|
+
return meta?.status === 404;
|
|
19612
20163
|
}
|
|
19613
|
-
async runAccountBootstrap(steps) {
|
|
20164
|
+
async runAccountBootstrap(steps, { mode } = { mode: "fresh" }) {
|
|
19614
20165
|
if (!this.auth || !this._address) {
|
|
19615
20166
|
throw new Error("Account bootstrap requires an active wallet session");
|
|
19616
20167
|
}
|
|
@@ -19711,9 +20262,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19711
20262
|
if (step.kind === "seed-applications") {
|
|
19712
20263
|
const registered = await this.account.applications.register(
|
|
19713
20264
|
step.manifests.length > 0 ? [...step.manifests] : TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
|
|
19714
|
-
//
|
|
19715
|
-
// manifest-hash pre-read
|
|
19716
|
-
// it guards is an INSERT OR REPLACE and is safe to repeat.
|
|
20265
|
+
// The write is an INSERT OR REPLACE and is safe to repeat during a
|
|
20266
|
+
// repair, so skip the manifest-hash pre-read.
|
|
19717
20267
|
{ assumeUnregistered: true }
|
|
19718
20268
|
);
|
|
19719
20269
|
if (!registered.ok) {
|
|
@@ -19721,7 +20271,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19721
20271
|
}
|
|
19722
20272
|
}
|
|
19723
20273
|
if (step.kind === "encryption-network-create") {
|
|
19724
|
-
await this.ensureEncryptionNetwork(step.networkId, { assumeMissing:
|
|
20274
|
+
await this.ensureEncryptionNetwork(step.networkId, { assumeMissing: mode === "fresh" });
|
|
19725
20275
|
}
|
|
19726
20276
|
if (step.kind === "secret-records-schema") {
|
|
19727
20277
|
const db = this.sqlForSpace(step.spaceId).db(step.database);
|
|
@@ -19904,7 +20454,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19904
20454
|
if (!this.auth || !this.signer) {
|
|
19905
20455
|
throw new Error("Manifest registry write requires wallet mode");
|
|
19906
20456
|
}
|
|
19907
|
-
const accountSpaceId = this.ownedSpaceId(
|
|
20457
|
+
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
19908
20458
|
await this.ensureOwnedSpaceHostedById(accountSpaceId);
|
|
19909
20459
|
const result = await this.account.applications.register(request.manifests);
|
|
19910
20460
|
if (!result.ok) {
|
|
@@ -19914,16 +20464,48 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19914
20464
|
}
|
|
19915
20465
|
}
|
|
19916
20466
|
scheduleAccountRegistrySync() {
|
|
19917
|
-
|
|
19918
|
-
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
20467
|
+
const session = this.currentTinyCloudSession();
|
|
20468
|
+
if (!session || this.pendingAccountRegistrySync?.session === session) {
|
|
20469
|
+
return;
|
|
20470
|
+
}
|
|
20471
|
+
const promise = this.enqueueAccountRegistryOperation(session, async () => {
|
|
20472
|
+
await this.withAccountRegistryRetry(async () => {
|
|
20473
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
20474
|
+
return;
|
|
19924
20475
|
}
|
|
20476
|
+
await this.account.index.ensure();
|
|
20477
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
20478
|
+
return;
|
|
20479
|
+
}
|
|
20480
|
+
await this.writeManifestRegistryRecords();
|
|
20481
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
20482
|
+
return;
|
|
20483
|
+
}
|
|
20484
|
+
if (this.currentSessionCanListSpaces()) {
|
|
20485
|
+
const spaces = await this.account.spaces.syncAccessible();
|
|
20486
|
+
if (!spaces.ok) {
|
|
20487
|
+
throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
|
|
20488
|
+
}
|
|
20489
|
+
}
|
|
20490
|
+
});
|
|
20491
|
+
});
|
|
20492
|
+
this.pendingAccountRegistrySync = { session, promise };
|
|
20493
|
+
const clearPendingSync = () => {
|
|
20494
|
+
if (this.pendingAccountRegistrySync?.promise === promise) {
|
|
20495
|
+
this.pendingAccountRegistrySync = void 0;
|
|
20496
|
+
}
|
|
20497
|
+
};
|
|
20498
|
+
void promise.then(clearPendingSync, clearPendingSync);
|
|
20499
|
+
}
|
|
20500
|
+
enqueueAccountRegistryOperation(session, task) {
|
|
20501
|
+
const operation = this.accountRegistryTail.then(async () => {
|
|
20502
|
+
if (this.currentTinyCloudSession() === session) {
|
|
20503
|
+
await task();
|
|
19925
20504
|
}
|
|
19926
20505
|
});
|
|
20506
|
+
this.accountRegistryTail = operation.catch(() => {
|
|
20507
|
+
});
|
|
20508
|
+
return operation;
|
|
19927
20509
|
}
|
|
19928
20510
|
/**
|
|
19929
20511
|
* Whether the current primary session may invoke `tinycloud.space/list`.
|
|
@@ -20076,7 +20658,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20076
20658
|
* @returns The hosted space URI.
|
|
20077
20659
|
*/
|
|
20078
20660
|
async hostOwnedSpace(name) {
|
|
20079
|
-
|
|
20661
|
+
const session = this.auth?.tinyCloudSession;
|
|
20662
|
+
if (!this.auth || !session) {
|
|
20080
20663
|
throw new Error("Not signed in. Call signIn() first.");
|
|
20081
20664
|
}
|
|
20082
20665
|
const spaceId = this.ownedSpaceId(name);
|
|
@@ -20092,22 +20675,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20092
20675
|
}
|
|
20093
20676
|
const activation = await activateSessionWithHost2(
|
|
20094
20677
|
host,
|
|
20095
|
-
|
|
20678
|
+
session.delegationHeader
|
|
20096
20679
|
);
|
|
20097
20680
|
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
20098
20681
|
throw new Error(
|
|
20099
20682
|
`Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
|
|
20100
20683
|
);
|
|
20101
20684
|
}
|
|
20102
|
-
|
|
20103
|
-
|
|
20104
|
-
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20108
|
-
|
|
20109
|
-
|
|
20110
|
-
|
|
20685
|
+
try {
|
|
20686
|
+
await this.enqueueAccountRegistryOperation(session, async () => {
|
|
20687
|
+
await this.account.spaces.register({
|
|
20688
|
+
spaceId,
|
|
20689
|
+
name,
|
|
20690
|
+
ownerDid: this.did,
|
|
20691
|
+
type: "owned",
|
|
20692
|
+
permissions: ["*"],
|
|
20693
|
+
status: "active"
|
|
20694
|
+
});
|
|
20695
|
+
});
|
|
20696
|
+
} catch {
|
|
20697
|
+
}
|
|
20111
20698
|
return spaceId;
|
|
20112
20699
|
}
|
|
20113
20700
|
/**
|
|
@@ -20142,23 +20729,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20142
20729
|
if (!this.auth || !this.auth.tinyCloudSession) {
|
|
20143
20730
|
throw new Error("Not signed in. Call signIn() first.");
|
|
20144
20731
|
}
|
|
20732
|
+
await this.accountRegistryTail;
|
|
20145
20733
|
const spaceId = this.ownedSpaceId(name);
|
|
20146
20734
|
if (await this.isOwnedSpaceRegistered(spaceId)) {
|
|
20147
20735
|
return spaceId;
|
|
20148
20736
|
}
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
20152
|
-
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
|
|
20156
|
-
permissions: ["*"],
|
|
20157
|
-
status: "active"
|
|
20158
|
-
});
|
|
20159
|
-
} catch {
|
|
20737
|
+
return this.hostOwnedSpace(name);
|
|
20738
|
+
}
|
|
20739
|
+
/** Resolve and authenticate the owner encoded by an owned-space URI. */
|
|
20740
|
+
credentialSpaceOwnerDid(spaceId) {
|
|
20741
|
+
const ownerDid = this.ownerDidFromSpaceId(spaceId);
|
|
20742
|
+
if (ownerDid === void 0 || !didPrincipalMatches2(ownerDid, this.did)) {
|
|
20743
|
+
throw new Error("Credential space owner does not match the active TinyCloud owner");
|
|
20160
20744
|
}
|
|
20161
|
-
return
|
|
20745
|
+
return ownerDid;
|
|
20162
20746
|
}
|
|
20163
20747
|
/**
|
|
20164
20748
|
* Check whether an owned space is already registered/hosted by consulting the
|
|
@@ -20703,6 +21287,106 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20703
21287
|
if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
|
|
20704
21288
|
return signBytesWithJwk(bytes, session.jwk);
|
|
20705
21289
|
}
|
|
21290
|
+
/** Author a proofless Policy/v3 owner root with the authenticated holder key. */
|
|
21291
|
+
async createUnifiedOwnerRoot(input) {
|
|
21292
|
+
const ownerDid = this.credentialHolderDid;
|
|
21293
|
+
if (input.ownerDid !== ownerDid) throw new Error("unified owner root signer does not match owner DID");
|
|
21294
|
+
const facts = {
|
|
21295
|
+
role: input.role,
|
|
21296
|
+
mode: input.role === "policy-authority" ? "policy-source" : "conditional-mint",
|
|
21297
|
+
ownerDid,
|
|
21298
|
+
policyId: input.policyId,
|
|
21299
|
+
policyDigestHex: input.policyDigestHex,
|
|
21300
|
+
policyCid: input.policyCid,
|
|
21301
|
+
contentSourceDigestHex: input.contentSourceDigestHex,
|
|
21302
|
+
capabilityCeilingHashHex: input.capabilityCeilingHashHex,
|
|
21303
|
+
nativeProjectionHashHex: input.nativeProjectionHashHex,
|
|
21304
|
+
nodeAudience: input.nodeAudience,
|
|
21305
|
+
...input.role === "policy-enforcement" ? { enforcerDid: input.audienceDid } : {}
|
|
21306
|
+
};
|
|
21307
|
+
const root = await signCompactUcanRootAuthorization({
|
|
21308
|
+
issuerDid: ownerDid,
|
|
21309
|
+
audienceDid: input.audienceDid,
|
|
21310
|
+
attenuation: unifiedRootAttenuation(input.capabilities),
|
|
21311
|
+
facts: [facts],
|
|
21312
|
+
notBefore: Math.floor(input.notBefore.getTime() / 1e3),
|
|
21313
|
+
expiresAt: Math.floor(input.expiresAt.getTime() / 1e3),
|
|
21314
|
+
nonce: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
|
|
21315
|
+
sign: (bytes) => this.signSessionBytes(bytes)
|
|
21316
|
+
});
|
|
21317
|
+
const kv = input.capabilities.find((capability) => capability.kind === "kv");
|
|
21318
|
+
const resource = kv?.resource ?? "";
|
|
21319
|
+
const marker = resource.indexOf("/kv/");
|
|
21320
|
+
return {
|
|
21321
|
+
cid: root.cid,
|
|
21322
|
+
delegationHeader: { Authorization: root.authorization },
|
|
21323
|
+
delegateDID: input.audienceDid,
|
|
21324
|
+
spaceId: marker === -1 ? "" : resource.slice("tinycloud://".length, marker),
|
|
21325
|
+
path: marker === -1 ? resource : resource.slice(marker + 4),
|
|
21326
|
+
actions: kv?.actions ?? [],
|
|
21327
|
+
expiry: input.expiresAt
|
|
21328
|
+
};
|
|
21329
|
+
}
|
|
21330
|
+
/** Apply only an already-enabled signing policy to the exact credential request. */
|
|
21331
|
+
async autoSignCredentialBytes(bytes) {
|
|
21332
|
+
const strategy = this.config.signStrategy;
|
|
21333
|
+
if (strategy?.type === "auto-sign") return this.signSessionBytes(bytes);
|
|
21334
|
+
if (strategy?.type !== "callback" || !isOpenKeyAutoSignStrategy(strategy)) return void 0;
|
|
21335
|
+
const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
|
|
21336
|
+
if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
|
|
21337
|
+
const request = {
|
|
21338
|
+
address: this.address ?? "",
|
|
21339
|
+
chainId: this.session?.chainId ?? this._chainId,
|
|
21340
|
+
message: new TextDecoder().decode(bytes),
|
|
21341
|
+
type: "message",
|
|
21342
|
+
purpose: "message"
|
|
21343
|
+
};
|
|
21344
|
+
const decision = await strategy.handler(request);
|
|
21345
|
+
if (!decision.approved) {
|
|
21346
|
+
if (decision.needsApproval) return void 0;
|
|
21347
|
+
throw new Error(decision.reason ?? "OpenKey automatic credential signing was rejected");
|
|
21348
|
+
}
|
|
21349
|
+
if (typeof decision.signature !== "string" || !await verifyEip191MessageSignature(
|
|
21350
|
+
request.message,
|
|
21351
|
+
decision.signature,
|
|
21352
|
+
request.address
|
|
21353
|
+
)) {
|
|
21354
|
+
throw new Error("OpenKey credential signature evidence is invalid");
|
|
21355
|
+
}
|
|
21356
|
+
return this.signSessionBytes(bytes);
|
|
21357
|
+
}
|
|
21358
|
+
/** Invoke the configured interactive approval strategy before session signing. */
|
|
21359
|
+
async approveCredentialBytes(bytes) {
|
|
21360
|
+
const strategy = this.config.signStrategy;
|
|
21361
|
+
const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
|
|
21362
|
+
if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
|
|
21363
|
+
const request = {
|
|
21364
|
+
address: this.address ?? "",
|
|
21365
|
+
chainId: this.session?.chainId ?? this._chainId,
|
|
21366
|
+
message: new TextDecoder().decode(bytes),
|
|
21367
|
+
type: "message",
|
|
21368
|
+
purpose: "message"
|
|
21369
|
+
};
|
|
21370
|
+
const approval = isOpenKeyAutoSignStrategy(strategy) ? openKeyApproval(strategy) : strategy?.type === "callback" ? strategy.handler : void 0;
|
|
21371
|
+
if (approval) {
|
|
21372
|
+
const decision = await approval(request);
|
|
21373
|
+
if (!decision.approved) throw new Error(decision.reason ?? "Credential signing was rejected");
|
|
21374
|
+
if (decision.signature !== void 0 && !await verifyEip191MessageSignature(
|
|
21375
|
+
request.message,
|
|
21376
|
+
decision.signature,
|
|
21377
|
+
request.address
|
|
21378
|
+
)) {
|
|
21379
|
+
throw new Error("OpenKey credential signature evidence is invalid");
|
|
21380
|
+
}
|
|
21381
|
+
return this.signSessionBytes(bytes);
|
|
21382
|
+
}
|
|
21383
|
+
if (strategy?.type === "auto-reject") throw new Error("Credential signing was rejected");
|
|
21384
|
+
if (this.config.signer) {
|
|
21385
|
+
await this.config.signer.signMessage(request.message);
|
|
21386
|
+
return this.signSessionBytes(bytes);
|
|
21387
|
+
}
|
|
21388
|
+
throw new Error("Interactive OpenKey approval is not configured");
|
|
21389
|
+
}
|
|
20706
21390
|
/** Bind a session-signed invocation to the canonical service audience. */
|
|
20707
21391
|
async bindInvocationAudience(authorization, audience) {
|
|
20708
21392
|
const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
|
|
@@ -20770,7 +21454,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20770
21454
|
nonce: this.config.nonce,
|
|
20771
21455
|
siweConfig: this.config.siweConfig,
|
|
20772
21456
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
20773
|
-
capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
21457
|
+
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
20774
21458
|
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
20775
21459
|
});
|
|
20776
21460
|
this.tc = new TinyCloud(this.auth, {
|
|
@@ -20823,7 +21507,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20823
21507
|
nonce: this.config.nonce,
|
|
20824
21508
|
siweConfig: this.config.siweConfig,
|
|
20825
21509
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
20826
|
-
capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
21510
|
+
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
20827
21511
|
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
20828
21512
|
});
|
|
20829
21513
|
this.tc = new TinyCloud(this.auth, {
|
|
@@ -23572,35 +24256,44 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23572
24256
|
if (parentDelegation.disableSubDelegation) {
|
|
23573
24257
|
throw new Error("Parent delegation does not allow sub-delegation");
|
|
23574
24258
|
}
|
|
23575
|
-
|
|
24259
|
+
const parentResources = parentDelegation.resources ?? [{
|
|
24260
|
+
service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
|
|
24261
|
+
space: parentDelegation.spaceId,
|
|
24262
|
+
path: parentDelegation.path,
|
|
24263
|
+
actions: parentDelegation.actions
|
|
24264
|
+
}];
|
|
24265
|
+
const requestedResources = params.resources ?? [{
|
|
24266
|
+
service: params.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
|
|
24267
|
+
space: parentDelegation.spaceId,
|
|
24268
|
+
path: params.path,
|
|
24269
|
+
actions: params.actions
|
|
24270
|
+
}];
|
|
24271
|
+
const resourceIsContained = (parent, child) => {
|
|
24272
|
+
const parentEncryption = parent.service === "encryption" || parent.service === "tinycloud.encryption";
|
|
24273
|
+
const childEncryption = child.service === "encryption" || child.service === "tinycloud.encryption";
|
|
24274
|
+
if (parentEncryption || childEncryption) return parentEncryption && childEncryption && parent.path === child.path && child.actions.every((action) => parent.actions.includes(action));
|
|
24275
|
+
const pathContained = parent.path === child.path || parent.path.endsWith("/") && child.path.startsWith(parent.path) || child.path.startsWith(`${parent.path}/`);
|
|
24276
|
+
return pathContained && child.actions.every((action) => parent.actions.includes(action));
|
|
24277
|
+
};
|
|
24278
|
+
if (requestedResources.length === 0 || requestedResources.some((child) => !parentResources.some((parent) => resourceIsContained(parent, child)))) {
|
|
23576
24279
|
throw new Error(
|
|
23577
|
-
|
|
24280
|
+
"Sub-delegation resources exceed the received delegation"
|
|
23578
24281
|
);
|
|
23579
24282
|
}
|
|
23580
|
-
const parentActions = new Set(parentDelegation.actions);
|
|
23581
|
-
for (const action of params.actions) {
|
|
23582
|
-
if (!parentActions.has(action)) {
|
|
23583
|
-
throw new Error(
|
|
23584
|
-
`Sub-delegation action "${action}" is not in parent's actions: ${parentDelegation.actions.join(", ")}`
|
|
23585
|
-
);
|
|
23586
|
-
}
|
|
23587
|
-
}
|
|
23588
24283
|
const now = /* @__PURE__ */ new Date();
|
|
23589
24284
|
const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
|
|
23590
24285
|
const requestedExpiry = new Date(now.getTime() + expiryMs);
|
|
23591
24286
|
const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
|
|
23592
24287
|
const abilities = {};
|
|
23593
|
-
const
|
|
23594
|
-
const
|
|
23595
|
-
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
abilities
|
|
23601
|
-
|
|
23602
|
-
if (duckdbActions.length > 0) {
|
|
23603
|
-
abilities.duckdb = { [params.path]: duckdbActions };
|
|
24288
|
+
const rawAbilities = {};
|
|
24289
|
+
for (const resource of requestedResources) {
|
|
24290
|
+
const service = resource.service.replace(/^tinycloud\./, "");
|
|
24291
|
+
if (service === "encryption") {
|
|
24292
|
+
rawAbilities[resource.path] = [...resource.actions];
|
|
24293
|
+
continue;
|
|
24294
|
+
}
|
|
24295
|
+
abilities[service] ?? (abilities[service] = {});
|
|
24296
|
+
abilities[service][resource.path] = [...resource.actions];
|
|
23604
24297
|
}
|
|
23605
24298
|
const targetHost = parentDelegation.host ?? this.config.host;
|
|
23606
24299
|
const prepared = this.wasmBindings.prepareSession({
|
|
@@ -23612,7 +24305,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23612
24305
|
expirationTime: actualExpiry.toISOString(),
|
|
23613
24306
|
spaceId: parentDelegation.spaceId,
|
|
23614
24307
|
delegateUri: params.delegateDID,
|
|
23615
|
-
parents: [parentDelegation.cid]
|
|
24308
|
+
parents: [parentDelegation.cid],
|
|
24309
|
+
...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
|
|
23616
24310
|
});
|
|
23617
24311
|
const signature2 = await this.signer.signMessage(prepared.siwe);
|
|
23618
24312
|
const subDelegationSession = this.wasmBindings.completeSessionSetup({
|
|
@@ -23630,14 +24324,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23630
24324
|
cid: subDelegationSession.delegationCid,
|
|
23631
24325
|
delegationHeader: subDelegationSession.delegationHeader,
|
|
23632
24326
|
spaceId: parentDelegation.spaceId,
|
|
23633
|
-
path:
|
|
23634
|
-
actions:
|
|
24327
|
+
path: requestedResources[0].path,
|
|
24328
|
+
actions: requestedResources[0].actions,
|
|
23635
24329
|
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
23636
24330
|
expiry: actualExpiry,
|
|
23637
24331
|
delegateDID: params.delegateDID,
|
|
23638
24332
|
ownerAddress: parentDelegation.ownerAddress,
|
|
23639
24333
|
chainId: parentDelegation.chainId,
|
|
23640
|
-
host: targetHost
|
|
24334
|
+
host: targetHost,
|
|
24335
|
+
resources: requestedResources.map((resource) => ({
|
|
24336
|
+
service: resource.service.replace(/^tinycloud\./, ""),
|
|
24337
|
+
space: resource.service.replace(/^tinycloud\./, "") === "encryption" ? "encryption" : resource.space ?? parentDelegation.spaceId,
|
|
24338
|
+
path: resource.path,
|
|
24339
|
+
actions: [...resource.actions]
|
|
24340
|
+
}))
|
|
23641
24341
|
};
|
|
23642
24342
|
}
|
|
23643
24343
|
};
|
|
@@ -23810,10 +24510,54 @@ var FileSessionStorage = class {
|
|
|
23810
24510
|
}
|
|
23811
24511
|
};
|
|
23812
24512
|
|
|
24513
|
+
// src/authorization/openKeyBridge.ts
|
|
24514
|
+
function wireOpenKeyAuthorize(openkey) {
|
|
24515
|
+
return async (input) => {
|
|
24516
|
+
const result = await openkey.authorizeTinyCloud({
|
|
24517
|
+
protocolVersion: 1,
|
|
24518
|
+
siwe: input.siwe,
|
|
24519
|
+
keyId: input.keyId,
|
|
24520
|
+
jwk: input.jwk,
|
|
24521
|
+
host: input.host,
|
|
24522
|
+
// Forward the presentation envelope verbatim — the OpenKey SDK
|
|
24523
|
+
// validates and size-bounds it before use.
|
|
24524
|
+
presentation: input.presentation
|
|
24525
|
+
});
|
|
24526
|
+
if (result.protocolVersion !== 1) {
|
|
24527
|
+
throw new Error(
|
|
24528
|
+
`wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
|
|
24529
|
+
);
|
|
24530
|
+
}
|
|
24531
|
+
if (typeof result.signedMessage !== "string" || !result.signedMessage) {
|
|
24532
|
+
throw new Error(
|
|
24533
|
+
"wireOpenKeyAuthorize: OpenKey returned no signedMessage \u2014 cannot complete a TinyCloud session"
|
|
24534
|
+
);
|
|
24535
|
+
}
|
|
24536
|
+
if (!Array.isArray(result.selectedActionKeys)) {
|
|
24537
|
+
throw new Error(
|
|
24538
|
+
"wireOpenKeyAuthorize: OpenKey returned no selectedActionKeys"
|
|
24539
|
+
);
|
|
24540
|
+
}
|
|
24541
|
+
if (!Array.isArray(result.permissions)) {
|
|
24542
|
+
throw new Error(
|
|
24543
|
+
"wireOpenKeyAuthorize: OpenKey returned no permissions"
|
|
24544
|
+
);
|
|
24545
|
+
}
|
|
24546
|
+
return {
|
|
24547
|
+
protocolVersion: 1,
|
|
24548
|
+
address: result.address,
|
|
24549
|
+
signature: result.signature,
|
|
24550
|
+
signedMessage: result.signedMessage,
|
|
24551
|
+
selectedActionKeys: result.selectedActionKeys,
|
|
24552
|
+
permissions: result.permissions
|
|
24553
|
+
};
|
|
24554
|
+
};
|
|
24555
|
+
}
|
|
24556
|
+
|
|
23813
24557
|
// src/index.ts
|
|
23814
24558
|
import {
|
|
23815
24559
|
ACCOUNT_REGISTRY_PATH,
|
|
23816
|
-
ACCOUNT_REGISTRY_SPACE as
|
|
24560
|
+
ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE3,
|
|
23817
24561
|
DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
|
|
23818
24562
|
DEFAULT_MANIFEST_VERSION,
|
|
23819
24563
|
VAULT_PERMISSION_SERVICE,
|
|
@@ -23836,6 +24580,7 @@ import {
|
|
|
23836
24580
|
// src/delegation.ts
|
|
23837
24581
|
import {
|
|
23838
24582
|
canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
|
|
24583
|
+
decodeBase64Url,
|
|
23839
24584
|
principalDidEquals as principalDidEquals3
|
|
23840
24585
|
} from "@tinycloud/sdk-core";
|
|
23841
24586
|
var SERVICE_BY_SHORT_NAME = {
|
|
@@ -23870,7 +24615,7 @@ function compactUcanPayload(authorization) {
|
|
|
23870
24615
|
}
|
|
23871
24616
|
try {
|
|
23872
24617
|
const payload = JSON.parse(
|
|
23873
|
-
|
|
24618
|
+
new TextDecoder("utf-8", { fatal: true }).decode(decodeBase64Url(parts[1]))
|
|
23874
24619
|
);
|
|
23875
24620
|
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
23876
24621
|
throw new Error("payload is not an object");
|
|
@@ -23921,6 +24666,14 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23921
24666
|
if (typeof payload.aud !== "string" || payload.aud.length === 0) {
|
|
23922
24667
|
throw new Error("Validated runtime delegation is missing a signed audience.");
|
|
23923
24668
|
}
|
|
24669
|
+
if (typeof payload.iss !== "string" || payload.iss.length === 0) {
|
|
24670
|
+
throw new Error("Validated runtime delegation is missing a signed issuer.");
|
|
24671
|
+
}
|
|
24672
|
+
if (!Array.isArray(payload.prf) || payload.prf.some(
|
|
24673
|
+
(proof) => typeof proof !== "string" || proof.length === 0
|
|
24674
|
+
)) {
|
|
24675
|
+
throw new Error("Validated runtime delegation has malformed signed proofs.");
|
|
24676
|
+
}
|
|
23924
24677
|
if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
|
|
23925
24678
|
throw new Error("Validated runtime delegation is missing a signed expiry.");
|
|
23926
24679
|
}
|
|
@@ -23931,6 +24684,11 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23931
24684
|
if (payload.att === null || typeof payload.att !== "object" || Array.isArray(payload.att)) {
|
|
23932
24685
|
throw new Error("Validated runtime delegation is missing a signed attenuation.");
|
|
23933
24686
|
}
|
|
24687
|
+
const fact = Array.isArray(payload.fct) && payload.fct.length > 0 && payload.fct[0] !== null && typeof payload.fct[0] === "object" && !Array.isArray(payload.fct[0]) ? payload.fct[0] : void 0;
|
|
24688
|
+
const remainingRedelegationDepth = fact?.remainingRedelegationDepth;
|
|
24689
|
+
if (remainingRedelegationDepth !== void 0 && (!Number.isInteger(remainingRedelegationDepth) || remainingRedelegationDepth < 0 || remainingRedelegationDepth > 8)) {
|
|
24690
|
+
throw new Error("Validated runtime delegation has invalid signed redelegation depth.");
|
|
24691
|
+
}
|
|
23934
24692
|
const permissions = [];
|
|
23935
24693
|
for (const [resource, abilities] of Object.entries(payload.att)) {
|
|
23936
24694
|
if (abilities === null || typeof abilities !== "object" || Array.isArray(abilities)) {
|
|
@@ -23949,7 +24707,9 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23949
24707
|
));
|
|
23950
24708
|
continue;
|
|
23951
24709
|
}
|
|
23952
|
-
const
|
|
24710
|
+
const nativeMatch = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
|
|
24711
|
+
const policyMatch = /^tinycloud:\/\/([^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
|
|
24712
|
+
const match = nativeMatch ?? policyMatch;
|
|
23953
24713
|
if (!match) {
|
|
23954
24714
|
throw new Error(
|
|
23955
24715
|
`Validated runtime delegation has an unsupported signed resource '${resource}'.`
|
|
@@ -23981,8 +24741,11 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23981
24741
|
...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
|
|
23982
24742
|
}));
|
|
23983
24743
|
return {
|
|
24744
|
+
issuer: payload.iss,
|
|
23984
24745
|
audience: payload.aud,
|
|
23985
24746
|
expiry,
|
|
24747
|
+
proofs: [...payload.prf],
|
|
24748
|
+
...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
|
|
23986
24749
|
permissions: canonicalPermissions,
|
|
23987
24750
|
resources: canonicalResources
|
|
23988
24751
|
};
|
|
@@ -24073,6 +24836,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
24073
24836
|
delegationHeader: { Authorization: authorization },
|
|
24074
24837
|
ownerAddress: delegation.ownerAddress,
|
|
24075
24838
|
chainId: delegation.chainId,
|
|
24839
|
+
delegatorDID: signed2.issuer,
|
|
24840
|
+
...signed2.proofs.length === 1 ? { parentCid: signed2.proofs[0] } : {},
|
|
24841
|
+
...signed2.remainingRedelegationDepth === void 0 ? {} : {
|
|
24842
|
+
allowSubDelegation: signed2.remainingRedelegationDepth > 0,
|
|
24843
|
+
disableSubDelegation: signed2.remainingRedelegationDepth === 0
|
|
24844
|
+
},
|
|
24845
|
+
authHeader: authorization,
|
|
24076
24846
|
spaceId: signedSpace ?? delegation.spaceId,
|
|
24077
24847
|
path: primary.path,
|
|
24078
24848
|
actions: [...primary.actions],
|
|
@@ -24111,6 +24881,40 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
24111
24881
|
host: normalizedHost(installed.host ?? host)
|
|
24112
24882
|
};
|
|
24113
24883
|
}
|
|
24884
|
+
async function activateCompactRuntimeDelegation(node, input) {
|
|
24885
|
+
const authorization = input.authorization;
|
|
24886
|
+
const cid = node.computeDelegationCid(authorization);
|
|
24887
|
+
if (cid !== input.cid) {
|
|
24888
|
+
throw new Error("Compact runtime delegation CID does not match authorization bytes.");
|
|
24889
|
+
}
|
|
24890
|
+
const signed2 = signedAuthorityFromCompactUcan(authorization);
|
|
24891
|
+
const primary = signed2.resources[0];
|
|
24892
|
+
const signedSpace = signed2.permissions.find(
|
|
24893
|
+
(permission) => permission.service !== "tinycloud.encryption"
|
|
24894
|
+
)?.space;
|
|
24895
|
+
const delegation = {
|
|
24896
|
+
cid,
|
|
24897
|
+
delegationHeader: { Authorization: authorization },
|
|
24898
|
+
ownerAddress: input.ownerAddress,
|
|
24899
|
+
chainId: input.chainId,
|
|
24900
|
+
delegatorDID: signed2.issuer,
|
|
24901
|
+
authHeader: authorization,
|
|
24902
|
+
spaceId: signedSpace ?? primary.space,
|
|
24903
|
+
path: primary.path,
|
|
24904
|
+
actions: [...primary.actions],
|
|
24905
|
+
...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
|
|
24906
|
+
resources: signed2.resources.map((resource) => ({
|
|
24907
|
+
...resource,
|
|
24908
|
+
actions: [...resource.actions]
|
|
24909
|
+
})),
|
|
24910
|
+
expiry: new Date(signed2.expiry),
|
|
24911
|
+
delegateDID: signed2.audience,
|
|
24912
|
+
host: normalizedHost(input.host)
|
|
24913
|
+
};
|
|
24914
|
+
return activateValidatedRuntimeDelegation(node, delegation, {
|
|
24915
|
+
host: input.host
|
|
24916
|
+
});
|
|
24917
|
+
}
|
|
24114
24918
|
async function grantAuthRequest(authority, request, options) {
|
|
24115
24919
|
if (request.kind !== "tinycloud.auth.request") {
|
|
24116
24920
|
throw new Error(
|
|
@@ -24247,10 +25051,13 @@ import {
|
|
|
24247
25051
|
} from "@tinycloud/sdk-core";
|
|
24248
25052
|
import { ServiceContext as ServiceContext3 } from "@tinycloud/sdk-core";
|
|
24249
25053
|
export {
|
|
25054
|
+
ACCEPTED_MARKER_VERSIONS,
|
|
24250
25055
|
ACCOUNT_REGISTRY_PATH,
|
|
24251
|
-
|
|
25056
|
+
ACCOUNT_REGISTRY_SPACE3 as ACCOUNT_REGISTRY_SPACE,
|
|
24252
25057
|
AccountService,
|
|
24253
25058
|
AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
|
|
25059
|
+
BOOTSTRAP_COMPLETION_MARKER_KEY,
|
|
25060
|
+
BOOTSTRAP_COMPLETION_MARKER_VERSION,
|
|
24254
25061
|
CapabilityKeyRegistry2 as CapabilityKeyRegistry,
|
|
24255
25062
|
CapabilityKeyRegistryErrorCodes,
|
|
24256
25063
|
CaveatedDelegationUnsupportedError2 as CaveatedDelegationUnsupportedError,
|
|
@@ -24311,6 +25118,7 @@ export {
|
|
|
24311
25118
|
VaultPublicSpaceKVActions,
|
|
24312
25119
|
VersionCheckError,
|
|
24313
25120
|
WasmKeyProvider,
|
|
25121
|
+
activateCompactRuntimeDelegation,
|
|
24314
25122
|
activateValidatedRuntimeDelegation,
|
|
24315
25123
|
addressStorageKey,
|
|
24316
25124
|
buildCanonicalDecryptRequest,
|
|
@@ -24387,7 +25195,8 @@ export {
|
|
|
24387
25195
|
tinyCloudDebugLogger,
|
|
24388
25196
|
validateEnvelope,
|
|
24389
25197
|
validateManifest,
|
|
24390
|
-
verifyDecryptResponse
|
|
25198
|
+
verifyDecryptResponse,
|
|
25199
|
+
wireOpenKeyAuthorize
|
|
24391
25200
|
};
|
|
24392
25201
|
/*! Bundled license information:
|
|
24393
25202
|
|