@tinycloud/sdk-core 3.0.0-beta.0 → 3.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -565,6 +565,17 @@ function isDigest(value) {
565
565
 
566
566
  // src/credentials/requirement.ts
567
567
  var ID2 = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
568
+ var EMAIL_CREDENTIAL_MAX_AGE_SECONDS = 3600;
569
+ function createEmailCredentialRequirement(input) {
570
+ return validateCredentialRequirement({
571
+ type: "TinyCloudCredentialRequirement",
572
+ version: 1,
573
+ profile: input.profile,
574
+ credentialType: input.credentialType,
575
+ claims: { email: input.email },
576
+ maxAgeSeconds: EMAIL_CREDENTIAL_MAX_AGE_SECONDS
577
+ });
578
+ }
568
579
  function validateCredentialRequirement(value) {
569
580
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
570
581
  const raw = value;
@@ -3429,6 +3440,7 @@ async function requestPolicyChallengeV3(input) {
3429
3440
  const response = await fetchFn(new URL("/share/v3/policy/challenges", input.nodeOrigin), {
3430
3441
  method: "POST",
3431
3442
  redirect: "error",
3443
+ signal: input.signal,
3432
3444
  headers: { accept: "application/json", "content-type": "application/json" },
3433
3445
  body: JSON.stringify({ policyCid: input.policyCid, recipientDid: input.recipientDid, requestedCapabilities: input.requestedCapabilities.map(normalizeUnifiedPolicyCapability) })
3434
3446
  });
@@ -3598,6 +3610,11 @@ var POLICY_SESSION_FACT_KEYS = [
3598
3610
  "issuanceAuditDigestHex",
3599
3611
  "remainingRedelegationDepth"
3600
3612
  ];
3613
+ var POLICY_SESSION_V4_AUDIT_FACT_KEYS = [
3614
+ "credentialIdAuditDigestHex",
3615
+ "presentationJtiAuditDigestHex"
3616
+ ];
3617
+ var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
3601
3618
  var KV_ACTIONS = /* @__PURE__ */ new Set([
3602
3619
  "tinycloud.kv/get",
3603
3620
  "tinycloud.kv/list",
@@ -3990,9 +4007,13 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
3990
4007
  if (expectedProofs !== void 0 && (parsed.payload.prf[0] !== expectedProofs[0] || parsed.payload.prf[1] !== expectedProofs[1]))
3991
4008
  throw new Error("policy session UCAN ordered proofs do not match");
3992
4009
  const factKeys = Object.keys(fact);
3993
- if (factKeys.length !== POLICY_SESSION_FACT_KEYS.length || factKeys.some((key) => !POLICY_SESSION_FACT_KEYS.includes(key)) || POLICY_SESSION_FACT_KEYS.some((key) => !(key in fact)))
4010
+ const hasLegacyFactShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
4011
+ const hasV4AuditFactShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
4012
+ if (!hasLegacyFactShape && !hasV4AuditFactShape)
3994
4013
  throw new Error("policy session UCAN fact is incomplete");
3995
- if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf > 60 || parsed.payload.iss.split("#", 1)[0] !== fact.enforcerDid || parsed.payload.aud !== fact.recipientDid)
4014
+ if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
4015
+ throw new Error("policy session UCAN v4 audit facts are invalid");
4016
+ if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf > 60 || parsed.payload.iss.split("#", 1)[0] !== fact.nodeAudience || parsed.payload.aud !== fact.recipientDid)
3996
4017
  throw new Error("policy session UCAN fact is invalid");
3997
4018
  return {
3998
4019
  authorization,
@@ -4051,6 +4072,8 @@ var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
4051
4072
  var POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE = "TinyCloudPolicyCredentialRequirement";
4052
4073
  var POLICY_PRESENTATION_V3_SCHEMA = "xyz.tinycloud.policy/presentation/v3";
4053
4074
  var POLICY_PRESENTATION_V3_DOMAIN = "xyz.tinycloud.policy/Presentation/v3\0";
4075
+ var POLICY_PRESENTATION_V4_SCHEMA = "xyz.tinycloud.policy/presentation/v4";
4076
+ var POLICY_PRESENTATION_V4_DOMAIN = "xyz.tinycloud.policy/Presentation/v4\0";
4054
4077
  function record(value, label) {
4055
4078
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4056
4079
  throw new Error(`${label} must be an object`);
@@ -4225,6 +4248,97 @@ function envelopeFromVerified(verified) {
4225
4248
  } = verified;
4226
4249
  return envelope;
4227
4250
  }
4251
+ function issuedCredentialEnvelopeFromVerified(verified) {
4252
+ return envelopeFromVerified(verified);
4253
+ }
4254
+ async function signPolicyCredentialPresentationV4(value, sign) {
4255
+ const item = record(value, "Policy presentation v4");
4256
+ exactKeys2(item, [
4257
+ "schema",
4258
+ "jti",
4259
+ "challengeId",
4260
+ "nonce",
4261
+ "policyCid",
4262
+ "nodeAudience",
4263
+ "holderDid",
4264
+ "subjectDid",
4265
+ "credentialDigest",
4266
+ "requirementDigest",
4267
+ "descriptorDigest",
4268
+ "requestedCapabilities",
4269
+ "issuedAt",
4270
+ "expiresAt"
4271
+ ], "Policy presentation v4");
4272
+ if (item.schema !== POLICY_PRESENTATION_V4_SCHEMA || item.holderDid !== item.subjectDid || !Array.isArray(item.requestedCapabilities)) throw new Error("Policy presentation v4 is invalid");
4273
+ const holderDid = nonEmpty(item.holderDid, "Policy presentation v4 holder DID");
4274
+ didKeyBytes(holderDid);
4275
+ const unsigned = Object.freeze({
4276
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4277
+ jti: nonEmpty(item.jti, "Policy presentation v4 jti"),
4278
+ challengeId: nonEmpty(item.challengeId, "Policy presentation v4 challenge"),
4279
+ nonce: nonEmpty(item.nonce, "Policy presentation v4 nonce"),
4280
+ policyCid: nonEmpty(item.policyCid, "Policy presentation v4 policy CID"),
4281
+ nodeAudience: nonEmpty(item.nodeAudience, "Policy presentation v4 audience"),
4282
+ holderDid,
4283
+ subjectDid: holderDid,
4284
+ credentialDigest: nonEmpty(item.credentialDigest, "Policy presentation v4 credential digest"),
4285
+ requirementDigest: nonEmpty(item.requirementDigest, "Policy presentation v4 requirement digest"),
4286
+ descriptorDigest: nonEmpty(item.descriptorDigest, "Policy presentation v4 descriptor digest"),
4287
+ // The low-level signer preserves the frozen wire bytes exactly. The
4288
+ // policy-aware builder performs capability normalization and containment.
4289
+ requestedCapabilities: Object.freeze(item.requestedCapabilities),
4290
+ issuedAt: date(item.issuedAt, "Policy presentation v4 issuedAt"),
4291
+ expiresAt: date(item.expiresAt, "Policy presentation v4 expiresAt")
4292
+ });
4293
+ const digest4 = sha2565(new TextEncoder().encode(
4294
+ POLICY_PRESENTATION_V4_DOMAIN + jcsCanonicalize(unsigned)
4295
+ ));
4296
+ const signature = await sign(digest4);
4297
+ if (signature.length !== 64 || !ed255194.verify(signature, digest4, didKeyBytes(holderDid))) throw new Error("policy presentation signature is invalid");
4298
+ return Object.freeze({
4299
+ ...unsigned,
4300
+ signature: Object.freeze({
4301
+ suite: "Ed25519",
4302
+ signerDid: holderDid,
4303
+ value: encodeBase64Url(signature)
4304
+ })
4305
+ });
4306
+ }
4307
+ async function buildPolicyCredentialPresentationV4(input) {
4308
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4309
+ const requirement = validateCredentialRequirement(input.requirement);
4310
+ const commitment = policy.credentialRequirement;
4311
+ const requirementDigest = await credentialRequirementDigest(requirement);
4312
+ const credentialDigest = await sha256Base64Url(input.credential.credential);
4313
+ if (requirementDigest !== commitment.requirementDigest || input.credential.descriptorDigest !== commitment.descriptorDigest || input.credential.credentialDigest !== credentialDigest || input.credential.issuerDid !== commitment.issuerDid || input.credential.issuerKid !== commitment.issuerKid || jcsCanonicalize(input.credential.profile) !== jcsCanonicalize(commitment.profile) || jcsCanonicalize(input.credential.credentialType) !== jcsCanonicalize(commitment.credentialType)) throw new Error("credential does not match the signed Policy/v2 requirement");
4314
+ const requestedCapabilities = input.requestedCapabilities.map(normalizeUnifiedPolicyCapability);
4315
+ if (requestedCapabilities.length === 0 || requestedCapabilities.some((requested) => !policy.capabilityCeiling.some((authority) => unifiedPolicyCapabilityContains(authority, requested)))) throw new Error("requested capabilities exceed the signed Policy/v2 ceiling");
4316
+ const holderDid = input.credential.holderDid;
4317
+ if (input.challenge.policyCid !== input.policyCid || input.challenge.recipientDid !== holderDid || input.credential.subjectDid !== holderDid || typeof input.challenge.nodeAudience !== "string" || input.challenge.nodeAudience.length === 0 || typeof input.challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(input.challenge.expiresAt))) throw new Error("policy challenge binding is invalid");
4318
+ const now = input.now ?? /* @__PURE__ */ new Date();
4319
+ const expiresAtMs = Math.min(
4320
+ now.getTime() + 6e4,
4321
+ Date.parse(input.challenge.expiresAt),
4322
+ Date.parse(input.credential.expiresAt)
4323
+ );
4324
+ if (expiresAtMs <= now.getTime()) throw new Error("policy challenge is expired");
4325
+ return signPolicyCredentialPresentationV4(Object.freeze({
4326
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4327
+ jti: input.jti ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16))),
4328
+ challengeId: input.challenge.challengeId,
4329
+ nonce: input.challenge.nonce,
4330
+ policyCid: input.policyCid,
4331
+ nodeAudience: input.challenge.nodeAudience,
4332
+ holderDid,
4333
+ subjectDid: holderDid,
4334
+ credentialDigest,
4335
+ requirementDigest,
4336
+ descriptorDigest: input.credential.descriptorDigest,
4337
+ requestedCapabilities: Object.freeze(requestedCapabilities),
4338
+ issuedAt: rfc3339Seconds(now),
4339
+ expiresAt: rfc3339Seconds(new Date(expiresAtMs))
4340
+ }), input.sign);
4341
+ }
4228
4342
  async function buildPolicyCredentialPresentationV3(input) {
4229
4343
  const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4230
4344
  const requirement = validateCredentialRequirement(input.requirement);
@@ -4314,7 +4428,8 @@ async function admitPolicyCredentialV3(input) {
4314
4428
  policyCid: input.policyCid,
4315
4429
  recipientDid: input.credential.holderDid,
4316
4430
  requestedCapabilities: input.requestedCapabilities,
4317
- fetch: fetchFn
4431
+ fetch: fetchFn,
4432
+ signal: input.signal
4318
4433
  });
4319
4434
  const presentation = await buildPolicyCredentialPresentationV3({
4320
4435
  ...input,
@@ -4326,6 +4441,7 @@ async function admitPolicyCredentialV3(input) {
4326
4441
  {
4327
4442
  method: "POST",
4328
4443
  redirect: "error",
4444
+ signal: input.signal,
4329
4445
  headers: { accept: "application/json", "content-type": "application/json" },
4330
4446
  body: JSON.stringify({
4331
4447
  policyCid: input.policyCid,
@@ -4354,6 +4470,57 @@ async function admitPolicyCredentialV3(input) {
4354
4470
  if (session.cid !== value.sessionCid || session.aud !== input.credential.holderDid || session.fact.policyCid !== input.policyCid || session.fact.recipientDid !== input.credential.holderDid || session.fact.policyDelegationCid !== input.policyRootCid || session.fact.enforcementDelegationCid !== input.enforcementRootCid || jcsCanonicalize(session.att) !== jcsCanonicalize(expectedAttenuation)) {
4355
4471
  throw new Error("policy delegation signed binding is invalid");
4356
4472
  }
4473
+ input.signal?.throwIfAborted();
4474
+ return Object.freeze({ challenge, presentation, session });
4475
+ }
4476
+ function validatePolicyCredentialAdmissionV4Authority(session, expected) {
4477
+ if (session.iss.split("#", 1)[0] !== expected.nodeAudience || session.fact.nodeAudience !== expected.nodeAudience || session.fact.enforcerDid !== expected.enforcerDid) throw new Error("policy delegation authority binding is invalid");
4478
+ }
4479
+ async function admitPolicyCredentialV4(input) {
4480
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4481
+ const nodeOrigin = policyNodeOrigin(input.nodeOrigin);
4482
+ const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
4483
+ const challenge = await requestPolicyChallengeV3({
4484
+ nodeOrigin,
4485
+ policyCid: input.policyCid,
4486
+ recipientDid: input.credential.holderDid,
4487
+ requestedCapabilities: input.requestedCapabilities,
4488
+ fetch: fetchFn,
4489
+ signal: input.signal
4490
+ });
4491
+ const presentation = await buildPolicyCredentialPresentationV4({
4492
+ ...input,
4493
+ policy,
4494
+ challenge
4495
+ });
4496
+ const response = await fetchFn(new URL("/share/v3/policy/delegations", nodeOrigin), {
4497
+ method: "POST",
4498
+ redirect: "error",
4499
+ signal: input.signal,
4500
+ headers: { accept: "application/json", "content-type": "application/json" },
4501
+ body: JSON.stringify({
4502
+ policyCid: input.policyCid,
4503
+ challengeId: challenge.challengeId,
4504
+ nonce: challenge.nonce,
4505
+ requirement: validateCredentialRequirement(input.requirement),
4506
+ credential: envelopeFromVerified(input.credential),
4507
+ presentation
4508
+ })
4509
+ });
4510
+ if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4511
+ const value = record(await response.json(), "policy delegation");
4512
+ if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
4513
+ const session = parsePolicySessionUcan(value.authorization, [
4514
+ input.policyRootCid,
4515
+ input.enforcementRootCid
4516
+ ]);
4517
+ const expectedAttenuation = compactAttenuationForPolicyCapabilities(input.requestedCapabilities);
4518
+ validatePolicyCredentialAdmissionV4Authority(session, {
4519
+ nodeAudience: input.expectedNodeAudience,
4520
+ enforcerDid: input.expectedEnforcerDid
4521
+ });
4522
+ if (session.cid !== value.sessionCid || session.aud !== input.credential.holderDid || session.fact.policyCid !== input.policyCid || session.fact.recipientDid !== input.credential.holderDid || session.fact.policyDelegationCid !== input.policyRootCid || session.fact.enforcementDelegationCid !== input.enforcementRootCid || jcsCanonicalize(session.att) !== jcsCanonicalize(expectedAttenuation)) throw new Error("policy delegation signed binding is invalid");
4523
+ input.signal?.throwIfAborted();
4357
4524
  return Object.freeze({ challenge, presentation, session });
4358
4525
  }
4359
4526
 
@@ -15048,6 +15215,236 @@ function createOpenKeyCallbackSigningStrategy(options2) {
15048
15215
  }
15049
15216
  var defaultSignStrategy = { type: "auto-sign" };
15050
15217
 
15218
+ // src/authorization/manage-key.ts
15219
+ var TINYCLOUD_MANAGE_KEY_SCOPE = "tinycloud:manage-key";
15220
+ var TINYCLOUD_CANONICAL_IDENTITY_CLAIM = "https://tinycloud.xyz/canonical_identity";
15221
+ function requestTinyCloudManageKeyScope(scopes = []) {
15222
+ const requested = typeof scopes === "string" ? scopes.split(/\s+/u) : scopes;
15223
+ return [
15224
+ ...new Set([...requested, TINYCLOUD_MANAGE_KEY_SCOPE].filter(Boolean))
15225
+ ].sort().join(" ");
15226
+ }
15227
+ function hasTinyCloudManageKeyScope(scopes) {
15228
+ const values = Array.isArray(scopes) ? scopes : typeof scopes === "string" ? scopes.split(/\s+/u) : [];
15229
+ return values.includes(TINYCLOUD_MANAGE_KEY_SCOPE);
15230
+ }
15231
+ var OpenKeyManageKeyError = class extends Error {
15232
+ constructor(code3, message, approvalUrl, retryable = false) {
15233
+ super(message);
15234
+ this.code = code3;
15235
+ this.approvalUrl = approvalUrl;
15236
+ this.retryable = retryable;
15237
+ this.name = "OpenKeyManageKeyError";
15238
+ }
15239
+ };
15240
+ function isRecord2(value) {
15241
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15242
+ }
15243
+ function rejected(message) {
15244
+ return new OpenKeyManageKeyError("MESSAGE_REJECTED", message);
15245
+ }
15246
+ function identityMismatch(message) {
15247
+ return new OpenKeyManageKeyError("IDENTITY_MISMATCH", message);
15248
+ }
15249
+ function parseCanonicalTinyCloudIdentity(value) {
15250
+ if (!isRecord2(value))
15251
+ throw rejected("Canonical TinyCloud identity is missing");
15252
+ const expectedKeys = [
15253
+ "address",
15254
+ "chainId",
15255
+ "did",
15256
+ "keyId",
15257
+ "spaceId",
15258
+ "version"
15259
+ ];
15260
+ const actualKeys = Object.keys(value).sort();
15261
+ if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
15262
+ throw rejected("Canonical TinyCloud identity has an invalid shape");
15263
+ }
15264
+ if (value.version !== "v1")
15265
+ throw rejected("Canonical TinyCloud identity version is unsupported");
15266
+ if (typeof value.keyId !== "string" || value.keyId.trim() === "") {
15267
+ throw rejected("Canonical TinyCloud identity key ID is invalid");
15268
+ }
15269
+ if (typeof value.address !== "string") {
15270
+ throw rejected("Canonical TinyCloud identity address is invalid");
15271
+ }
15272
+ let address;
15273
+ try {
15274
+ address = canonicalizeAddress(value.address);
15275
+ } catch {
15276
+ throw rejected("Canonical TinyCloud identity address is invalid");
15277
+ }
15278
+ if (address !== value.address) {
15279
+ throw rejected(
15280
+ "Canonical TinyCloud identity address must use EIP-55 checksum casing"
15281
+ );
15282
+ }
15283
+ if (typeof value.chainId !== "number" || !Number.isSafeInteger(value.chainId) || value.chainId <= 0) {
15284
+ throw rejected("Canonical TinyCloud identity chain ID is invalid");
15285
+ }
15286
+ const did = pkhDid(address, value.chainId);
15287
+ const spacePrefix = `tinycloud:pkh:eip155:${value.chainId}:${address}:`;
15288
+ if (typeof value.spaceId !== "string" || !value.spaceId.startsWith(spacePrefix)) {
15289
+ throw rejected("Canonical TinyCloud identity space is invalid");
15290
+ }
15291
+ const spaceName = value.spaceId.slice(spacePrefix.length);
15292
+ let spaceId;
15293
+ try {
15294
+ spaceId = makePkhSpaceId(address, value.chainId, spaceName);
15295
+ } catch {
15296
+ throw rejected("Canonical TinyCloud identity space is invalid");
15297
+ }
15298
+ if (value.did !== did || value.spaceId !== spaceId) {
15299
+ throw rejected("Canonical TinyCloud identity does not match its address");
15300
+ }
15301
+ return {
15302
+ version: "v1",
15303
+ keyId: value.keyId,
15304
+ address,
15305
+ chainId: value.chainId,
15306
+ did,
15307
+ spaceId
15308
+ };
15309
+ }
15310
+ function parseCanonicalTinyCloudIdentityClaims(claims) {
15311
+ if (!isRecord2(claims)) throw rejected("OAuth claims are missing");
15312
+ return parseCanonicalTinyCloudIdentity(
15313
+ claims[TINYCLOUD_CANONICAL_IDENTITY_CLAIM]
15314
+ );
15315
+ }
15316
+ function identitiesEqual(left, right) {
15317
+ return left.version === right.version && left.keyId === right.keyId && left.address === right.address && left.chainId === right.chainId && left.did === right.did && left.spaceId === right.spaceId;
15318
+ }
15319
+ function errorForResponse(body, status) {
15320
+ const message = body?.reason ?? body?.error ?? `OpenKey manage-key signing failed with HTTP ${status}`;
15321
+ switch (body?.code ?? body?.error) {
15322
+ case "missing_scope":
15323
+ case "consent_required":
15324
+ return new OpenKeyManageKeyError("CONSENT_REQUIRED", message, body?.approvalUrl);
15325
+ case "client_disabled":
15326
+ case "signing_disabled":
15327
+ case "grant_disabled":
15328
+ return new OpenKeyManageKeyError("GRANT_DISABLED", message, body?.approvalUrl);
15329
+ case "user_exclusive":
15330
+ return new OpenKeyManageKeyError("USER_EXCLUSIVE", message, body?.approvalUrl);
15331
+ case "token_expired":
15332
+ case "token_too_old":
15333
+ case "invalid_token":
15334
+ case "expired_token":
15335
+ return new OpenKeyManageKeyError("TOKEN_EXPIRED", message, body?.approvalUrl);
15336
+ case "canonical_key_unavailable":
15337
+ case "signer_failed":
15338
+ return new OpenKeyManageKeyError(
15339
+ "SIGNER_UNAVAILABLE",
15340
+ "The canonical key signer is temporarily unavailable",
15341
+ void 0,
15342
+ true
15343
+ );
15344
+ default:
15345
+ return new OpenKeyManageKeyError(
15346
+ status === 401 ? "TOKEN_EXPIRED" : "MESSAGE_REJECTED",
15347
+ message,
15348
+ body?.approvalUrl
15349
+ );
15350
+ }
15351
+ }
15352
+ async function resolveToken(token) {
15353
+ const value = typeof token === "function" ? await token() : token;
15354
+ if (typeof value !== "string" || value.trim() === "") {
15355
+ throw new OpenKeyManageKeyError(
15356
+ "CONSENT_REQUIRED",
15357
+ "A manage-key OAuth bearer token is required"
15358
+ );
15359
+ }
15360
+ return value;
15361
+ }
15362
+ function normalizeEndpoint(value) {
15363
+ let endpoint;
15364
+ try {
15365
+ endpoint = new URL(value);
15366
+ } catch {
15367
+ throw rejected("OpenKey manage-key signing endpoint must be an absolute URL");
15368
+ }
15369
+ const rawHttpAuthority = value.match(/^http:\/\/([^/?#]*)/iu)?.[1];
15370
+ const loopback = rawHttpAuthority !== void 0 && /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/u.test(rawHttpAuthority);
15371
+ if (endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && loopback) || endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "") {
15372
+ throw rejected("OpenKey manage-key signing endpoint must use HTTPS or exact loopback HTTP");
15373
+ }
15374
+ return endpoint.href;
15375
+ }
15376
+ function assertSignInRequest(request, identity3) {
15377
+ if (request.type !== "siwe" || request.purpose !== "sign-in" || request.chainId !== identity3.chainId || request.address !== identity3.address || typeof request.message !== "string" || request.message.length === 0) {
15378
+ throw rejected(
15379
+ "Manage-key signing only permits the canonical sign-in SIWE request"
15380
+ );
15381
+ }
15382
+ }
15383
+ function createOpenKeyManageKeySigningStrategy(options2) {
15384
+ const identity3 = parseCanonicalTinyCloudIdentity(options2.identity);
15385
+ const endpoint = normalizeEndpoint(options2.endpoint);
15386
+ return {
15387
+ type: "callback",
15388
+ openKeyAutoSign: true,
15389
+ handler: async (request) => {
15390
+ assertSignInRequest(request, identity3);
15391
+ const fetchImpl = options2.fetch ?? globalThis.fetch;
15392
+ if (!fetchImpl)
15393
+ throw rejected("OpenKey manage-key signing requires fetch");
15394
+ if (!hasTinyCloudManageKeyScope(options2.scopes)) {
15395
+ throw new OpenKeyManageKeyError(
15396
+ "CONSENT_REQUIRED",
15397
+ "The OAuth bearer token was not granted tinycloud:manage-key"
15398
+ );
15399
+ }
15400
+ const token = await resolveToken(options2.token);
15401
+ const response = await fetchImpl(endpoint, {
15402
+ method: "POST",
15403
+ credentials: "omit",
15404
+ headers: {
15405
+ authorization: `Bearer ${token}`,
15406
+ "content-type": "application/json"
15407
+ },
15408
+ body: JSON.stringify({
15409
+ address: request.address,
15410
+ chainId: request.chainId,
15411
+ message: request.message,
15412
+ type: "siwe"
15413
+ })
15414
+ });
15415
+ let body;
15416
+ try {
15417
+ const value = await response.json();
15418
+ body = isRecord2(value) ? value : void 0;
15419
+ } catch {
15420
+ body = void 0;
15421
+ }
15422
+ if (!response.ok || body?.approved !== true)
15423
+ throw errorForResponse(body, response.status);
15424
+ if (typeof body.signature !== "string")
15425
+ throw rejected("OpenKey manage-key signer did not return a signature");
15426
+ const responseIdentity = parseCanonicalTinyCloudIdentity(
15427
+ body.canonicalIdentity
15428
+ );
15429
+ if (!identitiesEqual(identity3, responseIdentity)) {
15430
+ throw identityMismatch(
15431
+ "OpenKey manage-key signer returned a different canonical identity"
15432
+ );
15433
+ }
15434
+ if (!await verifyEip191MessageSignature(
15435
+ request.message,
15436
+ body.signature,
15437
+ identity3.address
15438
+ )) {
15439
+ throw rejected(
15440
+ "OpenKey manage-key signer returned an invalid signature"
15441
+ );
15442
+ }
15443
+ return { approved: true, signature: body.signature };
15444
+ }
15445
+ };
15446
+ }
15447
+
15051
15448
  // src/authorization/spaceCreation.ts
15052
15449
  var AutoApproveSpaceCreationHandler = class {
15053
15450
  /**
@@ -17778,6 +18175,7 @@ export {
17778
18175
  DuckDbService2 as DuckDbService,
17779
18176
  ED25519_JCS_SIGNATURE_SUITE,
17780
18177
  EIP191_JCS_SIGNATURE_SUITE,
18178
+ EMAIL_CREDENTIAL_MAX_AGE_SECONDS,
17781
18179
  ENCRYPTION,
17782
18180
  ENCRYPTION_MANIFEST_SPACE,
17783
18181
  ENCRYPTION_NETWORK_URN_PREFIX,
@@ -17816,6 +18214,7 @@ export {
17816
18214
  NETWORK_NAME_PATTERN,
17817
18215
  NetworkIdError,
17818
18216
  OPENKEY_ACTION_ID_SEPARATOR,
18217
+ OpenKeyManageKeyError,
17819
18218
  POLICY_CAPABILITY_V1_DOMAIN,
17820
18219
  POLICY_CAPABILITY_V1_SCHEMA,
17821
18220
  POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE,
@@ -17828,6 +18227,8 @@ export {
17828
18227
  POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA,
17829
18228
  POLICY_PRESENTATION_V3_DOMAIN,
17830
18229
  POLICY_PRESENTATION_V3_SCHEMA,
18230
+ POLICY_PRESENTATION_V4_DOMAIN,
18231
+ POLICY_PRESENTATION_V4_SCHEMA,
17831
18232
  POLICY_SCHEMA,
17832
18233
  POLICY_SESSION_UCAN_V1_PROFILE,
17833
18234
  POLICY_STATUS_SCHEMA,
@@ -17886,7 +18287,9 @@ export {
17886
18287
  SpaceService,
17887
18288
  TINYCLOUD_ACCOUNT_SPACE_MANIFEST,
17888
18289
  TINYCLOUD_APPLICATIONS_SPACE_MANIFEST,
18290
+ TINYCLOUD_CANONICAL_IDENTITY_CLAIM,
17889
18291
  TINYCLOUD_DEFAULT_SPACE_MANIFEST,
18292
+ TINYCLOUD_MANAGE_KEY_SCOPE,
17890
18293
  TINYCLOUD_PUBLIC_SPACE_MANIFEST,
17891
18294
  TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
17892
18295
  TinyCloud,
@@ -17903,6 +18306,7 @@ export {
17903
18306
  activateSessionWithHost,
17904
18307
  addressStorageKey,
17905
18308
  admitPolicyCredentialV3,
18309
+ admitPolicyCredentialV4,
17906
18310
  applyPrefix,
17907
18311
  bootstrapEncryptionNetworkId,
17908
18312
  bootstrapSpaceId,
@@ -17913,6 +18317,7 @@ export {
17913
18317
  buildDecryptInvocation,
17914
18318
  buildNetworkId2 as buildNetworkId,
17915
18319
  buildPolicyCredentialPresentationV3,
18320
+ buildPolicyCredentialPresentationV4,
17916
18321
  buildSpaceUri,
17917
18322
  canonicalDigest,
17918
18323
  canonicalHashHex,
@@ -17943,10 +18348,12 @@ export {
17943
18348
  createAndSignSignedObject,
17944
18349
  createCapabilityKeyRegistry,
17945
18350
  createDelegatedShareKey,
18351
+ createEmailCredentialRequirement,
17946
18352
  createHolderBinding,
17947
18353
  createInMemoryLocalNodeIdentityStore,
17948
18354
  createLocalStorageLocalNodeIdentityStore,
17949
18355
  createOpenKeyCallbackSigningStrategy,
18356
+ createOpenKeyManageKeySigningStrategy,
17950
18357
  createPolicyEnforcementDelegation,
17951
18358
  createShareArtifact,
17952
18359
  createShareArtifactAsync,
@@ -18005,6 +18412,7 @@ export {
18005
18412
  generateRandomReceiverKey,
18006
18413
  getPolicyRootStatusV3,
18007
18414
  getTinyCloudDebugLogs,
18415
+ hasTinyCloudManageKeyScope,
18008
18416
  hexDecode,
18009
18417
  hexEncode,
18010
18418
  holderBindingCanonicalBytes,
@@ -18019,6 +18427,7 @@ export {
18019
18427
  isNetworkId,
18020
18428
  isPlausibleOpenKeyActionId,
18021
18429
  isV2ShareLink,
18430
+ issuedCredentialEnvelopeFromVerified,
18022
18431
  jcsCanonicalize,
18023
18432
  loadManifest,
18024
18433
  locationPayloadForRecord,
@@ -18037,6 +18446,8 @@ export {
18037
18446
  openWrappedKey,
18038
18447
  parseCanonicalNetworkId,
18039
18448
  parseCanonicalRecapResource,
18449
+ parseCanonicalTinyCloudIdentity,
18450
+ parseCanonicalTinyCloudIdentityClaims,
18040
18451
  parseCompactUcanAuthorization,
18041
18452
  parseExpiry,
18042
18453
  parseNetworkId2 as parseNetworkId,
@@ -18059,6 +18470,7 @@ export {
18059
18470
  projectUnifiedPolicyCapability,
18060
18471
  recapCaveatsEqual,
18061
18472
  renewPolicyRootStatusV3,
18473
+ requestTinyCloudManageKeyScope,
18062
18474
  resolveCloudLocation,
18063
18475
  resolveManifest,
18064
18476
  resolveManifestKnowledgeRoot,
@@ -18078,6 +18490,7 @@ export {
18078
18490
  signCompactUcanAuthorization,
18079
18491
  signCompactUcanRootAuthorization,
18080
18492
  signLocationRecord,
18493
+ signPolicyCredentialPresentationV4,
18081
18494
  signShareEnvelope,
18082
18495
  signedObjectIdFor,
18083
18496
  submitHostDelegation,