@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/{credential-admission-C_cO1QqB.d.cts → credential-admission-BVUC6kdw.d.cts} +67 -2
- package/dist/{credential-admission-cvHe-HUC.d.ts → credential-admission-BgvwOTIV.d.ts} +67 -2
- package/dist/delegations/index.cjs +11 -2
- package/dist/delegations/index.cjs.map +1 -1
- package/dist/delegations/index.js +11 -2
- package/dist/delegations/index.js.map +1 -1
- package/dist/index.cjs +416 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -3
- package/dist/index.d.ts +92 -3
- package/dist/index.js +416 -3
- package/dist/index.js.map +1 -1
- package/dist/policy/index.cjs +166 -3
- package/dist/policy/index.cjs.map +1 -1
- package/dist/policy/index.d.cts +2 -2
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +166 -3
- package/dist/policy/index.js.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
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 (
|
|
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,
|