@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.cjs
CHANGED
|
@@ -17025,10 +17025,13 @@ var require_utils2 = __commonJS({
|
|
|
17025
17025
|
// src/index.ts
|
|
17026
17026
|
var index_exports = {};
|
|
17027
17027
|
__export(index_exports, {
|
|
17028
|
+
ACCEPTED_MARKER_VERSIONS: () => ACCEPTED_MARKER_VERSIONS,
|
|
17028
17029
|
ACCOUNT_REGISTRY_PATH: () => import_sdk_core12.ACCOUNT_REGISTRY_PATH,
|
|
17029
17030
|
ACCOUNT_REGISTRY_SPACE: () => import_sdk_core12.ACCOUNT_REGISTRY_SPACE,
|
|
17030
17031
|
AccountService: () => import_sdk_core3.AccountService,
|
|
17031
17032
|
AutoApproveSpaceCreationHandler: () => import_sdk_core11.AutoApproveSpaceCreationHandler,
|
|
17033
|
+
BOOTSTRAP_COMPLETION_MARKER_KEY: () => BOOTSTRAP_COMPLETION_MARKER_KEY,
|
|
17034
|
+
BOOTSTRAP_COMPLETION_MARKER_VERSION: () => BOOTSTRAP_COMPLETION_MARKER_VERSION,
|
|
17032
17035
|
CapabilityKeyRegistry: () => import_sdk_core20.CapabilityKeyRegistry,
|
|
17033
17036
|
CapabilityKeyRegistryErrorCodes: () => import_sdk_core20.CapabilityKeyRegistryErrorCodes,
|
|
17034
17037
|
CaveatedDelegationUnsupportedError: () => import_sdk_core12.CaveatedDelegationUnsupportedError,
|
|
@@ -17089,6 +17092,7 @@ __export(index_exports, {
|
|
|
17089
17092
|
VaultPublicSpaceKVActions: () => import_sdk_core16.VaultPublicSpaceKVActions,
|
|
17090
17093
|
VersionCheckError: () => import_sdk_core22.VersionCheckError,
|
|
17091
17094
|
WasmKeyProvider: () => WasmKeyProvider,
|
|
17095
|
+
activateCompactRuntimeDelegation: () => activateCompactRuntimeDelegation,
|
|
17092
17096
|
activateValidatedRuntimeDelegation: () => activateValidatedRuntimeDelegation,
|
|
17093
17097
|
addressStorageKey: () => import_sdk_core11.addressStorageKey,
|
|
17094
17098
|
buildCanonicalDecryptRequest: () => import_sdk_core17.buildCanonicalDecryptRequest,
|
|
@@ -17165,7 +17169,8 @@ __export(index_exports, {
|
|
|
17165
17169
|
tinyCloudDebugLogger: () => import_sdk_core11.tinyCloudDebugLogger,
|
|
17166
17170
|
validateEnvelope: () => import_sdk_core17.validateEnvelope,
|
|
17167
17171
|
validateManifest: () => import_sdk_core12.validateManifest,
|
|
17168
|
-
verifyDecryptResponse: () => import_sdk_core17.verifyDecryptResponse
|
|
17172
|
+
verifyDecryptResponse: () => import_sdk_core17.verifyDecryptResponse,
|
|
17173
|
+
wireOpenKeyAuthorize: () => wireOpenKeyAuthorize
|
|
17169
17174
|
});
|
|
17170
17175
|
module.exports = __toCommonJS(index_exports);
|
|
17171
17176
|
|
|
@@ -17278,6 +17283,8 @@ var import_sdk_core7 = require("@tinycloud/sdk-core");
|
|
|
17278
17283
|
var import_sdk_services = require("@tinycloud/sdk-services");
|
|
17279
17284
|
|
|
17280
17285
|
// src/authorization/NodeUserAuthorization.ts
|
|
17286
|
+
var import_node_crypto = require("crypto");
|
|
17287
|
+
var import_siwe = require("siwe");
|
|
17281
17288
|
var import_sdk_core2 = require("@tinycloud/sdk-core");
|
|
17282
17289
|
|
|
17283
17290
|
// src/authorization/strategies.ts
|
|
@@ -17358,6 +17365,27 @@ var MemorySessionStorage = class {
|
|
|
17358
17365
|
// src/authorization/NodeUserAuthorization.ts
|
|
17359
17366
|
var DECRYPT_ACTION = import_sdk_core2.ENCRYPTION.DECRYPT;
|
|
17360
17367
|
var NETWORK_CREATE_ACTION = import_sdk_core2.ENCRYPTION.NETWORK_CREATE;
|
|
17368
|
+
function canonicalizeOpenKeyManifestJson(value) {
|
|
17369
|
+
const json = JSON.stringify(value);
|
|
17370
|
+
if (json === void 0) {
|
|
17371
|
+
throw new TypeError("manifest is not JSON-serializable");
|
|
17372
|
+
}
|
|
17373
|
+
return canonicalizeJsonValue(JSON.parse(json));
|
|
17374
|
+
}
|
|
17375
|
+
function canonicalizeJsonValue(value) {
|
|
17376
|
+
if (value === null || typeof value !== "object") {
|
|
17377
|
+
return JSON.stringify(value);
|
|
17378
|
+
}
|
|
17379
|
+
if (Array.isArray(value)) {
|
|
17380
|
+
return `[${value.map(canonicalizeJsonValue).join(",")}]`;
|
|
17381
|
+
}
|
|
17382
|
+
return `{${Object.keys(value).sort().map(
|
|
17383
|
+
(key2) => `${JSON.stringify(key2)}:${canonicalizeJsonValue(value[key2])}`
|
|
17384
|
+
).join(",")}}`;
|
|
17385
|
+
}
|
|
17386
|
+
function canonicalOpenKeyManifestSha256Hex(value) {
|
|
17387
|
+
return (0, import_node_crypto.createHash)("sha256").update(canonicalizeOpenKeyManifestJson(value)).digest("hex");
|
|
17388
|
+
}
|
|
17361
17389
|
function didPrincipalMatches(actual, expected) {
|
|
17362
17390
|
try {
|
|
17363
17391
|
return (0, import_sdk_core2.principalDidEquals)(actual, expected);
|
|
@@ -17375,6 +17403,16 @@ function addRawAbility(rawAbilities, resource, action) {
|
|
|
17375
17403
|
actions.push(action);
|
|
17376
17404
|
}
|
|
17377
17405
|
}
|
|
17406
|
+
function cloneAbilitiesMap(actions) {
|
|
17407
|
+
return Object.fromEntries(
|
|
17408
|
+
Object.entries(actions).map(([service, paths]) => [
|
|
17409
|
+
service,
|
|
17410
|
+
Object.fromEntries(
|
|
17411
|
+
Object.entries(paths).map(([path, pathActions]) => [path, [...pathActions]])
|
|
17412
|
+
)
|
|
17413
|
+
])
|
|
17414
|
+
);
|
|
17415
|
+
}
|
|
17378
17416
|
var NodeUserAuthorization = class {
|
|
17379
17417
|
constructor(config) {
|
|
17380
17418
|
this.activeSessionKeyId = "default";
|
|
@@ -17646,23 +17684,41 @@ var NodeUserAuthorization = class {
|
|
|
17646
17684
|
const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
|
|
17647
17685
|
const primarySpaceId2 = (0, import_sdk_core2.makePkhSpaceId)(address, chainId, this.spacePrefix);
|
|
17648
17686
|
const secretsSpaceId = (0, import_sdk_core2.makePkhSpaceId)(address, chainId, "secrets");
|
|
17649
|
-
|
|
17650
|
-
|
|
17651
|
-
|
|
17652
|
-
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17687
|
+
const primaryActions = this.includeAccountRegistryPermissions && this.spacePrefix === import_sdk_core2.ACCOUNT_REGISTRY_SPACE ? cloneAbilitiesMap(this.defaultActions) : this.defaultActions;
|
|
17688
|
+
const spaceAbilities2 = {
|
|
17689
|
+
[primarySpaceId2]: primaryActions,
|
|
17690
|
+
[secretsSpaceId]: {
|
|
17691
|
+
kv: {
|
|
17692
|
+
"vault/secrets/": [
|
|
17693
|
+
import_sdk_core2.KV.GET,
|
|
17694
|
+
import_sdk_core2.KV.PUT,
|
|
17695
|
+
import_sdk_core2.KV.DEL,
|
|
17696
|
+
import_sdk_core2.KV.LIST,
|
|
17697
|
+
import_sdk_core2.KV.METADATA
|
|
17698
|
+
]
|
|
17699
|
+
}
|
|
17700
|
+
}
|
|
17701
|
+
};
|
|
17702
|
+
if (this.includeAccountRegistryPermissions && this.spacePrefix !== import_sdk_core2.ACCOUNT_REGISTRY_SPACE) {
|
|
17703
|
+
spaceAbilities2[(0, import_sdk_core2.makePkhSpaceId)(address, chainId, import_sdk_core2.ACCOUNT_REGISTRY_SPACE)] = (0, import_sdk_core2.resourceCapabilitiesToAbilitiesMap)(import_sdk_core2.ACCOUNT_MANIFEST_PERMISSIONS);
|
|
17704
|
+
} else if (this.includeAccountRegistryPermissions) {
|
|
17705
|
+
const accountAbilities = (0, import_sdk_core2.resourceCapabilitiesToAbilitiesMap)(
|
|
17706
|
+
import_sdk_core2.ACCOUNT_MANIFEST_PERMISSIONS
|
|
17707
|
+
);
|
|
17708
|
+
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
17709
|
+
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
17710
|
+
for (const [path, actions] of Object.entries(paths)) {
|
|
17711
|
+
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
17712
|
+
for (const action of actions) {
|
|
17713
|
+
if (!existingActions.includes(action)) existingActions.push(action);
|
|
17663
17714
|
}
|
|
17664
17715
|
}
|
|
17665
|
-
}
|
|
17716
|
+
}
|
|
17717
|
+
}
|
|
17718
|
+
return {
|
|
17719
|
+
abilities: primaryActions,
|
|
17720
|
+
spaceId: primarySpaceId2,
|
|
17721
|
+
spaceAbilities: spaceAbilities2,
|
|
17666
17722
|
rawAbilities: {
|
|
17667
17723
|
[defaultNetworkId]: [DECRYPT_ACTION, NETWORK_CREATE_ACTION]
|
|
17668
17724
|
}
|
|
@@ -18243,6 +18299,398 @@ var NodeUserAuthorization = class {
|
|
|
18243
18299
|
await this.ensureSpaceExists();
|
|
18244
18300
|
return clientSession;
|
|
18245
18301
|
}
|
|
18302
|
+
/**
|
|
18303
|
+
* End-to-end sign-in via OpenKey `authorizeTinyCloud()` (Sol CRITICAL-4).
|
|
18304
|
+
*
|
|
18305
|
+
* This is the production entry point that wires
|
|
18306
|
+
* {@link prepareSessionForSigning} → OpenKey `authorizeTinyCloud()` →
|
|
18307
|
+
* {@link signInWithOpenKeyResult} into one call. Consumers pass in a
|
|
18308
|
+
* caller-provided `authorizeFn` (typically a thin wrapper around
|
|
18309
|
+
* `openkey.authorizeTinyCloud()`) so this method has no direct
|
|
18310
|
+
* dependency on the OpenKey SDK — it works with any transport that
|
|
18311
|
+
* conforms to the versioned protocol.
|
|
18312
|
+
*
|
|
18313
|
+
* The `authorizeFn` MUST:
|
|
18314
|
+
* 1. Send the prepared SIWE bytes to OpenKey.
|
|
18315
|
+
* 2. Return the exact `TinyCloudAuthorizationResultV1` shape (with
|
|
18316
|
+
* `signedMessage` — the bytes the signature actually verifies
|
|
18317
|
+
* against — plus `selectedActionKeys` and `permissions`).
|
|
18318
|
+
*
|
|
18319
|
+
* NodeUserAuthorization then completes the session with `signedMessage`
|
|
18320
|
+
* (never the prepared bytes), verifying the signature, the immutable
|
|
18321
|
+
* SIWE fields, and every subset invariant enforced by
|
|
18322
|
+
* {@link signInWithOpenKeyResult}. This preserves the exact-byte
|
|
18323
|
+
* guarantee: if the user did NOT narrow, the returned `signedMessage`
|
|
18324
|
+
* equals the prepared SIWE byte-for-byte; if the user narrowed, only
|
|
18325
|
+
* the ReCap block differs and every immutable header field survives.
|
|
18326
|
+
*
|
|
18327
|
+
* Legacy exact-byte {@link signIn} callers are unaffected — this method
|
|
18328
|
+
* is opt-in.
|
|
18329
|
+
*
|
|
18330
|
+
* @param authorizeFn Callback that invokes the versioned OpenKey
|
|
18331
|
+
* `authorizeTinyCloud()` protocol and returns the result. Receives
|
|
18332
|
+
* the prepared SIWE bytes plus a `host` hint the widget should bind
|
|
18333
|
+
* into its /authorize-sign-prepare context.
|
|
18334
|
+
* @param options Optional overrides for the OpenKey request.
|
|
18335
|
+
*/
|
|
18336
|
+
async signInWithOpenKey(authorizeFn, options) {
|
|
18337
|
+
if (options?.host !== void 0) {
|
|
18338
|
+
const overrideHost = options.host.trim();
|
|
18339
|
+
if (!overrideHost) {
|
|
18340
|
+
throw new Error("OpenKey authorization host must not be empty");
|
|
18341
|
+
}
|
|
18342
|
+
this.tinycloudHosts = [overrideHost];
|
|
18343
|
+
}
|
|
18344
|
+
const signingAddress = (0, import_sdk_core2.canonicalizeAddress)(await this.signer.getAddress());
|
|
18345
|
+
const signingChainId = await this.signer.getChainId();
|
|
18346
|
+
await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
|
|
18347
|
+
const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
|
|
18348
|
+
const presentation = this.buildPresentationEnvelope(options?.reason);
|
|
18349
|
+
const hostHint = this.primaryTinyCloudHost;
|
|
18350
|
+
const result = await authorizeFn({
|
|
18351
|
+
protocolVersion: 1,
|
|
18352
|
+
siwe: prepared.siwe,
|
|
18353
|
+
jwk,
|
|
18354
|
+
host: hostHint,
|
|
18355
|
+
keyId: options?.openkeyKeyId,
|
|
18356
|
+
presentation
|
|
18357
|
+
});
|
|
18358
|
+
return this.signInWithOpenKeyResult(result, prepared, keyId, jwk);
|
|
18359
|
+
}
|
|
18360
|
+
/**
|
|
18361
|
+
* Build a `CapabilityPresentationEnvelopeV1` from `this._manifest`. The
|
|
18362
|
+
* envelope is display-only — the OpenKey widget renders it under an
|
|
18363
|
+
* honest trust label, and the server /authorize-sign-prepare route
|
|
18364
|
+
* decides whether to upgrade the label to `origin-bound` by fetching
|
|
18365
|
+
* the well-known manifest and comparing its digest to
|
|
18366
|
+
* `manifestDigest`.
|
|
18367
|
+
*
|
|
18368
|
+
* Returns `undefined` when no manifest is set — the widget renders as
|
|
18369
|
+
* "no manifest supplied" (unsigned).
|
|
18370
|
+
*
|
|
18371
|
+
* The digest is a deterministic SHA-256 over canonical JSON of the
|
|
18372
|
+
* first manifest payload; OpenKey parses the fetched well-known manifest
|
|
18373
|
+
* and applies the same canonicalization before comparing digests.
|
|
18374
|
+
*/
|
|
18375
|
+
buildPresentationEnvelope(reason) {
|
|
18376
|
+
const manifests = Array.isArray(this._manifest) ? this._manifest : this._manifest ? [this._manifest] : [];
|
|
18377
|
+
if (manifests.length === 0 && !reason) {
|
|
18378
|
+
return void 0;
|
|
18379
|
+
}
|
|
18380
|
+
const primary = manifests[0];
|
|
18381
|
+
let displayName;
|
|
18382
|
+
let manifestId;
|
|
18383
|
+
let manifestDigest;
|
|
18384
|
+
const envelopeManifests = [];
|
|
18385
|
+
for (const m of manifests) {
|
|
18386
|
+
envelopeManifests.push({
|
|
18387
|
+
name: m.name,
|
|
18388
|
+
appId: m.app_id,
|
|
18389
|
+
// Deep-copy to prevent later mutations from affecting the digest.
|
|
18390
|
+
payload: JSON.parse(JSON.stringify(m))
|
|
18391
|
+
});
|
|
18392
|
+
}
|
|
18393
|
+
if (primary) {
|
|
18394
|
+
displayName = primary.name;
|
|
18395
|
+
manifestId = primary.app_id;
|
|
18396
|
+
manifestDigest = canonicalOpenKeyManifestSha256Hex(primary);
|
|
18397
|
+
}
|
|
18398
|
+
return {
|
|
18399
|
+
protocolVersion: 1,
|
|
18400
|
+
displayName,
|
|
18401
|
+
reason: reason && reason.length > 0 ? reason : void 0,
|
|
18402
|
+
manifestId,
|
|
18403
|
+
manifestDigest,
|
|
18404
|
+
manifests: envelopeManifests.length > 0 ? envelopeManifests : void 0
|
|
18405
|
+
};
|
|
18406
|
+
}
|
|
18407
|
+
/**
|
|
18408
|
+
* Complete sign-in with a versioned OpenKey authorization result.
|
|
18409
|
+
*
|
|
18410
|
+
* Where `signInWithPreparedSession` completes with a signature over the
|
|
18411
|
+
* ORIGINAL prepared SIWE, this method completes with the SIWE bytes the
|
|
18412
|
+
* OpenKey widget actually presented and signed (`signedMessage`). It
|
|
18413
|
+
* exists because the versioned authorizeTinyCloud protocol lets the user
|
|
18414
|
+
* narrow capabilities; the returned bytes may differ from the original
|
|
18415
|
+
* request and are the only ones the signature actually verifies against.
|
|
18416
|
+
*
|
|
18417
|
+
* The `result` parameter is validated at the trust boundary via
|
|
18418
|
+
* `validateAuthorizationResultV1` (in sdk-core). Callers should validate
|
|
18419
|
+
* BEFORE calling this method so unknown/missing fields are rejected
|
|
18420
|
+
* before any session state is created.
|
|
18421
|
+
*
|
|
18422
|
+
* Validation performed here (defence-in-depth: even a "valid" v1 payload
|
|
18423
|
+
* must clear these checks before any session state is created):
|
|
18424
|
+
* 1. `result.address` matches the local signer's address.
|
|
18425
|
+
* 2. `result.signature` recovers to `result.address` when verified against
|
|
18426
|
+
* `result.signedMessage`.
|
|
18427
|
+
* 3. Every immutable SIWE header field (domain, address, URI, version,
|
|
18428
|
+
* chainId, nonce, issuedAt) in the original prepared SIWE is preserved
|
|
18429
|
+
* byte-for-byte in `result.signedMessage`. `statement` is NOT in this
|
|
18430
|
+
* list because the WASM renders it from the ReCap contents; it MUST
|
|
18431
|
+
* change when the ReCap is narrowed.
|
|
18432
|
+
* 4. Every capability in `result.signedMessage`'s ReCap block was present
|
|
18433
|
+
* in the original prepared SIWE — narrowing OK, broadening rejected.
|
|
18434
|
+
* 5. Every `result.selectedActionKeys` entry corresponds to a
|
|
18435
|
+
* (resource, action) pair in the returned SIWE.
|
|
18436
|
+
*/
|
|
18437
|
+
async signInWithOpenKeyResult(result, prepared, keyId, jwk) {
|
|
18438
|
+
if (result.protocolVersion !== 1) {
|
|
18439
|
+
throw new Error(`Unsupported OpenKey protocol version ${result.protocolVersion}`);
|
|
18440
|
+
}
|
|
18441
|
+
if (!result.signedMessage) {
|
|
18442
|
+
throw new Error("OpenKey result must include signedMessage");
|
|
18443
|
+
}
|
|
18444
|
+
if (!prepared.siwe) {
|
|
18445
|
+
throw new Error(
|
|
18446
|
+
"signInWithOpenKeyResult requires prepared.siwe (the SDK-generated SIWE the sign-in was proposed with)"
|
|
18447
|
+
);
|
|
18448
|
+
}
|
|
18449
|
+
const expectedAddress = (0, import_sdk_core2.canonicalizeAddress)(await this.signer.getAddress());
|
|
18450
|
+
if ((0, import_sdk_core2.canonicalizeAddress)(result.address) !== expectedAddress) {
|
|
18451
|
+
throw new Error(
|
|
18452
|
+
`OpenKey returned signature from ${result.address} but expected ${expectedAddress}`
|
|
18453
|
+
);
|
|
18454
|
+
}
|
|
18455
|
+
let parsedSignedSiwe;
|
|
18456
|
+
try {
|
|
18457
|
+
parsedSignedSiwe = new import_siwe.SiweMessage(result.signedMessage);
|
|
18458
|
+
} catch (err) {
|
|
18459
|
+
throw new Error(
|
|
18460
|
+
`OpenKey signedMessage is not a parseable SIWE: ${err.message}`
|
|
18461
|
+
);
|
|
18462
|
+
}
|
|
18463
|
+
const verifyResult = await parsedSignedSiwe.verify(
|
|
18464
|
+
{ signature: result.signature },
|
|
18465
|
+
{ suppressExceptions: true }
|
|
18466
|
+
);
|
|
18467
|
+
if (!verifyResult.success) {
|
|
18468
|
+
const errData = verifyResult.error;
|
|
18469
|
+
const type = errData && typeof errData === "object" && "type" in errData ? errData.type : void 0;
|
|
18470
|
+
const details = errData && typeof errData === "object" && "expected" in errData ? ` (expected=${errData.expected}, received=${errData.received})` : "";
|
|
18471
|
+
throw new Error(
|
|
18472
|
+
`OpenKey signature verification failed${type ? `: ${type}` : ""}${details}`
|
|
18473
|
+
);
|
|
18474
|
+
}
|
|
18475
|
+
if ((0, import_sdk_core2.canonicalizeAddress)(parsedSignedSiwe.address) !== expectedAddress) {
|
|
18476
|
+
throw new Error(
|
|
18477
|
+
`OpenKey signedMessage.address (${parsedSignedSiwe.address}) does not match local signer address (${expectedAddress})`
|
|
18478
|
+
);
|
|
18479
|
+
}
|
|
18480
|
+
const originalHasRecap = /(?:^|\n)-\s+urn:recap:/i.test(prepared.siwe);
|
|
18481
|
+
const originalFields = (0, import_sdk_core2.extractImmutableSiweFields)(prepared.siwe);
|
|
18482
|
+
const signedFields = (0, import_sdk_core2.extractImmutableSiweFields)(result.signedMessage);
|
|
18483
|
+
const changedFields = (0, import_sdk_core2.diffImmutableSiweFields)(originalFields, signedFields, {
|
|
18484
|
+
originalHasRecap
|
|
18485
|
+
});
|
|
18486
|
+
if (changedFields.length > 0) {
|
|
18487
|
+
throw new Error(
|
|
18488
|
+
`OpenKey signedMessage altered immutable SIWE fields: ${changedFields.join(", ")}`
|
|
18489
|
+
);
|
|
18490
|
+
}
|
|
18491
|
+
let originalCaps;
|
|
18492
|
+
let signedCaps;
|
|
18493
|
+
try {
|
|
18494
|
+
originalCaps = (0, import_sdk_core2.extractRecapAttenuations)(prepared.siwe);
|
|
18495
|
+
} catch (err) {
|
|
18496
|
+
throw new Error(
|
|
18497
|
+
`Failed to decode prepared SIWE recap: ${err.message}`
|
|
18498
|
+
);
|
|
18499
|
+
}
|
|
18500
|
+
try {
|
|
18501
|
+
signedCaps = (0, import_sdk_core2.extractRecapAttenuations)(result.signedMessage);
|
|
18502
|
+
} catch (err) {
|
|
18503
|
+
throw new Error(
|
|
18504
|
+
`Failed to decode OpenKey signedMessage recap: ${err.message}`
|
|
18505
|
+
);
|
|
18506
|
+
}
|
|
18507
|
+
const unauthorized = (0, import_sdk_core2.unauthorizedRecapCapabilities)(signedCaps, originalCaps);
|
|
18508
|
+
if (unauthorized.length > 0) {
|
|
18509
|
+
const previewLimit = 5;
|
|
18510
|
+
const preview = unauthorized.slice(0, previewLimit).map((u) => `${u.resource}::${u.action}`).join(", ");
|
|
18511
|
+
const overflow = unauthorized.length > previewLimit ? ` (and ${unauthorized.length - previewLimit} more)` : "";
|
|
18512
|
+
throw new Error(
|
|
18513
|
+
`OpenKey signedMessage broadened authorization beyond the original request: ${preview}${overflow}`
|
|
18514
|
+
);
|
|
18515
|
+
}
|
|
18516
|
+
if (!result.signedMessage.includes(prepared.spaceId)) {
|
|
18517
|
+
throw new Error(
|
|
18518
|
+
`OpenKey signedMessage does not reference the expected spaceId ${prepared.spaceId}`
|
|
18519
|
+
);
|
|
18520
|
+
}
|
|
18521
|
+
const grantedPairs = /* @__PURE__ */ new Set();
|
|
18522
|
+
const grantedFourPartIndex = /* @__PURE__ */ new Map();
|
|
18523
|
+
for (const [resource, actions] of Object.entries(signedCaps)) {
|
|
18524
|
+
const { space, path } = (0, import_sdk_core2.parseCanonicalRecapResource)(resource);
|
|
18525
|
+
for (const action of Object.keys(actions)) {
|
|
18526
|
+
grantedPairs.add(`${resource}\0${action}`);
|
|
18527
|
+
const slashIdx = action.indexOf("/");
|
|
18528
|
+
const service = slashIdx > 0 ? action.slice(0, slashIdx) : "";
|
|
18529
|
+
if (service) {
|
|
18530
|
+
grantedFourPartIndex.set(
|
|
18531
|
+
`${service}\0${space}\0${path}\0${action}`,
|
|
18532
|
+
`${resource}\0${action}`
|
|
18533
|
+
);
|
|
18534
|
+
}
|
|
18535
|
+
}
|
|
18536
|
+
}
|
|
18537
|
+
const isStructurallyRequired = (action) => action === "tinycloud.capabilities/read" || action === "capabilities/read";
|
|
18538
|
+
const nonRequiredGrantedPairs = /* @__PURE__ */ new Set();
|
|
18539
|
+
for (const pair of grantedPairs) {
|
|
18540
|
+
const idx = pair.indexOf("\0");
|
|
18541
|
+
const action = idx >= 0 ? pair.slice(idx + 1) : pair;
|
|
18542
|
+
if (!isStructurallyRequired(action)) {
|
|
18543
|
+
nonRequiredGrantedPairs.add(pair);
|
|
18544
|
+
}
|
|
18545
|
+
}
|
|
18546
|
+
const allSignedGrantedPairs = new Set(grantedPairs);
|
|
18547
|
+
const seenRawKeys = /* @__PURE__ */ new Set();
|
|
18548
|
+
for (const rawKey of result.selectedActionKeys) {
|
|
18549
|
+
if (seenRawKeys.has(rawKey)) {
|
|
18550
|
+
throw new Error(
|
|
18551
|
+
`OpenKey selectedActionKeys contains duplicate entry ${rawKey}`
|
|
18552
|
+
);
|
|
18553
|
+
}
|
|
18554
|
+
seenRawKeys.add(rawKey);
|
|
18555
|
+
}
|
|
18556
|
+
const selectedResourceActionPairs = /* @__PURE__ */ new Set();
|
|
18557
|
+
for (const rawKey of result.selectedActionKeys) {
|
|
18558
|
+
const parts = rawKey.split("\0");
|
|
18559
|
+
if (parts.length !== 4) {
|
|
18560
|
+
throw new Error(
|
|
18561
|
+
`OpenKey selectedActionKeys entry is malformed (expected canonical 4-part service\\0space\\0path\\0ability): ${rawKey}`
|
|
18562
|
+
);
|
|
18563
|
+
}
|
|
18564
|
+
const canonical = grantedFourPartIndex.get(rawKey);
|
|
18565
|
+
if (!canonical) {
|
|
18566
|
+
throw new Error(
|
|
18567
|
+
`OpenKey selectedActionKeys entry ${rawKey} is not covered by any granted capability`
|
|
18568
|
+
);
|
|
18569
|
+
}
|
|
18570
|
+
selectedResourceActionPairs.add(canonical);
|
|
18571
|
+
}
|
|
18572
|
+
if (nonRequiredGrantedPairs.size > 0) {
|
|
18573
|
+
const missingPairs = [];
|
|
18574
|
+
for (const pair of nonRequiredGrantedPairs) {
|
|
18575
|
+
if (!selectedResourceActionPairs.has(pair)) {
|
|
18576
|
+
missingPairs.push(pair);
|
|
18577
|
+
}
|
|
18578
|
+
}
|
|
18579
|
+
if (missingPairs.length > 0) {
|
|
18580
|
+
const previewLimit = 5;
|
|
18581
|
+
const preview = missingPairs.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
|
|
18582
|
+
const overflow = missingPairs.length > previewLimit ? ` (and ${missingPairs.length - previewLimit} more)` : "";
|
|
18583
|
+
throw new Error(
|
|
18584
|
+
`OpenKey selectedActionKeys is missing entries for signedMessage capabilities: ${preview}${overflow}`
|
|
18585
|
+
);
|
|
18586
|
+
}
|
|
18587
|
+
}
|
|
18588
|
+
if (allSignedGrantedPairs.size > 0 && result.permissions.length === 0) {
|
|
18589
|
+
throw new Error(
|
|
18590
|
+
"OpenKey permissions is empty but signedMessage carries capabilities \u2014 refusing to accept a session without an authoritative grant list"
|
|
18591
|
+
);
|
|
18592
|
+
}
|
|
18593
|
+
const permissionCanonicalKeys = /* @__PURE__ */ new Set();
|
|
18594
|
+
const permissionPairsCovered = /* @__PURE__ */ new Set();
|
|
18595
|
+
for (const perm of result.permissions) {
|
|
18596
|
+
const { service, space, path, actions } = perm;
|
|
18597
|
+
if (typeof service !== "string" || typeof space !== "string" || typeof path !== "string") {
|
|
18598
|
+
throw new Error(
|
|
18599
|
+
`OpenKey permissions entry has non-string service/space/path (service=${service}, space=${space}, path=${path})`
|
|
18600
|
+
);
|
|
18601
|
+
}
|
|
18602
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
18603
|
+
throw new Error(
|
|
18604
|
+
`OpenKey permissions entry (${service} ${space} ${path}) has empty actions \u2014 a capability-bearing entry must list at least one action`
|
|
18605
|
+
);
|
|
18606
|
+
}
|
|
18607
|
+
const canonicalKey = `${service}\0${space}\0${path}`;
|
|
18608
|
+
if (permissionCanonicalKeys.has(canonicalKey)) {
|
|
18609
|
+
throw new Error(
|
|
18610
|
+
`OpenKey permissions has duplicate entry for (${service} ${space} ${path})`
|
|
18611
|
+
);
|
|
18612
|
+
}
|
|
18613
|
+
permissionCanonicalKeys.add(canonicalKey);
|
|
18614
|
+
let expectedResource;
|
|
18615
|
+
if (space.startsWith("tinycloud:")) {
|
|
18616
|
+
const shortService = service.startsWith("tinycloud.") ? service.slice("tinycloud.".length) : service;
|
|
18617
|
+
expectedResource = path ? `${space}/${shortService}/${path}` : `${space}/${shortService}`;
|
|
18618
|
+
} else {
|
|
18619
|
+
if (path) {
|
|
18620
|
+
throw new Error(
|
|
18621
|
+
`OpenKey permissions entry (${service} ${space} ${path}) carries a path on a non-tinycloud: resource URI, which is not a recognized encoding`
|
|
18622
|
+
);
|
|
18623
|
+
}
|
|
18624
|
+
expectedResource = space;
|
|
18625
|
+
}
|
|
18626
|
+
let matchedResource;
|
|
18627
|
+
if (signedCaps[expectedResource] !== void 0) {
|
|
18628
|
+
matchedResource = expectedResource;
|
|
18629
|
+
} else {
|
|
18630
|
+
throw new Error(
|
|
18631
|
+
`OpenKey permissions entry (${service} ${space} ${path}) claims grants not confirmed in signedMessage capabilities`
|
|
18632
|
+
);
|
|
18633
|
+
}
|
|
18634
|
+
const grantedActionsForResource = new Set(
|
|
18635
|
+
Object.keys(signedCaps[matchedResource])
|
|
18636
|
+
);
|
|
18637
|
+
for (const action of actions) {
|
|
18638
|
+
if (typeof action !== "string" || !action) {
|
|
18639
|
+
throw new Error(
|
|
18640
|
+
`OpenKey permissions entry (${service} ${space} ${path}) has non-string action ${String(action)}`
|
|
18641
|
+
);
|
|
18642
|
+
}
|
|
18643
|
+
if (!grantedActionsForResource.has(action)) {
|
|
18644
|
+
throw new Error(
|
|
18645
|
+
`OpenKey permissions entry (${service} ${space} ${path}) claims action ${action} not present in signedMessage capabilities`
|
|
18646
|
+
);
|
|
18647
|
+
}
|
|
18648
|
+
permissionPairsCovered.add(`${matchedResource}\0${action}`);
|
|
18649
|
+
}
|
|
18650
|
+
}
|
|
18651
|
+
if (allSignedGrantedPairs.size > 0) {
|
|
18652
|
+
const missingFromPerms = [];
|
|
18653
|
+
for (const pair of allSignedGrantedPairs) {
|
|
18654
|
+
if (!permissionPairsCovered.has(pair)) {
|
|
18655
|
+
missingFromPerms.push(pair);
|
|
18656
|
+
}
|
|
18657
|
+
}
|
|
18658
|
+
if (missingFromPerms.length > 0) {
|
|
18659
|
+
const previewLimit = 5;
|
|
18660
|
+
const preview = missingFromPerms.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
|
|
18661
|
+
const overflow = missingFromPerms.length > previewLimit ? ` (and ${missingFromPerms.length - previewLimit} more)` : "";
|
|
18662
|
+
throw new Error(
|
|
18663
|
+
`OpenKey permissions is missing entries for signedMessage capabilities: ${preview}${overflow}`
|
|
18664
|
+
);
|
|
18665
|
+
}
|
|
18666
|
+
const extrasInPerms = [];
|
|
18667
|
+
for (const pair of permissionPairsCovered) {
|
|
18668
|
+
if (!allSignedGrantedPairs.has(pair)) {
|
|
18669
|
+
extrasInPerms.push(pair);
|
|
18670
|
+
}
|
|
18671
|
+
}
|
|
18672
|
+
if (extrasInPerms.length > 0) {
|
|
18673
|
+
const previewLimit = 5;
|
|
18674
|
+
const preview = extrasInPerms.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
|
|
18675
|
+
const overflow = extrasInPerms.length > previewLimit ? ` (and ${extrasInPerms.length - previewLimit} more)` : "";
|
|
18676
|
+
throw new Error(
|
|
18677
|
+
`OpenKey permissions contains entries not present in the signed authority: ${preview}${overflow}`
|
|
18678
|
+
);
|
|
18679
|
+
}
|
|
18680
|
+
}
|
|
18681
|
+
void nonRequiredGrantedPairs;
|
|
18682
|
+
return this.signInWithPreparedSession(
|
|
18683
|
+
{
|
|
18684
|
+
siwe: result.signedMessage,
|
|
18685
|
+
jwk: prepared.jwk,
|
|
18686
|
+
spaceId: prepared.spaceId,
|
|
18687
|
+
verificationMethod: prepared.verificationMethod
|
|
18688
|
+
},
|
|
18689
|
+
result.signature,
|
|
18690
|
+
keyId,
|
|
18691
|
+
jwk
|
|
18692
|
+
);
|
|
18693
|
+
}
|
|
18246
18694
|
/**
|
|
18247
18695
|
* Clear persisted session data.
|
|
18248
18696
|
*/
|
|
@@ -18733,6 +19181,9 @@ var DEFAULT_HOST = "https://node.tinycloud.xyz";
|
|
|
18733
19181
|
var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
|
|
18734
19182
|
var NETWORK_CREATE_ACTION2 = import_sdk_core7.ENCRYPTION.NETWORK_CREATE;
|
|
18735
19183
|
var DECRYPT_ACTION2 = import_sdk_core7.ENCRYPTION.DECRYPT;
|
|
19184
|
+
var BOOTSTRAP_COMPLETION_MARKER_KEY = "system/bootstrap/complete";
|
|
19185
|
+
var BOOTSTRAP_COMPLETION_MARKER_VERSION = 1;
|
|
19186
|
+
var ACCEPTED_MARKER_VERSIONS = [BOOTSTRAP_COMPLETION_MARKER_VERSION];
|
|
18736
19187
|
var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
|
|
18737
19188
|
function isSecretPayload(value) {
|
|
18738
19189
|
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";
|
|
@@ -18756,6 +19207,18 @@ var ROOT_DELEGATION_ACTIONS = [
|
|
|
18756
19207
|
import_sdk_core7.DUCKDB.ALL
|
|
18757
19208
|
];
|
|
18758
19209
|
var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core7.EXPIRY.SESSION_MS;
|
|
19210
|
+
function unifiedRootAttenuation(capabilities) {
|
|
19211
|
+
const attenuation = {};
|
|
19212
|
+
for (const capability of capabilities) {
|
|
19213
|
+
if (attenuation[capability.resource] !== void 0) throw new Error("unified owner root contains a duplicate resource");
|
|
19214
|
+
attenuation[capability.resource] = capability.kind === "encryption" ? { [capability.action]: [{}] } : Object.fromEntries(capability.actions.map((action) => [action, [{
|
|
19215
|
+
type: "xyz.tinycloud.resource/selector",
|
|
19216
|
+
kind: capability.selector,
|
|
19217
|
+
value: capability.resource
|
|
19218
|
+
}]]));
|
|
19219
|
+
}
|
|
19220
|
+
return attenuation;
|
|
19221
|
+
}
|
|
18759
19222
|
function decodeAuthorizationBytes(authorization) {
|
|
18760
19223
|
const encoded = authorization.replace(/^Bearer /i, "");
|
|
18761
19224
|
const match = /^([A-Za-z0-9_-]+)(={1,2})?$/.exec(encoded);
|
|
@@ -18775,6 +19238,9 @@ function decodeAuthorizationBytes(authorization) {
|
|
|
18775
19238
|
function isOpenKeyAutoSignStrategy(strategy) {
|
|
18776
19239
|
return strategy?.openKeyAutoSign === true;
|
|
18777
19240
|
}
|
|
19241
|
+
function openKeyApproval(strategy) {
|
|
19242
|
+
return strategy?.openKeyRequestApproval;
|
|
19243
|
+
}
|
|
18778
19244
|
function isInteractiveSigner(config) {
|
|
18779
19245
|
if (config.privateKey) {
|
|
18780
19246
|
return false;
|
|
@@ -19132,6 +19598,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19132
19598
|
* hosting is confirmed against the session that was active at the time.
|
|
19133
19599
|
*/
|
|
19134
19600
|
this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
|
|
19601
|
+
/** Serializes account-registry writes for the active node instance. */
|
|
19602
|
+
this.accountRegistryTail = Promise.resolve();
|
|
19135
19603
|
/**
|
|
19136
19604
|
* True when the last signIn() detected an interactive signer and skipped
|
|
19137
19605
|
* client-side bootstrap. Apps can read this to know whether bootstrap was
|
|
@@ -19140,9 +19608,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19140
19608
|
this._bootstrapSkipped = false;
|
|
19141
19609
|
/**
|
|
19142
19610
|
* Outcome of the last signIn()'s account-bootstrap attempt. `skipped` is
|
|
19143
|
-
* true when bootstrap did not complete (interactive signer,
|
|
19144
|
-
*
|
|
19145
|
-
*
|
|
19611
|
+
* true when bootstrap did not complete in this invocation (interactive signer,
|
|
19612
|
+
* auto-bootstrap disabled, already provisioned, or a decision/provisioning
|
|
19613
|
+
* step failed). `already-provisioned` means the bootstrap was not run here;
|
|
19614
|
+
* it does not mean a prior provisioning attempt failed to complete.
|
|
19615
|
+
* `reason` carries the cause so apps can surface a "finish account setup"
|
|
19616
|
+
* call-to-action. `warnings` is
|
|
19146
19617
|
* present when bootstrap completed, but one or more steps recovered from
|
|
19147
19618
|
* an ambiguous failure (e.g. a KV batch write reconciled via per-space
|
|
19148
19619
|
* fallback) — clean vs. recovered runs are otherwise byte-identical.
|
|
@@ -19325,7 +19796,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19325
19796
|
nonce: config.nonce,
|
|
19326
19797
|
siweConfig: config.siweConfig,
|
|
19327
19798
|
manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
|
|
19328
|
-
capabilityRequest: useBootstrapSignInRequest ? import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
|
|
19799
|
+
capabilityRequest: useBootstrapSignInRequest ? config.includeAccountRegistryPermissions === false ? import_sdk_core7.BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
|
|
19329
19800
|
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
|
|
19330
19801
|
});
|
|
19331
19802
|
this.tc = new import_sdk_core7.TinyCloud(this.auth, {
|
|
@@ -19415,6 +19886,15 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19415
19886
|
get sessionDid() {
|
|
19416
19887
|
return this.sessionManager.getDID(this.sessionKeyId);
|
|
19417
19888
|
}
|
|
19889
|
+
/** Bare did:key principal used as the credential subject and holder binding. */
|
|
19890
|
+
get credentialHolderDid() {
|
|
19891
|
+
return this.sessionDid.split("#", 1)[0];
|
|
19892
|
+
}
|
|
19893
|
+
/** Canonical verification method for the active credential holder key. */
|
|
19894
|
+
get credentialHolderKid() {
|
|
19895
|
+
const holder = this.credentialHolderDid;
|
|
19896
|
+
return `${holder}#${holder.slice("did:key:".length)}`;
|
|
19897
|
+
}
|
|
19418
19898
|
/**
|
|
19419
19899
|
* Return the current session's signed ReCap capabilities after the session
|
|
19420
19900
|
* has been authenticated or restored. This is intentionally distinct from
|
|
@@ -19516,6 +19996,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19516
19996
|
"Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
|
|
19517
19997
|
);
|
|
19518
19998
|
}
|
|
19999
|
+
await this.accountRegistryTail;
|
|
19519
20000
|
await this.wasmBindings.ensureInitialized?.();
|
|
19520
20001
|
this._address = (0, import_sdk_core7.canonicalizeAddress)(await this.signer.getAddress());
|
|
19521
20002
|
this._chainId = await this.signer.getChainId();
|
|
@@ -19568,6 +20049,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19568
20049
|
this._bootstrapSkipped = false;
|
|
19569
20050
|
this._bootstrapStatus = { skipped: false };
|
|
19570
20051
|
if (this.config.autoBootstrapAccount === false) {
|
|
20052
|
+
this._bootstrapStatus = { skipped: true, reason: "auto-bootstrap-disabled" };
|
|
19571
20053
|
return false;
|
|
19572
20054
|
}
|
|
19573
20055
|
if (!this.auth || !this._address) {
|
|
@@ -19582,11 +20064,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19582
20064
|
return false;
|
|
19583
20065
|
}
|
|
19584
20066
|
const steps = (0, import_sdk_core7.bootstrapSteps)(this._address, this._chainId);
|
|
19585
|
-
if (!await this.isFreshBootstrapAccount(steps)) {
|
|
19586
|
-
return false;
|
|
19587
|
-
}
|
|
19588
20067
|
try {
|
|
19589
|
-
const
|
|
20068
|
+
const decision = await this.resolveBootstrapDecision(steps);
|
|
20069
|
+
if (decision.action === "skip") {
|
|
20070
|
+
this._bootstrapStatus = { skipped: true, reason: "already-provisioned" };
|
|
20071
|
+
return false;
|
|
20072
|
+
}
|
|
20073
|
+
const warnings = await this.runAccountBootstrap(steps, { mode: decision.mode });
|
|
20074
|
+
await this.writeBootstrapCompletionMarker(steps);
|
|
19590
20075
|
if (warnings.length > 0) {
|
|
19591
20076
|
this._bootstrapStatus = { skipped: false, warnings };
|
|
19592
20077
|
this.notificationHandler.warning(
|
|
@@ -19605,32 +20090,92 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19605
20090
|
}
|
|
19606
20091
|
return true;
|
|
19607
20092
|
}
|
|
19608
|
-
async
|
|
20093
|
+
async resolveBootstrapDecision(steps) {
|
|
19609
20094
|
const enshrinedSpaceIds = /* @__PURE__ */ new Set();
|
|
19610
20095
|
for (const step of steps) {
|
|
19611
20096
|
if (step.kind === "session") {
|
|
19612
20097
|
enshrinedSpaceIds.add(step.spaceId);
|
|
19613
20098
|
}
|
|
19614
20099
|
}
|
|
19615
|
-
const
|
|
20100
|
+
const auth = this.auth;
|
|
20101
|
+
if (!auth) {
|
|
20102
|
+
throw new Error("Account bootstrap requires an active wallet session");
|
|
20103
|
+
}
|
|
20104
|
+
const skipped = auth.lastActivationSkippedSpaceIds;
|
|
19616
20105
|
if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
|
|
19617
|
-
return
|
|
20106
|
+
return { action: "run", mode: "fresh" };
|
|
19618
20107
|
}
|
|
19619
20108
|
try {
|
|
19620
|
-
const
|
|
19621
|
-
|
|
19622
|
-
|
|
20109
|
+
const accountSpaceId = this.ownedSpaceId(import_sdk_core7.ACCOUNT_REGISTRY_SPACE);
|
|
20110
|
+
const markerPermission = {
|
|
20111
|
+
service: "tinycloud.kv",
|
|
20112
|
+
space: accountSpaceId,
|
|
20113
|
+
path: BOOTSTRAP_COMPLETION_MARKER_KEY,
|
|
20114
|
+
actions: ["get"]
|
|
20115
|
+
};
|
|
20116
|
+
if (!this.hasRuntimePermissions([markerPermission])) {
|
|
20117
|
+
return { action: "run", mode: "repair" };
|
|
19623
20118
|
}
|
|
19624
|
-
|
|
20119
|
+
const marker = await this.readBootstrapCompletionMarker();
|
|
20120
|
+
if (marker.ok) {
|
|
20121
|
+
if (this.isAcceptedBootstrapCompletionMarker(marker.data.data)) {
|
|
20122
|
+
return { action: "skip" };
|
|
20123
|
+
}
|
|
20124
|
+
return { action: "run", mode: "repair" };
|
|
20125
|
+
}
|
|
20126
|
+
if (marker.error.code === import_sdk_core7.ErrorCodes.KV_NOT_FOUND && this.markerReadIsUnhostedSpace(marker.error.meta)) {
|
|
20127
|
+
return { action: "run", mode: "fresh" };
|
|
20128
|
+
}
|
|
20129
|
+
} catch (err) {
|
|
20130
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
20131
|
+
this.notificationHandler.warning(
|
|
20132
|
+
`Account bootstrap decision failed; attempting one bounded repair: ${reason}`
|
|
20133
|
+
);
|
|
20134
|
+
console.warn(`[TinyCloudNode] account bootstrap decision failed: ${reason}`);
|
|
20135
|
+
return { action: "run", mode: "repair" };
|
|
19625
20136
|
}
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19629
|
-
|
|
19630
|
-
|
|
20137
|
+
return { action: "run", mode: "repair" };
|
|
20138
|
+
}
|
|
20139
|
+
async readBootstrapCompletionMarker() {
|
|
20140
|
+
return this.kvForSpace(this.ownedSpaceId(import_sdk_core7.ACCOUNT_REGISTRY_SPACE)).get(
|
|
20141
|
+
BOOTSTRAP_COMPLETION_MARKER_KEY
|
|
20142
|
+
);
|
|
20143
|
+
}
|
|
20144
|
+
async writeBootstrapCompletionMarker(steps) {
|
|
20145
|
+
if (!this._address) {
|
|
20146
|
+
throw new Error("Bootstrap completion marker requires an active wallet session");
|
|
20147
|
+
}
|
|
20148
|
+
const canonicalStepIds = new Set(
|
|
20149
|
+
(0, import_sdk_core7.bootstrapSteps)(this._address, this._chainId).map((step) => step.id)
|
|
20150
|
+
);
|
|
20151
|
+
const providedStepIds = new Set(steps.map((step) => step.id));
|
|
20152
|
+
if (canonicalStepIds.size !== providedStepIds.size || [...canonicalStepIds].some((stepId) => !providedStepIds.has(stepId))) {
|
|
20153
|
+
throw new Error("Bootstrap completion marker requires the canonical bootstrap step set");
|
|
20154
|
+
}
|
|
20155
|
+
const marker = {
|
|
20156
|
+
v: BOOTSTRAP_COMPLETION_MARKER_VERSION,
|
|
20157
|
+
stepIds: [...canonicalStepIds],
|
|
20158
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20159
|
+
};
|
|
20160
|
+
const written = await this.kvForSpace(this.ownedSpaceId(import_sdk_core7.ACCOUNT_REGISTRY_SPACE)).put(
|
|
20161
|
+
BOOTSTRAP_COMPLETION_MARKER_KEY,
|
|
20162
|
+
marker
|
|
20163
|
+
);
|
|
20164
|
+
if (!written.ok) {
|
|
20165
|
+
throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
|
|
19631
20166
|
}
|
|
19632
20167
|
}
|
|
19633
|
-
|
|
20168
|
+
isAcceptedBootstrapCompletionMarker(value) {
|
|
20169
|
+
if (typeof value !== "object" || value === null) return false;
|
|
20170
|
+
const entries = Object.entries(value);
|
|
20171
|
+
const markerValue = (name) => entries.find(([key2]) => key2 === name)?.[1];
|
|
20172
|
+
const version23 = markerValue("v");
|
|
20173
|
+
return typeof version23 === "number" && Number.isInteger(version23) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version23);
|
|
20174
|
+
}
|
|
20175
|
+
markerReadIsUnhostedSpace(meta) {
|
|
20176
|
+
return meta?.status === 404;
|
|
20177
|
+
}
|
|
20178
|
+
async runAccountBootstrap(steps, { mode } = { mode: "fresh" }) {
|
|
19634
20179
|
if (!this.auth || !this._address) {
|
|
19635
20180
|
throw new Error("Account bootstrap requires an active wallet session");
|
|
19636
20181
|
}
|
|
@@ -19731,9 +20276,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19731
20276
|
if (step.kind === "seed-applications") {
|
|
19732
20277
|
const registered = await this.account.applications.register(
|
|
19733
20278
|
step.manifests.length > 0 ? [...step.manifests] : import_sdk_core7.TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
|
|
19734
|
-
//
|
|
19735
|
-
// manifest-hash pre-read
|
|
19736
|
-
// it guards is an INSERT OR REPLACE and is safe to repeat.
|
|
20279
|
+
// The write is an INSERT OR REPLACE and is safe to repeat during a
|
|
20280
|
+
// repair, so skip the manifest-hash pre-read.
|
|
19737
20281
|
{ assumeUnregistered: true }
|
|
19738
20282
|
);
|
|
19739
20283
|
if (!registered.ok) {
|
|
@@ -19741,7 +20285,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19741
20285
|
}
|
|
19742
20286
|
}
|
|
19743
20287
|
if (step.kind === "encryption-network-create") {
|
|
19744
|
-
await this.ensureEncryptionNetwork(step.networkId, { assumeMissing:
|
|
20288
|
+
await this.ensureEncryptionNetwork(step.networkId, { assumeMissing: mode === "fresh" });
|
|
19745
20289
|
}
|
|
19746
20290
|
if (step.kind === "secret-records-schema") {
|
|
19747
20291
|
const db = this.sqlForSpace(step.spaceId).db(step.database);
|
|
@@ -19934,16 +20478,48 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
19934
20478
|
}
|
|
19935
20479
|
}
|
|
19936
20480
|
scheduleAccountRegistrySync() {
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
19942
|
-
|
|
19943
|
-
|
|
20481
|
+
const session = this.currentTinyCloudSession();
|
|
20482
|
+
if (!session || this.pendingAccountRegistrySync?.session === session) {
|
|
20483
|
+
return;
|
|
20484
|
+
}
|
|
20485
|
+
const promise = this.enqueueAccountRegistryOperation(session, async () => {
|
|
20486
|
+
await this.withAccountRegistryRetry(async () => {
|
|
20487
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
20488
|
+
return;
|
|
20489
|
+
}
|
|
20490
|
+
await this.account.index.ensure();
|
|
20491
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
20492
|
+
return;
|
|
19944
20493
|
}
|
|
20494
|
+
await this.writeManifestRegistryRecords();
|
|
20495
|
+
if (this.currentTinyCloudSession() !== session) {
|
|
20496
|
+
return;
|
|
20497
|
+
}
|
|
20498
|
+
if (this.currentSessionCanListSpaces()) {
|
|
20499
|
+
const spaces = await this.account.spaces.syncAccessible();
|
|
20500
|
+
if (!spaces.ok) {
|
|
20501
|
+
throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
|
|
20502
|
+
}
|
|
20503
|
+
}
|
|
20504
|
+
});
|
|
20505
|
+
});
|
|
20506
|
+
this.pendingAccountRegistrySync = { session, promise };
|
|
20507
|
+
const clearPendingSync = () => {
|
|
20508
|
+
if (this.pendingAccountRegistrySync?.promise === promise) {
|
|
20509
|
+
this.pendingAccountRegistrySync = void 0;
|
|
19945
20510
|
}
|
|
20511
|
+
};
|
|
20512
|
+
void promise.then(clearPendingSync, clearPendingSync);
|
|
20513
|
+
}
|
|
20514
|
+
enqueueAccountRegistryOperation(session, task) {
|
|
20515
|
+
const operation = this.accountRegistryTail.then(async () => {
|
|
20516
|
+
if (this.currentTinyCloudSession() === session) {
|
|
20517
|
+
await task();
|
|
20518
|
+
}
|
|
20519
|
+
});
|
|
20520
|
+
this.accountRegistryTail = operation.catch(() => {
|
|
19946
20521
|
});
|
|
20522
|
+
return operation;
|
|
19947
20523
|
}
|
|
19948
20524
|
/**
|
|
19949
20525
|
* Whether the current primary session may invoke `tinycloud.space/list`.
|
|
@@ -20096,7 +20672,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20096
20672
|
* @returns The hosted space URI.
|
|
20097
20673
|
*/
|
|
20098
20674
|
async hostOwnedSpace(name) {
|
|
20099
|
-
|
|
20675
|
+
const session = this.auth?.tinyCloudSession;
|
|
20676
|
+
if (!this.auth || !session) {
|
|
20100
20677
|
throw new Error("Not signed in. Call signIn() first.");
|
|
20101
20678
|
}
|
|
20102
20679
|
const spaceId = this.ownedSpaceId(name);
|
|
@@ -20112,22 +20689,26 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20112
20689
|
}
|
|
20113
20690
|
const activation = await (0, import_sdk_core7.activateSessionWithHost)(
|
|
20114
20691
|
host,
|
|
20115
|
-
|
|
20692
|
+
session.delegationHeader
|
|
20116
20693
|
);
|
|
20117
20694
|
if (!activation.success || activation.skipped?.includes(spaceId)) {
|
|
20118
20695
|
throw new Error(
|
|
20119
20696
|
`Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
|
|
20120
20697
|
);
|
|
20121
20698
|
}
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20699
|
+
try {
|
|
20700
|
+
await this.enqueueAccountRegistryOperation(session, async () => {
|
|
20701
|
+
await this.account.spaces.register({
|
|
20702
|
+
spaceId,
|
|
20703
|
+
name,
|
|
20704
|
+
ownerDid: this.did,
|
|
20705
|
+
type: "owned",
|
|
20706
|
+
permissions: ["*"],
|
|
20707
|
+
status: "active"
|
|
20708
|
+
});
|
|
20709
|
+
});
|
|
20710
|
+
} catch {
|
|
20711
|
+
}
|
|
20131
20712
|
return spaceId;
|
|
20132
20713
|
}
|
|
20133
20714
|
/**
|
|
@@ -20162,23 +20743,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20162
20743
|
if (!this.auth || !this.auth.tinyCloudSession) {
|
|
20163
20744
|
throw new Error("Not signed in. Call signIn() first.");
|
|
20164
20745
|
}
|
|
20746
|
+
await this.accountRegistryTail;
|
|
20165
20747
|
const spaceId = this.ownedSpaceId(name);
|
|
20166
20748
|
if (await this.isOwnedSpaceRegistered(spaceId)) {
|
|
20167
20749
|
return spaceId;
|
|
20168
20750
|
}
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
permissions: ["*"],
|
|
20177
|
-
status: "active"
|
|
20178
|
-
});
|
|
20179
|
-
} catch {
|
|
20751
|
+
return this.hostOwnedSpace(name);
|
|
20752
|
+
}
|
|
20753
|
+
/** Resolve and authenticate the owner encoded by an owned-space URI. */
|
|
20754
|
+
credentialSpaceOwnerDid(spaceId) {
|
|
20755
|
+
const ownerDid = this.ownerDidFromSpaceId(spaceId);
|
|
20756
|
+
if (ownerDid === void 0 || !didPrincipalMatches2(ownerDid, this.did)) {
|
|
20757
|
+
throw new Error("Credential space owner does not match the active TinyCloud owner");
|
|
20180
20758
|
}
|
|
20181
|
-
return
|
|
20759
|
+
return ownerDid;
|
|
20182
20760
|
}
|
|
20183
20761
|
/**
|
|
20184
20762
|
* Check whether an owned space is already registered/hosted by consulting the
|
|
@@ -20723,6 +21301,106 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20723
21301
|
if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
|
|
20724
21302
|
return signBytesWithJwk(bytes, session.jwk);
|
|
20725
21303
|
}
|
|
21304
|
+
/** Author a proofless Policy/v3 owner root with the authenticated holder key. */
|
|
21305
|
+
async createUnifiedOwnerRoot(input) {
|
|
21306
|
+
const ownerDid = this.credentialHolderDid;
|
|
21307
|
+
if (input.ownerDid !== ownerDid) throw new Error("unified owner root signer does not match owner DID");
|
|
21308
|
+
const facts = {
|
|
21309
|
+
role: input.role,
|
|
21310
|
+
mode: input.role === "policy-authority" ? "policy-source" : "conditional-mint",
|
|
21311
|
+
ownerDid,
|
|
21312
|
+
policyId: input.policyId,
|
|
21313
|
+
policyDigestHex: input.policyDigestHex,
|
|
21314
|
+
policyCid: input.policyCid,
|
|
21315
|
+
contentSourceDigestHex: input.contentSourceDigestHex,
|
|
21316
|
+
capabilityCeilingHashHex: input.capabilityCeilingHashHex,
|
|
21317
|
+
nativeProjectionHashHex: input.nativeProjectionHashHex,
|
|
21318
|
+
nodeAudience: input.nodeAudience,
|
|
21319
|
+
...input.role === "policy-enforcement" ? { enforcerDid: input.audienceDid } : {}
|
|
21320
|
+
};
|
|
21321
|
+
const root = await (0, import_sdk_core7.signCompactUcanRootAuthorization)({
|
|
21322
|
+
issuerDid: ownerDid,
|
|
21323
|
+
audienceDid: input.audienceDid,
|
|
21324
|
+
attenuation: unifiedRootAttenuation(input.capabilities),
|
|
21325
|
+
facts: [facts],
|
|
21326
|
+
notBefore: Math.floor(input.notBefore.getTime() / 1e3),
|
|
21327
|
+
expiresAt: Math.floor(input.expiresAt.getTime() / 1e3),
|
|
21328
|
+
nonce: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
|
|
21329
|
+
sign: (bytes) => this.signSessionBytes(bytes)
|
|
21330
|
+
});
|
|
21331
|
+
const kv = input.capabilities.find((capability) => capability.kind === "kv");
|
|
21332
|
+
const resource = kv?.resource ?? "";
|
|
21333
|
+
const marker = resource.indexOf("/kv/");
|
|
21334
|
+
return {
|
|
21335
|
+
cid: root.cid,
|
|
21336
|
+
delegationHeader: { Authorization: root.authorization },
|
|
21337
|
+
delegateDID: input.audienceDid,
|
|
21338
|
+
spaceId: marker === -1 ? "" : resource.slice("tinycloud://".length, marker),
|
|
21339
|
+
path: marker === -1 ? resource : resource.slice(marker + 4),
|
|
21340
|
+
actions: kv?.actions ?? [],
|
|
21341
|
+
expiry: input.expiresAt
|
|
21342
|
+
};
|
|
21343
|
+
}
|
|
21344
|
+
/** Apply only an already-enabled signing policy to the exact credential request. */
|
|
21345
|
+
async autoSignCredentialBytes(bytes) {
|
|
21346
|
+
const strategy = this.config.signStrategy;
|
|
21347
|
+
if (strategy?.type === "auto-sign") return this.signSessionBytes(bytes);
|
|
21348
|
+
if (strategy?.type !== "callback" || !isOpenKeyAutoSignStrategy(strategy)) return void 0;
|
|
21349
|
+
const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
|
|
21350
|
+
if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
|
|
21351
|
+
const request = {
|
|
21352
|
+
address: this.address ?? "",
|
|
21353
|
+
chainId: this.session?.chainId ?? this._chainId,
|
|
21354
|
+
message: new TextDecoder().decode(bytes),
|
|
21355
|
+
type: "message",
|
|
21356
|
+
purpose: "message"
|
|
21357
|
+
};
|
|
21358
|
+
const decision = await strategy.handler(request);
|
|
21359
|
+
if (!decision.approved) {
|
|
21360
|
+
if (decision.needsApproval) return void 0;
|
|
21361
|
+
throw new Error(decision.reason ?? "OpenKey automatic credential signing was rejected");
|
|
21362
|
+
}
|
|
21363
|
+
if (typeof decision.signature !== "string" || !await (0, import_sdk_core7.verifyEip191MessageSignature)(
|
|
21364
|
+
request.message,
|
|
21365
|
+
decision.signature,
|
|
21366
|
+
request.address
|
|
21367
|
+
)) {
|
|
21368
|
+
throw new Error("OpenKey credential signature evidence is invalid");
|
|
21369
|
+
}
|
|
21370
|
+
return this.signSessionBytes(bytes);
|
|
21371
|
+
}
|
|
21372
|
+
/** Invoke the configured interactive approval strategy before session signing. */
|
|
21373
|
+
async approveCredentialBytes(bytes) {
|
|
21374
|
+
const strategy = this.config.signStrategy;
|
|
21375
|
+
const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
|
|
21376
|
+
if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
|
|
21377
|
+
const request = {
|
|
21378
|
+
address: this.address ?? "",
|
|
21379
|
+
chainId: this.session?.chainId ?? this._chainId,
|
|
21380
|
+
message: new TextDecoder().decode(bytes),
|
|
21381
|
+
type: "message",
|
|
21382
|
+
purpose: "message"
|
|
21383
|
+
};
|
|
21384
|
+
const approval = isOpenKeyAutoSignStrategy(strategy) ? openKeyApproval(strategy) : strategy?.type === "callback" ? strategy.handler : void 0;
|
|
21385
|
+
if (approval) {
|
|
21386
|
+
const decision = await approval(request);
|
|
21387
|
+
if (!decision.approved) throw new Error(decision.reason ?? "Credential signing was rejected");
|
|
21388
|
+
if (decision.signature !== void 0 && !await (0, import_sdk_core7.verifyEip191MessageSignature)(
|
|
21389
|
+
request.message,
|
|
21390
|
+
decision.signature,
|
|
21391
|
+
request.address
|
|
21392
|
+
)) {
|
|
21393
|
+
throw new Error("OpenKey credential signature evidence is invalid");
|
|
21394
|
+
}
|
|
21395
|
+
return this.signSessionBytes(bytes);
|
|
21396
|
+
}
|
|
21397
|
+
if (strategy?.type === "auto-reject") throw new Error("Credential signing was rejected");
|
|
21398
|
+
if (this.config.signer) {
|
|
21399
|
+
await this.config.signer.signMessage(request.message);
|
|
21400
|
+
return this.signSessionBytes(bytes);
|
|
21401
|
+
}
|
|
21402
|
+
throw new Error("Interactive OpenKey approval is not configured");
|
|
21403
|
+
}
|
|
20726
21404
|
/** Bind a session-signed invocation to the canonical service audience. */
|
|
20727
21405
|
async bindInvocationAudience(authorization, audience) {
|
|
20728
21406
|
const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
|
|
@@ -20790,7 +21468,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20790
21468
|
nonce: this.config.nonce,
|
|
20791
21469
|
siweConfig: this.config.siweConfig,
|
|
20792
21470
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
20793
|
-
capabilityRequest: useBootstrapSignInRequest ? import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
21471
|
+
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? import_sdk_core7.BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
20794
21472
|
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
20795
21473
|
});
|
|
20796
21474
|
this.tc = new import_sdk_core7.TinyCloud(this.auth, {
|
|
@@ -20843,7 +21521,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20843
21521
|
nonce: this.config.nonce,
|
|
20844
21522
|
siweConfig: this.config.siweConfig,
|
|
20845
21523
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
20846
|
-
capabilityRequest: useBootstrapSignInRequest ? import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
21524
|
+
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? import_sdk_core7.BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : import_sdk_core7.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
20847
21525
|
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
20848
21526
|
});
|
|
20849
21527
|
this.tc = new import_sdk_core7.TinyCloud(this.auth, {
|
|
@@ -23592,35 +24270,44 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23592
24270
|
if (parentDelegation.disableSubDelegation) {
|
|
23593
24271
|
throw new Error("Parent delegation does not allow sub-delegation");
|
|
23594
24272
|
}
|
|
23595
|
-
|
|
24273
|
+
const parentResources = parentDelegation.resources ?? [{
|
|
24274
|
+
service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
|
|
24275
|
+
space: parentDelegation.spaceId,
|
|
24276
|
+
path: parentDelegation.path,
|
|
24277
|
+
actions: parentDelegation.actions
|
|
24278
|
+
}];
|
|
24279
|
+
const requestedResources = params.resources ?? [{
|
|
24280
|
+
service: params.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
|
|
24281
|
+
space: parentDelegation.spaceId,
|
|
24282
|
+
path: params.path,
|
|
24283
|
+
actions: params.actions
|
|
24284
|
+
}];
|
|
24285
|
+
const resourceIsContained = (parent, child) => {
|
|
24286
|
+
const parentEncryption = parent.service === "encryption" || parent.service === "tinycloud.encryption";
|
|
24287
|
+
const childEncryption = child.service === "encryption" || child.service === "tinycloud.encryption";
|
|
24288
|
+
if (parentEncryption || childEncryption) return parentEncryption && childEncryption && parent.path === child.path && child.actions.every((action) => parent.actions.includes(action));
|
|
24289
|
+
const pathContained = parent.path === child.path || parent.path.endsWith("/") && child.path.startsWith(parent.path) || child.path.startsWith(`${parent.path}/`);
|
|
24290
|
+
return pathContained && child.actions.every((action) => parent.actions.includes(action));
|
|
24291
|
+
};
|
|
24292
|
+
if (requestedResources.length === 0 || requestedResources.some((child) => !parentResources.some((parent) => resourceIsContained(parent, child)))) {
|
|
23596
24293
|
throw new Error(
|
|
23597
|
-
|
|
24294
|
+
"Sub-delegation resources exceed the received delegation"
|
|
23598
24295
|
);
|
|
23599
24296
|
}
|
|
23600
|
-
const parentActions = new Set(parentDelegation.actions);
|
|
23601
|
-
for (const action of params.actions) {
|
|
23602
|
-
if (!parentActions.has(action)) {
|
|
23603
|
-
throw new Error(
|
|
23604
|
-
`Sub-delegation action "${action}" is not in parent's actions: ${parentDelegation.actions.join(", ")}`
|
|
23605
|
-
);
|
|
23606
|
-
}
|
|
23607
|
-
}
|
|
23608
24297
|
const now = /* @__PURE__ */ new Date();
|
|
23609
24298
|
const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
|
|
23610
24299
|
const requestedExpiry = new Date(now.getTime() + expiryMs);
|
|
23611
24300
|
const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
|
|
23612
24301
|
const abilities = {};
|
|
23613
|
-
const
|
|
23614
|
-
const
|
|
23615
|
-
|
|
23616
|
-
|
|
23617
|
-
|
|
23618
|
-
|
|
23619
|
-
|
|
23620
|
-
abilities
|
|
23621
|
-
|
|
23622
|
-
if (duckdbActions.length > 0) {
|
|
23623
|
-
abilities.duckdb = { [params.path]: duckdbActions };
|
|
24302
|
+
const rawAbilities = {};
|
|
24303
|
+
for (const resource of requestedResources) {
|
|
24304
|
+
const service = resource.service.replace(/^tinycloud\./, "");
|
|
24305
|
+
if (service === "encryption") {
|
|
24306
|
+
rawAbilities[resource.path] = [...resource.actions];
|
|
24307
|
+
continue;
|
|
24308
|
+
}
|
|
24309
|
+
abilities[service] ?? (abilities[service] = {});
|
|
24310
|
+
abilities[service][resource.path] = [...resource.actions];
|
|
23624
24311
|
}
|
|
23625
24312
|
const targetHost = parentDelegation.host ?? this.config.host;
|
|
23626
24313
|
const prepared = this.wasmBindings.prepareSession({
|
|
@@ -23632,7 +24319,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23632
24319
|
expirationTime: actualExpiry.toISOString(),
|
|
23633
24320
|
spaceId: parentDelegation.spaceId,
|
|
23634
24321
|
delegateUri: params.delegateDID,
|
|
23635
|
-
parents: [parentDelegation.cid]
|
|
24322
|
+
parents: [parentDelegation.cid],
|
|
24323
|
+
...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
|
|
23636
24324
|
});
|
|
23637
24325
|
const signature2 = await this.signer.signMessage(prepared.siwe);
|
|
23638
24326
|
const subDelegationSession = this.wasmBindings.completeSessionSetup({
|
|
@@ -23650,14 +24338,20 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23650
24338
|
cid: subDelegationSession.delegationCid,
|
|
23651
24339
|
delegationHeader: subDelegationSession.delegationHeader,
|
|
23652
24340
|
spaceId: parentDelegation.spaceId,
|
|
23653
|
-
path:
|
|
23654
|
-
actions:
|
|
24341
|
+
path: requestedResources[0].path,
|
|
24342
|
+
actions: requestedResources[0].actions,
|
|
23655
24343
|
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
23656
24344
|
expiry: actualExpiry,
|
|
23657
24345
|
delegateDID: params.delegateDID,
|
|
23658
24346
|
ownerAddress: parentDelegation.ownerAddress,
|
|
23659
24347
|
chainId: parentDelegation.chainId,
|
|
23660
|
-
host: targetHost
|
|
24348
|
+
host: targetHost,
|
|
24349
|
+
resources: requestedResources.map((resource) => ({
|
|
24350
|
+
service: resource.service.replace(/^tinycloud\./, ""),
|
|
24351
|
+
space: resource.service.replace(/^tinycloud\./, "") === "encryption" ? "encryption" : resource.space ?? parentDelegation.spaceId,
|
|
24352
|
+
path: resource.path,
|
|
24353
|
+
actions: [...resource.actions]
|
|
24354
|
+
}))
|
|
23661
24355
|
};
|
|
23662
24356
|
}
|
|
23663
24357
|
};
|
|
@@ -23804,6 +24498,50 @@ var FileSessionStorage = class {
|
|
|
23804
24498
|
}
|
|
23805
24499
|
};
|
|
23806
24500
|
|
|
24501
|
+
// src/authorization/openKeyBridge.ts
|
|
24502
|
+
function wireOpenKeyAuthorize(openkey) {
|
|
24503
|
+
return async (input) => {
|
|
24504
|
+
const result = await openkey.authorizeTinyCloud({
|
|
24505
|
+
protocolVersion: 1,
|
|
24506
|
+
siwe: input.siwe,
|
|
24507
|
+
keyId: input.keyId,
|
|
24508
|
+
jwk: input.jwk,
|
|
24509
|
+
host: input.host,
|
|
24510
|
+
// Forward the presentation envelope verbatim — the OpenKey SDK
|
|
24511
|
+
// validates and size-bounds it before use.
|
|
24512
|
+
presentation: input.presentation
|
|
24513
|
+
});
|
|
24514
|
+
if (result.protocolVersion !== 1) {
|
|
24515
|
+
throw new Error(
|
|
24516
|
+
`wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
|
|
24517
|
+
);
|
|
24518
|
+
}
|
|
24519
|
+
if (typeof result.signedMessage !== "string" || !result.signedMessage) {
|
|
24520
|
+
throw new Error(
|
|
24521
|
+
"wireOpenKeyAuthorize: OpenKey returned no signedMessage \u2014 cannot complete a TinyCloud session"
|
|
24522
|
+
);
|
|
24523
|
+
}
|
|
24524
|
+
if (!Array.isArray(result.selectedActionKeys)) {
|
|
24525
|
+
throw new Error(
|
|
24526
|
+
"wireOpenKeyAuthorize: OpenKey returned no selectedActionKeys"
|
|
24527
|
+
);
|
|
24528
|
+
}
|
|
24529
|
+
if (!Array.isArray(result.permissions)) {
|
|
24530
|
+
throw new Error(
|
|
24531
|
+
"wireOpenKeyAuthorize: OpenKey returned no permissions"
|
|
24532
|
+
);
|
|
24533
|
+
}
|
|
24534
|
+
return {
|
|
24535
|
+
protocolVersion: 1,
|
|
24536
|
+
address: result.address,
|
|
24537
|
+
signature: result.signature,
|
|
24538
|
+
signedMessage: result.signedMessage,
|
|
24539
|
+
selectedActionKeys: result.selectedActionKeys,
|
|
24540
|
+
permissions: result.permissions
|
|
24541
|
+
};
|
|
24542
|
+
};
|
|
24543
|
+
}
|
|
24544
|
+
|
|
23807
24545
|
// src/index.ts
|
|
23808
24546
|
var import_sdk_core12 = require("@tinycloud/sdk-core");
|
|
23809
24547
|
|
|
@@ -23841,7 +24579,7 @@ function compactUcanPayload(authorization) {
|
|
|
23841
24579
|
}
|
|
23842
24580
|
try {
|
|
23843
24581
|
const payload = JSON.parse(
|
|
23844
|
-
|
|
24582
|
+
new TextDecoder("utf-8", { fatal: true }).decode((0, import_sdk_core9.decodeBase64Url)(parts[1]))
|
|
23845
24583
|
);
|
|
23846
24584
|
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
23847
24585
|
throw new Error("payload is not an object");
|
|
@@ -23892,6 +24630,14 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23892
24630
|
if (typeof payload.aud !== "string" || payload.aud.length === 0) {
|
|
23893
24631
|
throw new Error("Validated runtime delegation is missing a signed audience.");
|
|
23894
24632
|
}
|
|
24633
|
+
if (typeof payload.iss !== "string" || payload.iss.length === 0) {
|
|
24634
|
+
throw new Error("Validated runtime delegation is missing a signed issuer.");
|
|
24635
|
+
}
|
|
24636
|
+
if (!Array.isArray(payload.prf) || payload.prf.some(
|
|
24637
|
+
(proof) => typeof proof !== "string" || proof.length === 0
|
|
24638
|
+
)) {
|
|
24639
|
+
throw new Error("Validated runtime delegation has malformed signed proofs.");
|
|
24640
|
+
}
|
|
23895
24641
|
if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
|
|
23896
24642
|
throw new Error("Validated runtime delegation is missing a signed expiry.");
|
|
23897
24643
|
}
|
|
@@ -23902,6 +24648,11 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23902
24648
|
if (payload.att === null || typeof payload.att !== "object" || Array.isArray(payload.att)) {
|
|
23903
24649
|
throw new Error("Validated runtime delegation is missing a signed attenuation.");
|
|
23904
24650
|
}
|
|
24651
|
+
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;
|
|
24652
|
+
const remainingRedelegationDepth = fact?.remainingRedelegationDepth;
|
|
24653
|
+
if (remainingRedelegationDepth !== void 0 && (!Number.isInteger(remainingRedelegationDepth) || remainingRedelegationDepth < 0 || remainingRedelegationDepth > 8)) {
|
|
24654
|
+
throw new Error("Validated runtime delegation has invalid signed redelegation depth.");
|
|
24655
|
+
}
|
|
23905
24656
|
const permissions = [];
|
|
23906
24657
|
for (const [resource, abilities] of Object.entries(payload.att)) {
|
|
23907
24658
|
if (abilities === null || typeof abilities !== "object" || Array.isArray(abilities)) {
|
|
@@ -23920,7 +24671,9 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23920
24671
|
));
|
|
23921
24672
|
continue;
|
|
23922
24673
|
}
|
|
23923
|
-
const
|
|
24674
|
+
const nativeMatch = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
|
|
24675
|
+
const policyMatch = /^tinycloud:\/\/([^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
|
|
24676
|
+
const match = nativeMatch ?? policyMatch;
|
|
23924
24677
|
if (!match) {
|
|
23925
24678
|
throw new Error(
|
|
23926
24679
|
`Validated runtime delegation has an unsupported signed resource '${resource}'.`
|
|
@@ -23952,8 +24705,11 @@ function signedAuthorityFromCompactUcan(authorization) {
|
|
|
23952
24705
|
...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
|
|
23953
24706
|
}));
|
|
23954
24707
|
return {
|
|
24708
|
+
issuer: payload.iss,
|
|
23955
24709
|
audience: payload.aud,
|
|
23956
24710
|
expiry,
|
|
24711
|
+
proofs: [...payload.prf],
|
|
24712
|
+
...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
|
|
23957
24713
|
permissions: canonicalPermissions,
|
|
23958
24714
|
resources: canonicalResources
|
|
23959
24715
|
};
|
|
@@ -24044,6 +24800,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
24044
24800
|
delegationHeader: { Authorization: authorization },
|
|
24045
24801
|
ownerAddress: delegation.ownerAddress,
|
|
24046
24802
|
chainId: delegation.chainId,
|
|
24803
|
+
delegatorDID: signed2.issuer,
|
|
24804
|
+
...signed2.proofs.length === 1 ? { parentCid: signed2.proofs[0] } : {},
|
|
24805
|
+
...signed2.remainingRedelegationDepth === void 0 ? {} : {
|
|
24806
|
+
allowSubDelegation: signed2.remainingRedelegationDepth > 0,
|
|
24807
|
+
disableSubDelegation: signed2.remainingRedelegationDepth === 0
|
|
24808
|
+
},
|
|
24809
|
+
authHeader: authorization,
|
|
24047
24810
|
spaceId: signedSpace ?? delegation.spaceId,
|
|
24048
24811
|
path: primary.path,
|
|
24049
24812
|
actions: [...primary.actions],
|
|
@@ -24082,6 +24845,40 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
24082
24845
|
host: normalizedHost(installed.host ?? host)
|
|
24083
24846
|
};
|
|
24084
24847
|
}
|
|
24848
|
+
async function activateCompactRuntimeDelegation(node, input) {
|
|
24849
|
+
const authorization = input.authorization;
|
|
24850
|
+
const cid = node.computeDelegationCid(authorization);
|
|
24851
|
+
if (cid !== input.cid) {
|
|
24852
|
+
throw new Error("Compact runtime delegation CID does not match authorization bytes.");
|
|
24853
|
+
}
|
|
24854
|
+
const signed2 = signedAuthorityFromCompactUcan(authorization);
|
|
24855
|
+
const primary = signed2.resources[0];
|
|
24856
|
+
const signedSpace = signed2.permissions.find(
|
|
24857
|
+
(permission) => permission.service !== "tinycloud.encryption"
|
|
24858
|
+
)?.space;
|
|
24859
|
+
const delegation = {
|
|
24860
|
+
cid,
|
|
24861
|
+
delegationHeader: { Authorization: authorization },
|
|
24862
|
+
ownerAddress: input.ownerAddress,
|
|
24863
|
+
chainId: input.chainId,
|
|
24864
|
+
delegatorDID: signed2.issuer,
|
|
24865
|
+
authHeader: authorization,
|
|
24866
|
+
spaceId: signedSpace ?? primary.space,
|
|
24867
|
+
path: primary.path,
|
|
24868
|
+
actions: [...primary.actions],
|
|
24869
|
+
...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
|
|
24870
|
+
resources: signed2.resources.map((resource) => ({
|
|
24871
|
+
...resource,
|
|
24872
|
+
actions: [...resource.actions]
|
|
24873
|
+
})),
|
|
24874
|
+
expiry: new Date(signed2.expiry),
|
|
24875
|
+
delegateDID: signed2.audience,
|
|
24876
|
+
host: normalizedHost(input.host)
|
|
24877
|
+
};
|
|
24878
|
+
return activateValidatedRuntimeDelegation(node, delegation, {
|
|
24879
|
+
host: input.host
|
|
24880
|
+
});
|
|
24881
|
+
}
|
|
24085
24882
|
async function grantAuthRequest(authority, request, options) {
|
|
24086
24883
|
if (request.kind !== "tinycloud.auth.request") {
|
|
24087
24884
|
throw new Error(
|
|
@@ -24137,10 +24934,13 @@ var import_sdk_core22 = require("@tinycloud/sdk-core");
|
|
|
24137
24934
|
var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
24138
24935
|
// Annotate the CommonJS export names for ESM import in node:
|
|
24139
24936
|
0 && (module.exports = {
|
|
24937
|
+
ACCEPTED_MARKER_VERSIONS,
|
|
24140
24938
|
ACCOUNT_REGISTRY_PATH,
|
|
24141
24939
|
ACCOUNT_REGISTRY_SPACE,
|
|
24142
24940
|
AccountService,
|
|
24143
24941
|
AutoApproveSpaceCreationHandler,
|
|
24942
|
+
BOOTSTRAP_COMPLETION_MARKER_KEY,
|
|
24943
|
+
BOOTSTRAP_COMPLETION_MARKER_VERSION,
|
|
24144
24944
|
CapabilityKeyRegistry,
|
|
24145
24945
|
CapabilityKeyRegistryErrorCodes,
|
|
24146
24946
|
CaveatedDelegationUnsupportedError,
|
|
@@ -24201,6 +25001,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
|
24201
25001
|
VaultPublicSpaceKVActions,
|
|
24202
25002
|
VersionCheckError,
|
|
24203
25003
|
WasmKeyProvider,
|
|
25004
|
+
activateCompactRuntimeDelegation,
|
|
24204
25005
|
activateValidatedRuntimeDelegation,
|
|
24205
25006
|
addressStorageKey,
|
|
24206
25007
|
buildCanonicalDecryptRequest,
|
|
@@ -24277,7 +25078,8 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
|
|
|
24277
25078
|
tinyCloudDebugLogger,
|
|
24278
25079
|
validateEnvelope,
|
|
24279
25080
|
validateManifest,
|
|
24280
|
-
verifyDecryptResponse
|
|
25081
|
+
verifyDecryptResponse,
|
|
25082
|
+
wireOpenKeyAuthorize
|
|
24281
25083
|
});
|
|
24282
25084
|
/*! Bundled license information:
|
|
24283
25085
|
|