@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.cjs
CHANGED
|
@@ -98,6 +98,7 @@ __export(index_exports, {
|
|
|
98
98
|
DuckDbService: () => import_sdk_services8.DuckDbService,
|
|
99
99
|
ED25519_JCS_SIGNATURE_SUITE: () => ED25519_JCS_SIGNATURE_SUITE,
|
|
100
100
|
EIP191_JCS_SIGNATURE_SUITE: () => EIP191_JCS_SIGNATURE_SUITE,
|
|
101
|
+
EMAIL_CREDENTIAL_MAX_AGE_SECONDS: () => EMAIL_CREDENTIAL_MAX_AGE_SECONDS,
|
|
101
102
|
ENCRYPTION: () => import_bootstrap4.ENCRYPTION,
|
|
102
103
|
ENCRYPTION_MANIFEST_SPACE: () => ENCRYPTION_MANIFEST_SPACE,
|
|
103
104
|
ENCRYPTION_NETWORK_URN_PREFIX: () => import_sdk_services8.ENCRYPTION_NETWORK_URN_PREFIX,
|
|
@@ -136,6 +137,7 @@ __export(index_exports, {
|
|
|
136
137
|
NETWORK_NAME_PATTERN: () => import_sdk_services8.NETWORK_NAME_PATTERN,
|
|
137
138
|
NetworkIdError: () => import_sdk_services8.NetworkIdError,
|
|
138
139
|
OPENKEY_ACTION_ID_SEPARATOR: () => OPENKEY_ACTION_ID_SEPARATOR,
|
|
140
|
+
OpenKeyManageKeyError: () => OpenKeyManageKeyError,
|
|
139
141
|
POLICY_CAPABILITY_V1_DOMAIN: () => POLICY_CAPABILITY_V1_DOMAIN,
|
|
140
142
|
POLICY_CAPABILITY_V1_SCHEMA: () => POLICY_CAPABILITY_V1_SCHEMA,
|
|
141
143
|
POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE: () => POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE,
|
|
@@ -148,6 +150,8 @@ __export(index_exports, {
|
|
|
148
150
|
POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA: () => POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA,
|
|
149
151
|
POLICY_PRESENTATION_V3_DOMAIN: () => POLICY_PRESENTATION_V3_DOMAIN,
|
|
150
152
|
POLICY_PRESENTATION_V3_SCHEMA: () => POLICY_PRESENTATION_V3_SCHEMA,
|
|
153
|
+
POLICY_PRESENTATION_V4_DOMAIN: () => POLICY_PRESENTATION_V4_DOMAIN,
|
|
154
|
+
POLICY_PRESENTATION_V4_SCHEMA: () => POLICY_PRESENTATION_V4_SCHEMA,
|
|
151
155
|
POLICY_SCHEMA: () => POLICY_SCHEMA,
|
|
152
156
|
POLICY_SESSION_UCAN_V1_PROFILE: () => POLICY_SESSION_UCAN_V1_PROFILE,
|
|
153
157
|
POLICY_STATUS_SCHEMA: () => POLICY_STATUS_SCHEMA,
|
|
@@ -206,7 +210,9 @@ __export(index_exports, {
|
|
|
206
210
|
SpaceService: () => SpaceService,
|
|
207
211
|
TINYCLOUD_ACCOUNT_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_ACCOUNT_SPACE_MANIFEST,
|
|
208
212
|
TINYCLOUD_APPLICATIONS_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_APPLICATIONS_SPACE_MANIFEST,
|
|
213
|
+
TINYCLOUD_CANONICAL_IDENTITY_CLAIM: () => TINYCLOUD_CANONICAL_IDENTITY_CLAIM,
|
|
209
214
|
TINYCLOUD_DEFAULT_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_DEFAULT_SPACE_MANIFEST,
|
|
215
|
+
TINYCLOUD_MANAGE_KEY_SCOPE: () => TINYCLOUD_MANAGE_KEY_SCOPE,
|
|
210
216
|
TINYCLOUD_PUBLIC_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_PUBLIC_SPACE_MANIFEST,
|
|
211
217
|
TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST: () => import_bootstrap4.TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
|
|
212
218
|
TinyCloud: () => TinyCloud,
|
|
@@ -223,6 +229,7 @@ __export(index_exports, {
|
|
|
223
229
|
activateSessionWithHost: () => activateSessionWithHost,
|
|
224
230
|
addressStorageKey: () => addressStorageKey,
|
|
225
231
|
admitPolicyCredentialV3: () => admitPolicyCredentialV3,
|
|
232
|
+
admitPolicyCredentialV4: () => admitPolicyCredentialV4,
|
|
226
233
|
applyPrefix: () => applyPrefix,
|
|
227
234
|
bootstrapEncryptionNetworkId: () => import_bootstrap4.bootstrapEncryptionNetworkId,
|
|
228
235
|
bootstrapSpaceId: () => import_bootstrap4.bootstrapSpaceId,
|
|
@@ -233,6 +240,7 @@ __export(index_exports, {
|
|
|
233
240
|
buildDecryptInvocation: () => import_sdk_services8.buildDecryptInvocation,
|
|
234
241
|
buildNetworkId: () => import_sdk_services8.buildNetworkId,
|
|
235
242
|
buildPolicyCredentialPresentationV3: () => buildPolicyCredentialPresentationV3,
|
|
243
|
+
buildPolicyCredentialPresentationV4: () => buildPolicyCredentialPresentationV4,
|
|
236
244
|
buildSpaceUri: () => buildSpaceUri,
|
|
237
245
|
canonicalDigest: () => canonicalDigest,
|
|
238
246
|
canonicalHashHex: () => import_sdk_services8.canonicalHashHex,
|
|
@@ -263,10 +271,12 @@ __export(index_exports, {
|
|
|
263
271
|
createAndSignSignedObject: () => createAndSignSignedObject,
|
|
264
272
|
createCapabilityKeyRegistry: () => createCapabilityKeyRegistry,
|
|
265
273
|
createDelegatedShareKey: () => createDelegatedShareKey,
|
|
274
|
+
createEmailCredentialRequirement: () => createEmailCredentialRequirement,
|
|
266
275
|
createHolderBinding: () => createHolderBinding,
|
|
267
276
|
createInMemoryLocalNodeIdentityStore: () => createInMemoryLocalNodeIdentityStore,
|
|
268
277
|
createLocalStorageLocalNodeIdentityStore: () => createLocalStorageLocalNodeIdentityStore,
|
|
269
278
|
createOpenKeyCallbackSigningStrategy: () => createOpenKeyCallbackSigningStrategy,
|
|
279
|
+
createOpenKeyManageKeySigningStrategy: () => createOpenKeyManageKeySigningStrategy,
|
|
270
280
|
createPolicyEnforcementDelegation: () => createPolicyEnforcementDelegation,
|
|
271
281
|
createShareArtifact: () => createShareArtifact,
|
|
272
282
|
createShareArtifactAsync: () => createShareArtifactAsync,
|
|
@@ -325,6 +335,7 @@ __export(index_exports, {
|
|
|
325
335
|
generateRandomReceiverKey: () => import_sdk_services8.generateRandomReceiverKey,
|
|
326
336
|
getPolicyRootStatusV3: () => getPolicyRootStatusV3,
|
|
327
337
|
getTinyCloudDebugLogs: () => import_sdk_services8.getTinyCloudDebugLogs,
|
|
338
|
+
hasTinyCloudManageKeyScope: () => hasTinyCloudManageKeyScope,
|
|
328
339
|
hexDecode: () => import_sdk_services8.hexDecode,
|
|
329
340
|
hexEncode: () => import_sdk_services8.hexEncode,
|
|
330
341
|
holderBindingCanonicalBytes: () => holderBindingCanonicalBytes,
|
|
@@ -339,6 +350,7 @@ __export(index_exports, {
|
|
|
339
350
|
isNetworkId: () => import_sdk_services8.isNetworkId,
|
|
340
351
|
isPlausibleOpenKeyActionId: () => isPlausibleOpenKeyActionId,
|
|
341
352
|
isV2ShareLink: () => isV2ShareLink,
|
|
353
|
+
issuedCredentialEnvelopeFromVerified: () => issuedCredentialEnvelopeFromVerified,
|
|
342
354
|
jcsCanonicalize: () => jcsCanonicalize,
|
|
343
355
|
loadManifest: () => loadManifest,
|
|
344
356
|
locationPayloadForRecord: () => locationPayloadForRecord,
|
|
@@ -357,6 +369,8 @@ __export(index_exports, {
|
|
|
357
369
|
openWrappedKey: () => import_sdk_services8.openWrappedKey,
|
|
358
370
|
parseCanonicalNetworkId: () => parseCanonicalNetworkId,
|
|
359
371
|
parseCanonicalRecapResource: () => parseCanonicalRecapResource,
|
|
372
|
+
parseCanonicalTinyCloudIdentity: () => parseCanonicalTinyCloudIdentity,
|
|
373
|
+
parseCanonicalTinyCloudIdentityClaims: () => parseCanonicalTinyCloudIdentityClaims,
|
|
360
374
|
parseCompactUcanAuthorization: () => parseCompactUcanAuthorization,
|
|
361
375
|
parseExpiry: () => parseExpiry,
|
|
362
376
|
parseNetworkId: () => import_sdk_services8.parseNetworkId,
|
|
@@ -379,6 +393,7 @@ __export(index_exports, {
|
|
|
379
393
|
projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
|
|
380
394
|
recapCaveatsEqual: () => recapCaveatsEqual,
|
|
381
395
|
renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
|
|
396
|
+
requestTinyCloudManageKeyScope: () => requestTinyCloudManageKeyScope,
|
|
382
397
|
resolveCloudLocation: () => resolveCloudLocation,
|
|
383
398
|
resolveManifest: () => resolveManifest,
|
|
384
399
|
resolveManifestKnowledgeRoot: () => resolveManifestKnowledgeRoot,
|
|
@@ -398,6 +413,7 @@ __export(index_exports, {
|
|
|
398
413
|
signCompactUcanAuthorization: () => signCompactUcanAuthorization,
|
|
399
414
|
signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
|
|
400
415
|
signLocationRecord: () => signLocationRecord,
|
|
416
|
+
signPolicyCredentialPresentationV4: () => signPolicyCredentialPresentationV4,
|
|
401
417
|
signShareEnvelope: () => signShareEnvelope,
|
|
402
418
|
signedObjectIdFor: () => signedObjectIdFor,
|
|
403
419
|
submitHostDelegation: () => submitHostDelegation,
|
|
@@ -1001,6 +1017,17 @@ function isDigest(value) {
|
|
|
1001
1017
|
|
|
1002
1018
|
// src/credentials/requirement.ts
|
|
1003
1019
|
var ID2 = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
|
|
1020
|
+
var EMAIL_CREDENTIAL_MAX_AGE_SECONDS = 3600;
|
|
1021
|
+
function createEmailCredentialRequirement(input) {
|
|
1022
|
+
return validateCredentialRequirement({
|
|
1023
|
+
type: "TinyCloudCredentialRequirement",
|
|
1024
|
+
version: 1,
|
|
1025
|
+
profile: input.profile,
|
|
1026
|
+
credentialType: input.credentialType,
|
|
1027
|
+
claims: { email: input.email },
|
|
1028
|
+
maxAgeSeconds: EMAIL_CREDENTIAL_MAX_AGE_SECONDS
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1004
1031
|
function validateCredentialRequirement(value) {
|
|
1005
1032
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
|
|
1006
1033
|
const raw = value;
|
|
@@ -3865,6 +3892,7 @@ async function requestPolicyChallengeV3(input) {
|
|
|
3865
3892
|
const response = await fetchFn(new URL("/share/v3/policy/challenges", input.nodeOrigin), {
|
|
3866
3893
|
method: "POST",
|
|
3867
3894
|
redirect: "error",
|
|
3895
|
+
signal: input.signal,
|
|
3868
3896
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
3869
3897
|
body: JSON.stringify({ policyCid: input.policyCid, recipientDid: input.recipientDid, requestedCapabilities: input.requestedCapabilities.map(normalizeUnifiedPolicyCapability) })
|
|
3870
3898
|
});
|
|
@@ -4034,6 +4062,11 @@ var POLICY_SESSION_FACT_KEYS = [
|
|
|
4034
4062
|
"issuanceAuditDigestHex",
|
|
4035
4063
|
"remainingRedelegationDepth"
|
|
4036
4064
|
];
|
|
4065
|
+
var POLICY_SESSION_V4_AUDIT_FACT_KEYS = [
|
|
4066
|
+
"credentialIdAuditDigestHex",
|
|
4067
|
+
"presentationJtiAuditDigestHex"
|
|
4068
|
+
];
|
|
4069
|
+
var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
4037
4070
|
var KV_ACTIONS = /* @__PURE__ */ new Set([
|
|
4038
4071
|
"tinycloud.kv/get",
|
|
4039
4072
|
"tinycloud.kv/list",
|
|
@@ -4426,9 +4459,13 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
|
|
|
4426
4459
|
if (expectedProofs !== void 0 && (parsed.payload.prf[0] !== expectedProofs[0] || parsed.payload.prf[1] !== expectedProofs[1]))
|
|
4427
4460
|
throw new Error("policy session UCAN ordered proofs do not match");
|
|
4428
4461
|
const factKeys = Object.keys(fact);
|
|
4429
|
-
|
|
4462
|
+
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);
|
|
4463
|
+
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);
|
|
4464
|
+
if (!hasLegacyFactShape && !hasV4AuditFactShape)
|
|
4430
4465
|
throw new Error("policy session UCAN fact is incomplete");
|
|
4431
|
-
if (
|
|
4466
|
+
if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
|
|
4467
|
+
throw new Error("policy session UCAN v4 audit facts are invalid");
|
|
4468
|
+
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)
|
|
4432
4469
|
throw new Error("policy session UCAN fact is invalid");
|
|
4433
4470
|
return {
|
|
4434
4471
|
authorization,
|
|
@@ -4487,6 +4524,8 @@ var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
|
4487
4524
|
var POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE = "TinyCloudPolicyCredentialRequirement";
|
|
4488
4525
|
var POLICY_PRESENTATION_V3_SCHEMA = "xyz.tinycloud.policy/presentation/v3";
|
|
4489
4526
|
var POLICY_PRESENTATION_V3_DOMAIN = "xyz.tinycloud.policy/Presentation/v3\0";
|
|
4527
|
+
var POLICY_PRESENTATION_V4_SCHEMA = "xyz.tinycloud.policy/presentation/v4";
|
|
4528
|
+
var POLICY_PRESENTATION_V4_DOMAIN = "xyz.tinycloud.policy/Presentation/v4\0";
|
|
4490
4529
|
function record(value, label) {
|
|
4491
4530
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4492
4531
|
throw new Error(`${label} must be an object`);
|
|
@@ -4661,6 +4700,97 @@ function envelopeFromVerified(verified) {
|
|
|
4661
4700
|
} = verified;
|
|
4662
4701
|
return envelope;
|
|
4663
4702
|
}
|
|
4703
|
+
function issuedCredentialEnvelopeFromVerified(verified) {
|
|
4704
|
+
return envelopeFromVerified(verified);
|
|
4705
|
+
}
|
|
4706
|
+
async function signPolicyCredentialPresentationV4(value, sign) {
|
|
4707
|
+
const item = record(value, "Policy presentation v4");
|
|
4708
|
+
exactKeys2(item, [
|
|
4709
|
+
"schema",
|
|
4710
|
+
"jti",
|
|
4711
|
+
"challengeId",
|
|
4712
|
+
"nonce",
|
|
4713
|
+
"policyCid",
|
|
4714
|
+
"nodeAudience",
|
|
4715
|
+
"holderDid",
|
|
4716
|
+
"subjectDid",
|
|
4717
|
+
"credentialDigest",
|
|
4718
|
+
"requirementDigest",
|
|
4719
|
+
"descriptorDigest",
|
|
4720
|
+
"requestedCapabilities",
|
|
4721
|
+
"issuedAt",
|
|
4722
|
+
"expiresAt"
|
|
4723
|
+
], "Policy presentation v4");
|
|
4724
|
+
if (item.schema !== POLICY_PRESENTATION_V4_SCHEMA || item.holderDid !== item.subjectDid || !Array.isArray(item.requestedCapabilities)) throw new Error("Policy presentation v4 is invalid");
|
|
4725
|
+
const holderDid = nonEmpty(item.holderDid, "Policy presentation v4 holder DID");
|
|
4726
|
+
didKeyBytes(holderDid);
|
|
4727
|
+
const unsigned = Object.freeze({
|
|
4728
|
+
schema: POLICY_PRESENTATION_V4_SCHEMA,
|
|
4729
|
+
jti: nonEmpty(item.jti, "Policy presentation v4 jti"),
|
|
4730
|
+
challengeId: nonEmpty(item.challengeId, "Policy presentation v4 challenge"),
|
|
4731
|
+
nonce: nonEmpty(item.nonce, "Policy presentation v4 nonce"),
|
|
4732
|
+
policyCid: nonEmpty(item.policyCid, "Policy presentation v4 policy CID"),
|
|
4733
|
+
nodeAudience: nonEmpty(item.nodeAudience, "Policy presentation v4 audience"),
|
|
4734
|
+
holderDid,
|
|
4735
|
+
subjectDid: holderDid,
|
|
4736
|
+
credentialDigest: nonEmpty(item.credentialDigest, "Policy presentation v4 credential digest"),
|
|
4737
|
+
requirementDigest: nonEmpty(item.requirementDigest, "Policy presentation v4 requirement digest"),
|
|
4738
|
+
descriptorDigest: nonEmpty(item.descriptorDigest, "Policy presentation v4 descriptor digest"),
|
|
4739
|
+
// The low-level signer preserves the frozen wire bytes exactly. The
|
|
4740
|
+
// policy-aware builder performs capability normalization and containment.
|
|
4741
|
+
requestedCapabilities: Object.freeze(item.requestedCapabilities),
|
|
4742
|
+
issuedAt: date(item.issuedAt, "Policy presentation v4 issuedAt"),
|
|
4743
|
+
expiresAt: date(item.expiresAt, "Policy presentation v4 expiresAt")
|
|
4744
|
+
});
|
|
4745
|
+
const digest4 = (0, import_sha2562.sha256)(new TextEncoder().encode(
|
|
4746
|
+
POLICY_PRESENTATION_V4_DOMAIN + jcsCanonicalize(unsigned)
|
|
4747
|
+
));
|
|
4748
|
+
const signature = await sign(digest4);
|
|
4749
|
+
if (signature.length !== 64 || !import_ed255194.ed25519.verify(signature, digest4, didKeyBytes(holderDid))) throw new Error("policy presentation signature is invalid");
|
|
4750
|
+
return Object.freeze({
|
|
4751
|
+
...unsigned,
|
|
4752
|
+
signature: Object.freeze({
|
|
4753
|
+
suite: "Ed25519",
|
|
4754
|
+
signerDid: holderDid,
|
|
4755
|
+
value: encodeBase64Url(signature)
|
|
4756
|
+
})
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4759
|
+
async function buildPolicyCredentialPresentationV4(input) {
|
|
4760
|
+
const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
|
|
4761
|
+
const requirement = validateCredentialRequirement(input.requirement);
|
|
4762
|
+
const commitment = policy.credentialRequirement;
|
|
4763
|
+
const requirementDigest = await credentialRequirementDigest(requirement);
|
|
4764
|
+
const credentialDigest = await sha256Base64Url(input.credential.credential);
|
|
4765
|
+
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");
|
|
4766
|
+
const requestedCapabilities = input.requestedCapabilities.map(normalizeUnifiedPolicyCapability);
|
|
4767
|
+
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");
|
|
4768
|
+
const holderDid = input.credential.holderDid;
|
|
4769
|
+
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");
|
|
4770
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
4771
|
+
const expiresAtMs = Math.min(
|
|
4772
|
+
now.getTime() + 6e4,
|
|
4773
|
+
Date.parse(input.challenge.expiresAt),
|
|
4774
|
+
Date.parse(input.credential.expiresAt)
|
|
4775
|
+
);
|
|
4776
|
+
if (expiresAtMs <= now.getTime()) throw new Error("policy challenge is expired");
|
|
4777
|
+
return signPolicyCredentialPresentationV4(Object.freeze({
|
|
4778
|
+
schema: POLICY_PRESENTATION_V4_SCHEMA,
|
|
4779
|
+
jti: input.jti ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16))),
|
|
4780
|
+
challengeId: input.challenge.challengeId,
|
|
4781
|
+
nonce: input.challenge.nonce,
|
|
4782
|
+
policyCid: input.policyCid,
|
|
4783
|
+
nodeAudience: input.challenge.nodeAudience,
|
|
4784
|
+
holderDid,
|
|
4785
|
+
subjectDid: holderDid,
|
|
4786
|
+
credentialDigest,
|
|
4787
|
+
requirementDigest,
|
|
4788
|
+
descriptorDigest: input.credential.descriptorDigest,
|
|
4789
|
+
requestedCapabilities: Object.freeze(requestedCapabilities),
|
|
4790
|
+
issuedAt: rfc3339Seconds(now),
|
|
4791
|
+
expiresAt: rfc3339Seconds(new Date(expiresAtMs))
|
|
4792
|
+
}), input.sign);
|
|
4793
|
+
}
|
|
4664
4794
|
async function buildPolicyCredentialPresentationV3(input) {
|
|
4665
4795
|
const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
|
|
4666
4796
|
const requirement = validateCredentialRequirement(input.requirement);
|
|
@@ -4750,7 +4880,8 @@ async function admitPolicyCredentialV3(input) {
|
|
|
4750
4880
|
policyCid: input.policyCid,
|
|
4751
4881
|
recipientDid: input.credential.holderDid,
|
|
4752
4882
|
requestedCapabilities: input.requestedCapabilities,
|
|
4753
|
-
fetch: fetchFn
|
|
4883
|
+
fetch: fetchFn,
|
|
4884
|
+
signal: input.signal
|
|
4754
4885
|
});
|
|
4755
4886
|
const presentation = await buildPolicyCredentialPresentationV3({
|
|
4756
4887
|
...input,
|
|
@@ -4762,6 +4893,7 @@ async function admitPolicyCredentialV3(input) {
|
|
|
4762
4893
|
{
|
|
4763
4894
|
method: "POST",
|
|
4764
4895
|
redirect: "error",
|
|
4896
|
+
signal: input.signal,
|
|
4765
4897
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
4766
4898
|
body: JSON.stringify({
|
|
4767
4899
|
policyCid: input.policyCid,
|
|
@@ -4790,6 +4922,57 @@ async function admitPolicyCredentialV3(input) {
|
|
|
4790
4922
|
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)) {
|
|
4791
4923
|
throw new Error("policy delegation signed binding is invalid");
|
|
4792
4924
|
}
|
|
4925
|
+
input.signal?.throwIfAborted();
|
|
4926
|
+
return Object.freeze({ challenge, presentation, session });
|
|
4927
|
+
}
|
|
4928
|
+
function validatePolicyCredentialAdmissionV4Authority(session, expected) {
|
|
4929
|
+
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");
|
|
4930
|
+
}
|
|
4931
|
+
async function admitPolicyCredentialV4(input) {
|
|
4932
|
+
const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
|
|
4933
|
+
const nodeOrigin = policyNodeOrigin(input.nodeOrigin);
|
|
4934
|
+
const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
|
|
4935
|
+
const challenge = await requestPolicyChallengeV3({
|
|
4936
|
+
nodeOrigin,
|
|
4937
|
+
policyCid: input.policyCid,
|
|
4938
|
+
recipientDid: input.credential.holderDid,
|
|
4939
|
+
requestedCapabilities: input.requestedCapabilities,
|
|
4940
|
+
fetch: fetchFn,
|
|
4941
|
+
signal: input.signal
|
|
4942
|
+
});
|
|
4943
|
+
const presentation = await buildPolicyCredentialPresentationV4({
|
|
4944
|
+
...input,
|
|
4945
|
+
policy,
|
|
4946
|
+
challenge
|
|
4947
|
+
});
|
|
4948
|
+
const response = await fetchFn(new URL("/share/v3/policy/delegations", nodeOrigin), {
|
|
4949
|
+
method: "POST",
|
|
4950
|
+
redirect: "error",
|
|
4951
|
+
signal: input.signal,
|
|
4952
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
4953
|
+
body: JSON.stringify({
|
|
4954
|
+
policyCid: input.policyCid,
|
|
4955
|
+
challengeId: challenge.challengeId,
|
|
4956
|
+
nonce: challenge.nonce,
|
|
4957
|
+
requirement: validateCredentialRequirement(input.requirement),
|
|
4958
|
+
credential: envelopeFromVerified(input.credential),
|
|
4959
|
+
presentation
|
|
4960
|
+
})
|
|
4961
|
+
});
|
|
4962
|
+
if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
|
|
4963
|
+
const value = record(await response.json(), "policy delegation");
|
|
4964
|
+
if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
|
|
4965
|
+
const session = parsePolicySessionUcan(value.authorization, [
|
|
4966
|
+
input.policyRootCid,
|
|
4967
|
+
input.enforcementRootCid
|
|
4968
|
+
]);
|
|
4969
|
+
const expectedAttenuation = compactAttenuationForPolicyCapabilities(input.requestedCapabilities);
|
|
4970
|
+
validatePolicyCredentialAdmissionV4Authority(session, {
|
|
4971
|
+
nodeAudience: input.expectedNodeAudience,
|
|
4972
|
+
enforcerDid: input.expectedEnforcerDid
|
|
4973
|
+
});
|
|
4974
|
+
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");
|
|
4975
|
+
input.signal?.throwIfAborted();
|
|
4793
4976
|
return Object.freeze({ challenge, presentation, session });
|
|
4794
4977
|
}
|
|
4795
4978
|
|
|
@@ -15367,6 +15550,236 @@ function createOpenKeyCallbackSigningStrategy(options2) {
|
|
|
15367
15550
|
}
|
|
15368
15551
|
var defaultSignStrategy = { type: "auto-sign" };
|
|
15369
15552
|
|
|
15553
|
+
// src/authorization/manage-key.ts
|
|
15554
|
+
var TINYCLOUD_MANAGE_KEY_SCOPE = "tinycloud:manage-key";
|
|
15555
|
+
var TINYCLOUD_CANONICAL_IDENTITY_CLAIM = "https://tinycloud.xyz/canonical_identity";
|
|
15556
|
+
function requestTinyCloudManageKeyScope(scopes = []) {
|
|
15557
|
+
const requested = typeof scopes === "string" ? scopes.split(/\s+/u) : scopes;
|
|
15558
|
+
return [
|
|
15559
|
+
...new Set([...requested, TINYCLOUD_MANAGE_KEY_SCOPE].filter(Boolean))
|
|
15560
|
+
].sort().join(" ");
|
|
15561
|
+
}
|
|
15562
|
+
function hasTinyCloudManageKeyScope(scopes) {
|
|
15563
|
+
const values = Array.isArray(scopes) ? scopes : typeof scopes === "string" ? scopes.split(/\s+/u) : [];
|
|
15564
|
+
return values.includes(TINYCLOUD_MANAGE_KEY_SCOPE);
|
|
15565
|
+
}
|
|
15566
|
+
var OpenKeyManageKeyError = class extends Error {
|
|
15567
|
+
constructor(code3, message, approvalUrl, retryable = false) {
|
|
15568
|
+
super(message);
|
|
15569
|
+
this.code = code3;
|
|
15570
|
+
this.approvalUrl = approvalUrl;
|
|
15571
|
+
this.retryable = retryable;
|
|
15572
|
+
this.name = "OpenKeyManageKeyError";
|
|
15573
|
+
}
|
|
15574
|
+
};
|
|
15575
|
+
function isRecord2(value) {
|
|
15576
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15577
|
+
}
|
|
15578
|
+
function rejected(message) {
|
|
15579
|
+
return new OpenKeyManageKeyError("MESSAGE_REJECTED", message);
|
|
15580
|
+
}
|
|
15581
|
+
function identityMismatch(message) {
|
|
15582
|
+
return new OpenKeyManageKeyError("IDENTITY_MISMATCH", message);
|
|
15583
|
+
}
|
|
15584
|
+
function parseCanonicalTinyCloudIdentity(value) {
|
|
15585
|
+
if (!isRecord2(value))
|
|
15586
|
+
throw rejected("Canonical TinyCloud identity is missing");
|
|
15587
|
+
const expectedKeys = [
|
|
15588
|
+
"address",
|
|
15589
|
+
"chainId",
|
|
15590
|
+
"did",
|
|
15591
|
+
"keyId",
|
|
15592
|
+
"spaceId",
|
|
15593
|
+
"version"
|
|
15594
|
+
];
|
|
15595
|
+
const actualKeys = Object.keys(value).sort();
|
|
15596
|
+
if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
|
|
15597
|
+
throw rejected("Canonical TinyCloud identity has an invalid shape");
|
|
15598
|
+
}
|
|
15599
|
+
if (value.version !== "v1")
|
|
15600
|
+
throw rejected("Canonical TinyCloud identity version is unsupported");
|
|
15601
|
+
if (typeof value.keyId !== "string" || value.keyId.trim() === "") {
|
|
15602
|
+
throw rejected("Canonical TinyCloud identity key ID is invalid");
|
|
15603
|
+
}
|
|
15604
|
+
if (typeof value.address !== "string") {
|
|
15605
|
+
throw rejected("Canonical TinyCloud identity address is invalid");
|
|
15606
|
+
}
|
|
15607
|
+
let address;
|
|
15608
|
+
try {
|
|
15609
|
+
address = canonicalizeAddress(value.address);
|
|
15610
|
+
} catch {
|
|
15611
|
+
throw rejected("Canonical TinyCloud identity address is invalid");
|
|
15612
|
+
}
|
|
15613
|
+
if (address !== value.address) {
|
|
15614
|
+
throw rejected(
|
|
15615
|
+
"Canonical TinyCloud identity address must use EIP-55 checksum casing"
|
|
15616
|
+
);
|
|
15617
|
+
}
|
|
15618
|
+
if (typeof value.chainId !== "number" || !Number.isSafeInteger(value.chainId) || value.chainId <= 0) {
|
|
15619
|
+
throw rejected("Canonical TinyCloud identity chain ID is invalid");
|
|
15620
|
+
}
|
|
15621
|
+
const did = pkhDid(address, value.chainId);
|
|
15622
|
+
const spacePrefix = `tinycloud:pkh:eip155:${value.chainId}:${address}:`;
|
|
15623
|
+
if (typeof value.spaceId !== "string" || !value.spaceId.startsWith(spacePrefix)) {
|
|
15624
|
+
throw rejected("Canonical TinyCloud identity space is invalid");
|
|
15625
|
+
}
|
|
15626
|
+
const spaceName = value.spaceId.slice(spacePrefix.length);
|
|
15627
|
+
let spaceId;
|
|
15628
|
+
try {
|
|
15629
|
+
spaceId = makePkhSpaceId(address, value.chainId, spaceName);
|
|
15630
|
+
} catch {
|
|
15631
|
+
throw rejected("Canonical TinyCloud identity space is invalid");
|
|
15632
|
+
}
|
|
15633
|
+
if (value.did !== did || value.spaceId !== spaceId) {
|
|
15634
|
+
throw rejected("Canonical TinyCloud identity does not match its address");
|
|
15635
|
+
}
|
|
15636
|
+
return {
|
|
15637
|
+
version: "v1",
|
|
15638
|
+
keyId: value.keyId,
|
|
15639
|
+
address,
|
|
15640
|
+
chainId: value.chainId,
|
|
15641
|
+
did,
|
|
15642
|
+
spaceId
|
|
15643
|
+
};
|
|
15644
|
+
}
|
|
15645
|
+
function parseCanonicalTinyCloudIdentityClaims(claims) {
|
|
15646
|
+
if (!isRecord2(claims)) throw rejected("OAuth claims are missing");
|
|
15647
|
+
return parseCanonicalTinyCloudIdentity(
|
|
15648
|
+
claims[TINYCLOUD_CANONICAL_IDENTITY_CLAIM]
|
|
15649
|
+
);
|
|
15650
|
+
}
|
|
15651
|
+
function identitiesEqual(left, right) {
|
|
15652
|
+
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;
|
|
15653
|
+
}
|
|
15654
|
+
function errorForResponse(body, status) {
|
|
15655
|
+
const message = body?.reason ?? body?.error ?? `OpenKey manage-key signing failed with HTTP ${status}`;
|
|
15656
|
+
switch (body?.code ?? body?.error) {
|
|
15657
|
+
case "missing_scope":
|
|
15658
|
+
case "consent_required":
|
|
15659
|
+
return new OpenKeyManageKeyError("CONSENT_REQUIRED", message, body?.approvalUrl);
|
|
15660
|
+
case "client_disabled":
|
|
15661
|
+
case "signing_disabled":
|
|
15662
|
+
case "grant_disabled":
|
|
15663
|
+
return new OpenKeyManageKeyError("GRANT_DISABLED", message, body?.approvalUrl);
|
|
15664
|
+
case "user_exclusive":
|
|
15665
|
+
return new OpenKeyManageKeyError("USER_EXCLUSIVE", message, body?.approvalUrl);
|
|
15666
|
+
case "token_expired":
|
|
15667
|
+
case "token_too_old":
|
|
15668
|
+
case "invalid_token":
|
|
15669
|
+
case "expired_token":
|
|
15670
|
+
return new OpenKeyManageKeyError("TOKEN_EXPIRED", message, body?.approvalUrl);
|
|
15671
|
+
case "canonical_key_unavailable":
|
|
15672
|
+
case "signer_failed":
|
|
15673
|
+
return new OpenKeyManageKeyError(
|
|
15674
|
+
"SIGNER_UNAVAILABLE",
|
|
15675
|
+
"The canonical key signer is temporarily unavailable",
|
|
15676
|
+
void 0,
|
|
15677
|
+
true
|
|
15678
|
+
);
|
|
15679
|
+
default:
|
|
15680
|
+
return new OpenKeyManageKeyError(
|
|
15681
|
+
status === 401 ? "TOKEN_EXPIRED" : "MESSAGE_REJECTED",
|
|
15682
|
+
message,
|
|
15683
|
+
body?.approvalUrl
|
|
15684
|
+
);
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
async function resolveToken(token) {
|
|
15688
|
+
const value = typeof token === "function" ? await token() : token;
|
|
15689
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
15690
|
+
throw new OpenKeyManageKeyError(
|
|
15691
|
+
"CONSENT_REQUIRED",
|
|
15692
|
+
"A manage-key OAuth bearer token is required"
|
|
15693
|
+
);
|
|
15694
|
+
}
|
|
15695
|
+
return value;
|
|
15696
|
+
}
|
|
15697
|
+
function normalizeEndpoint(value) {
|
|
15698
|
+
let endpoint;
|
|
15699
|
+
try {
|
|
15700
|
+
endpoint = new URL(value);
|
|
15701
|
+
} catch {
|
|
15702
|
+
throw rejected("OpenKey manage-key signing endpoint must be an absolute URL");
|
|
15703
|
+
}
|
|
15704
|
+
const rawHttpAuthority = value.match(/^http:\/\/([^/?#]*)/iu)?.[1];
|
|
15705
|
+
const loopback = rawHttpAuthority !== void 0 && /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/u.test(rawHttpAuthority);
|
|
15706
|
+
if (endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && loopback) || endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "") {
|
|
15707
|
+
throw rejected("OpenKey manage-key signing endpoint must use HTTPS or exact loopback HTTP");
|
|
15708
|
+
}
|
|
15709
|
+
return endpoint.href;
|
|
15710
|
+
}
|
|
15711
|
+
function assertSignInRequest(request, identity3) {
|
|
15712
|
+
if (request.type !== "siwe" || request.purpose !== "sign-in" || request.chainId !== identity3.chainId || request.address !== identity3.address || typeof request.message !== "string" || request.message.length === 0) {
|
|
15713
|
+
throw rejected(
|
|
15714
|
+
"Manage-key signing only permits the canonical sign-in SIWE request"
|
|
15715
|
+
);
|
|
15716
|
+
}
|
|
15717
|
+
}
|
|
15718
|
+
function createOpenKeyManageKeySigningStrategy(options2) {
|
|
15719
|
+
const identity3 = parseCanonicalTinyCloudIdentity(options2.identity);
|
|
15720
|
+
const endpoint = normalizeEndpoint(options2.endpoint);
|
|
15721
|
+
return {
|
|
15722
|
+
type: "callback",
|
|
15723
|
+
openKeyAutoSign: true,
|
|
15724
|
+
handler: async (request) => {
|
|
15725
|
+
assertSignInRequest(request, identity3);
|
|
15726
|
+
const fetchImpl = options2.fetch ?? globalThis.fetch;
|
|
15727
|
+
if (!fetchImpl)
|
|
15728
|
+
throw rejected("OpenKey manage-key signing requires fetch");
|
|
15729
|
+
if (!hasTinyCloudManageKeyScope(options2.scopes)) {
|
|
15730
|
+
throw new OpenKeyManageKeyError(
|
|
15731
|
+
"CONSENT_REQUIRED",
|
|
15732
|
+
"The OAuth bearer token was not granted tinycloud:manage-key"
|
|
15733
|
+
);
|
|
15734
|
+
}
|
|
15735
|
+
const token = await resolveToken(options2.token);
|
|
15736
|
+
const response = await fetchImpl(endpoint, {
|
|
15737
|
+
method: "POST",
|
|
15738
|
+
credentials: "omit",
|
|
15739
|
+
headers: {
|
|
15740
|
+
authorization: `Bearer ${token}`,
|
|
15741
|
+
"content-type": "application/json"
|
|
15742
|
+
},
|
|
15743
|
+
body: JSON.stringify({
|
|
15744
|
+
address: request.address,
|
|
15745
|
+
chainId: request.chainId,
|
|
15746
|
+
message: request.message,
|
|
15747
|
+
type: "siwe"
|
|
15748
|
+
})
|
|
15749
|
+
});
|
|
15750
|
+
let body;
|
|
15751
|
+
try {
|
|
15752
|
+
const value = await response.json();
|
|
15753
|
+
body = isRecord2(value) ? value : void 0;
|
|
15754
|
+
} catch {
|
|
15755
|
+
body = void 0;
|
|
15756
|
+
}
|
|
15757
|
+
if (!response.ok || body?.approved !== true)
|
|
15758
|
+
throw errorForResponse(body, response.status);
|
|
15759
|
+
if (typeof body.signature !== "string")
|
|
15760
|
+
throw rejected("OpenKey manage-key signer did not return a signature");
|
|
15761
|
+
const responseIdentity = parseCanonicalTinyCloudIdentity(
|
|
15762
|
+
body.canonicalIdentity
|
|
15763
|
+
);
|
|
15764
|
+
if (!identitiesEqual(identity3, responseIdentity)) {
|
|
15765
|
+
throw identityMismatch(
|
|
15766
|
+
"OpenKey manage-key signer returned a different canonical identity"
|
|
15767
|
+
);
|
|
15768
|
+
}
|
|
15769
|
+
if (!await verifyEip191MessageSignature(
|
|
15770
|
+
request.message,
|
|
15771
|
+
body.signature,
|
|
15772
|
+
identity3.address
|
|
15773
|
+
)) {
|
|
15774
|
+
throw rejected(
|
|
15775
|
+
"OpenKey manage-key signer returned an invalid signature"
|
|
15776
|
+
);
|
|
15777
|
+
}
|
|
15778
|
+
return { approved: true, signature: body.signature };
|
|
15779
|
+
}
|
|
15780
|
+
};
|
|
15781
|
+
}
|
|
15782
|
+
|
|
15370
15783
|
// src/authorization/spaceCreation.ts
|
|
15371
15784
|
var AutoApproveSpaceCreationHandler = class {
|
|
15372
15785
|
/**
|