@astrasyncai/verification-gateway 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +118 -5
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +118 -5
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +189 -9
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +189 -9
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +1 -0
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +1 -0
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +33 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +36 -0
- package/dist/agent/index.mjs.map +1 -1
- package/dist/browser/background.d.mts +2 -0
- package/dist/browser/background.d.ts +2 -0
- package/dist/browser/background.js +4123 -0
- package/dist/browser/background.js.map +1 -0
- package/dist/browser/background.mjs +4124 -0
- package/dist/browser/background.mjs.map +1 -0
- package/dist/browser/browser-adapter.d.mts +10 -6
- package/dist/browser/browser-adapter.d.ts +10 -6
- package/dist/browser/browser-adapter.js +16 -5
- package/dist/browser/browser-adapter.js.map +1 -1
- package/dist/browser/browser-adapter.mjs +14 -4
- package/dist/browser/browser-adapter.mjs.map +1 -1
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cursor/cursor-adapter.d.mts +3 -4
- package/dist/cursor/cursor-adapter.d.ts +3 -4
- package/dist/cursor/cursor-adapter.js.map +1 -1
- package/dist/cursor/cursor-adapter.mjs.map +1 -1
- package/dist/cursor/extension.d.mts +27 -0
- package/dist/cursor/extension.d.ts +27 -0
- package/dist/cursor/extension.js +4090 -0
- package/dist/cursor/extension.js.map +1 -0
- package/dist/cursor/extension.mjs +4065 -0
- package/dist/cursor/extension.mjs.map +1 -0
- package/dist/{express-DIEyq1Tz.d.ts → express-Bcl-uBUE.d.ts} +1 -1
- package/dist/{express-Cp4eg77F.d.mts → express-CtwDIZyF.d.mts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +50 -17
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +47 -18
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/git-trigger/git-hooks.js +1 -2
- package/dist/git-trigger/git-hooks.js.map +1 -1
- package/dist/git-trigger/git-hooks.mjs +1 -9
- package/dist/git-trigger/git-hooks.mjs.map +1 -1
- package/dist/index-3NRaBNvp.d.mts +1397 -0
- package/dist/{index-BhTbGU-o.d.mts → index-BY8yQ8N8.d.mts} +1 -1
- package/dist/index-CME6r4uH.d.ts +1397 -0
- package/dist/{index-Bhfxq9xI.d.ts → index-CtYSYwn3.d.ts} +1 -1
- package/dist/index.d.mts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +2525 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2528 -15
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{nextjs-_C_FcJY5.d.mts → nextjs-BQyMCSx_.d.mts} +1 -1
- package/dist/{nextjs-Cag7libc.d.ts → nextjs-CEldnIJ9.d.ts} +1 -1
- package/dist/{sdk-DAJahT3p.d.mts → sdk-BhvuJSrH.d.mts} +1 -1
- package/dist/{sdk-CMPDFUjo.d.ts → sdk-BlyVSC_S.d.ts} +1 -1
- package/dist/transport/index.d.mts +3 -2
- package/dist/transport/index.d.ts +3 -2
- package/dist/transport/index.js +2384 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +2327 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-Ce2mFJkO.d.ts → types-79qS7aON.d.ts} +2 -2
- package/dist/{types-Bf8pML07.d.mts → types-CxQwJKbd.d.mts} +17 -2
- package/dist/{types-Bf8pML07.d.ts → types-CxQwJKbd.d.ts} +17 -2
- package/dist/{types-BvpGdsv1.d.mts → types-jJnPXStc.d.mts} +2 -2
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +18 -3
- package/dist/index-CNkmHmpi.d.ts +0 -89
- package/dist/index-CoLebmwv.d.mts +0 -89
package/dist/index.js
CHANGED
|
@@ -280,6 +280,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
280
280
|
if (requestData.subAgentDepth !== void 0) body.subAgentDepth = requestData.subAgentDepth;
|
|
281
281
|
if (requestData.enableRuntimeChallenge) body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
|
|
282
282
|
if (requestData.createSession) body.createSession = requestData.createSession;
|
|
283
|
+
if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
|
|
283
284
|
if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;
|
|
284
285
|
if (requestData.counterpartyUrl) body.counterpartyUrl = requestData.counterpartyUrl;
|
|
285
286
|
if (requestData.runtimeChallengeOptions) body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;
|
|
@@ -453,6 +454,24 @@ async function recordDecision(config, sessionId, decision, reason) {
|
|
|
453
454
|
}).catch(() => {
|
|
454
455
|
});
|
|
455
456
|
}
|
|
457
|
+
async function reportUnregisteredAttempt(config, data) {
|
|
458
|
+
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
459
|
+
await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers: { "Content-Type": "application/json" },
|
|
462
|
+
body: JSON.stringify(data)
|
|
463
|
+
}).catch(() => {
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
async function reportCounterpartyPreCheckFailure(config, data) {
|
|
467
|
+
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
468
|
+
await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
|
|
469
|
+
method: "POST",
|
|
470
|
+
headers: { "Content-Type": "application/json" },
|
|
471
|
+
body: JSON.stringify(data)
|
|
472
|
+
}).catch(() => {
|
|
473
|
+
});
|
|
474
|
+
}
|
|
456
475
|
async function quickVerify(config, credentials) {
|
|
457
476
|
const result = await verify(config, {
|
|
458
477
|
credentials,
|
|
@@ -534,6 +553,54 @@ function extractHttpCredentials(headers) {
|
|
|
534
553
|
return credentials;
|
|
535
554
|
}
|
|
536
555
|
|
|
556
|
+
// src/pdlss-pre-check.ts
|
|
557
|
+
function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
|
|
558
|
+
const failures = [];
|
|
559
|
+
if (routeConfig.allowedPurposes && routeConfig.allowedPurposes.length > 0 && purpose) {
|
|
560
|
+
if (!routeConfig.allowedPurposes.includes(purpose)) {
|
|
561
|
+
failures.push({
|
|
562
|
+
field: "purpose",
|
|
563
|
+
requested: purpose,
|
|
564
|
+
limit: routeConfig.allowedPurposes,
|
|
565
|
+
message: `Purpose "${purpose}" is not in the allowed list: [${routeConfig.allowedPurposes.join(", ")}]`
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (routeConfig.requiredPurposes && routeConfig.requiredPurposes.length > 0 && purpose) {
|
|
570
|
+
if (!routeConfig.requiredPurposes.includes(purpose)) {
|
|
571
|
+
failures.push({
|
|
572
|
+
field: "purpose",
|
|
573
|
+
requested: purpose,
|
|
574
|
+
limit: routeConfig.requiredPurposes,
|
|
575
|
+
message: `Purpose "${purpose}" is not in the required list: [${routeConfig.requiredPurposes.join(", ")}]`
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (routeConfig.maxDuration && astraCreds?.pdlss?.duration?.maxSessionDuration) {
|
|
580
|
+
const requested = astraCreds.pdlss.duration.maxSessionDuration;
|
|
581
|
+
if (requested > routeConfig.maxDuration) {
|
|
582
|
+
failures.push({
|
|
583
|
+
field: "duration",
|
|
584
|
+
requested,
|
|
585
|
+
limit: routeConfig.maxDuration,
|
|
586
|
+
message: `Requested duration ${requested}s exceeds maximum ${routeConfig.maxDuration}s`
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (routeConfig.allowedJurisdictions && routeConfig.allowedJurisdictions.length > 0 && astraCreds?.pdlss?.scope?.jurisdiction) {
|
|
591
|
+
const requested = astraCreds.pdlss.scope.jurisdiction;
|
|
592
|
+
if (!routeConfig.allowedJurisdictions.includes(requested)) {
|
|
593
|
+
failures.push({
|
|
594
|
+
field: "jurisdiction",
|
|
595
|
+
requested,
|
|
596
|
+
limit: routeConfig.allowedJurisdictions,
|
|
597
|
+
message: `Jurisdiction "${requested}" is not in the allowed list: [${routeConfig.allowedJurisdictions.join(", ")}]`
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return failures;
|
|
602
|
+
}
|
|
603
|
+
|
|
537
604
|
// src/adapters/express.ts
|
|
538
605
|
function defaultExtractCredentials(req) {
|
|
539
606
|
return extractCredentials(
|
|
@@ -545,6 +612,12 @@ function extractAstraSyncCredentials(req) {
|
|
|
545
612
|
return extractHttpCredentials(req.headers);
|
|
546
613
|
}
|
|
547
614
|
function defaultExtractPurpose(req) {
|
|
615
|
+
const astraPurpose = req.headers["x-astra-purpose"];
|
|
616
|
+
if (astraPurpose) {
|
|
617
|
+
const value = Array.isArray(astraPurpose) ? astraPurpose[0] : astraPurpose;
|
|
618
|
+
const category = value.split(":")[0];
|
|
619
|
+
return category;
|
|
620
|
+
}
|
|
548
621
|
const purposeHeader = req.headers["x-purpose"] || req.headers["X-Purpose"];
|
|
549
622
|
if (purposeHeader) {
|
|
550
623
|
return Array.isArray(purposeHeader) ? purposeHeader[0] : purposeHeader;
|
|
@@ -554,14 +627,14 @@ function defaultExtractPurpose(req) {
|
|
|
554
627
|
}
|
|
555
628
|
switch (req.method) {
|
|
556
629
|
case "GET":
|
|
557
|
-
return "
|
|
630
|
+
return "read_data";
|
|
558
631
|
case "POST":
|
|
559
|
-
return "
|
|
632
|
+
return "write_data";
|
|
560
633
|
case "PUT":
|
|
561
634
|
case "PATCH":
|
|
562
|
-
return "
|
|
635
|
+
return "write_data";
|
|
563
636
|
case "DELETE":
|
|
564
|
-
return "
|
|
637
|
+
return "delete_data";
|
|
565
638
|
default:
|
|
566
639
|
return "general";
|
|
567
640
|
}
|
|
@@ -598,6 +671,7 @@ function createMiddleware(options) {
|
|
|
598
671
|
skipPaths = [],
|
|
599
672
|
onDenied = defaultOnDenied,
|
|
600
673
|
recordDecisions,
|
|
674
|
+
enableRuntimeChallenge = true,
|
|
601
675
|
...config
|
|
602
676
|
} = options;
|
|
603
677
|
return async (req, res, next) => {
|
|
@@ -615,6 +689,16 @@ function createMiddleware(options) {
|
|
|
615
689
|
}
|
|
616
690
|
const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
|
|
617
691
|
if (!hasCredentials(credentials) && routeConfig.minAccessLevel !== "guidance") {
|
|
692
|
+
const counterpartyUrl2 = config.counterpartyUrl || `${req.protocol}://${req.get("host")}`;
|
|
693
|
+
reportUnregisteredAttempt(config, {
|
|
694
|
+
counterpartyUrl: counterpartyUrl2,
|
|
695
|
+
counterpartyType: config.counterpartyType || "api",
|
|
696
|
+
sourceIp: req.ip,
|
|
697
|
+
userAgent: req.headers["user-agent"],
|
|
698
|
+
requestPath: req.path,
|
|
699
|
+
requestMethod: req.method
|
|
700
|
+
}).catch(() => {
|
|
701
|
+
});
|
|
618
702
|
const result2 = {
|
|
619
703
|
verified: false,
|
|
620
704
|
accessLevel: "none",
|
|
@@ -631,7 +715,34 @@ function createMiddleware(options) {
|
|
|
631
715
|
return;
|
|
632
716
|
}
|
|
633
717
|
const purpose = customExtractPurpose ? customExtractPurpose(req) : defaultExtractPurpose(req);
|
|
718
|
+
const astraCreds = extractAstraSyncCredentials(req);
|
|
634
719
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}`;
|
|
720
|
+
const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
|
|
721
|
+
if (preCheckFailures.length > 0) {
|
|
722
|
+
const result2 = {
|
|
723
|
+
verified: false,
|
|
724
|
+
accessLevel: "none",
|
|
725
|
+
denialReasons: preCheckFailures.map((f) => f.message),
|
|
726
|
+
guidance: {
|
|
727
|
+
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
728
|
+
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
|
|
729
|
+
documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
|
|
730
|
+
},
|
|
731
|
+
verifiedAt: /* @__PURE__ */ new Date()
|
|
732
|
+
};
|
|
733
|
+
req.agentVerification = result2;
|
|
734
|
+
reportCounterpartyPreCheckFailure(config, {
|
|
735
|
+
agentId: astraCreds?.agentId || credentials.astraId || "unknown",
|
|
736
|
+
counterpartyUrl,
|
|
737
|
+
counterpartyType: config.counterpartyType || "api",
|
|
738
|
+
failures: preCheckFailures,
|
|
739
|
+
requestPath: req.path,
|
|
740
|
+
requestMethod: req.method
|
|
741
|
+
}).catch(() => {
|
|
742
|
+
});
|
|
743
|
+
onDenied(result2, req, res);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
635
746
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
636
747
|
const result = await verify(config, {
|
|
637
748
|
credentials,
|
|
@@ -642,7 +753,9 @@ function createMiddleware(options) {
|
|
|
642
753
|
userAgent: req.headers["user-agent"],
|
|
643
754
|
createSession: shouldRecordDecisions,
|
|
644
755
|
counterpartyUrl,
|
|
645
|
-
counterpartyType: config.counterpartyType || "api"
|
|
756
|
+
counterpartyType: config.counterpartyType || "api",
|
|
757
|
+
enableRuntimeChallenge,
|
|
758
|
+
durationRequired: astraCreds?.pdlss?.duration?.maxSessionDuration
|
|
646
759
|
});
|
|
647
760
|
req.agentVerification = result;
|
|
648
761
|
const sessionId = result.sessionId;
|
|
@@ -737,17 +850,32 @@ function findRouteConfig2(routes, path, method) {
|
|
|
737
850
|
return methodMatches && pathMatches;
|
|
738
851
|
});
|
|
739
852
|
}
|
|
740
|
-
function
|
|
741
|
-
|
|
853
|
+
function extractAstraSyncCredentialsFromNextRequest(request) {
|
|
854
|
+
const headers = {};
|
|
855
|
+
request.headers.forEach((value, key) => {
|
|
856
|
+
headers[key] = value;
|
|
857
|
+
});
|
|
858
|
+
return extractHttpCredentials(headers);
|
|
859
|
+
}
|
|
860
|
+
function extractPurpose(request) {
|
|
861
|
+
const astraPurpose = request.headers.get("x-astra-purpose");
|
|
862
|
+
if (astraPurpose) {
|
|
863
|
+
return astraPurpose.split(":")[0];
|
|
864
|
+
}
|
|
865
|
+
const purposeHeader = request.headers.get("x-purpose");
|
|
866
|
+
if (purposeHeader) {
|
|
867
|
+
return purposeHeader;
|
|
868
|
+
}
|
|
869
|
+
switch (request.method.toUpperCase()) {
|
|
742
870
|
case "GET":
|
|
743
|
-
return "
|
|
871
|
+
return "read_data";
|
|
744
872
|
case "POST":
|
|
745
|
-
return "
|
|
873
|
+
return "write_data";
|
|
746
874
|
case "PUT":
|
|
747
875
|
case "PATCH":
|
|
748
|
-
return "
|
|
876
|
+
return "write_data";
|
|
749
877
|
case "DELETE":
|
|
750
|
-
return "
|
|
878
|
+
return "delete_data";
|
|
751
879
|
default:
|
|
752
880
|
return "general";
|
|
753
881
|
}
|
|
@@ -899,7 +1027,7 @@ function generateCommerceShieldHtml(result, options) {
|
|
|
899
1027
|
`.trim();
|
|
900
1028
|
}
|
|
901
1029
|
function createMiddleware2(options) {
|
|
902
|
-
const { routes = [], skipPaths = [], showCommerceShield = true, ...config } = options;
|
|
1030
|
+
const { routes = [], skipPaths = [], showCommerceShield = true, enableRuntimeChallenge = true, ...config } = options;
|
|
903
1031
|
return async function middleware(request) {
|
|
904
1032
|
const { NextResponse } = await import("next/server");
|
|
905
1033
|
const pathname = request.nextUrl.pathname;
|
|
@@ -916,6 +1044,16 @@ function createMiddleware2(options) {
|
|
|
916
1044
|
}
|
|
917
1045
|
const credentials = extractCredentialsFromNextRequest(request);
|
|
918
1046
|
if (!hasCredentials(credentials) && routeConfig.minAccessLevel !== "guidance") {
|
|
1047
|
+
const counterpartyUrl2 = config.counterpartyUrl || request.nextUrl.origin;
|
|
1048
|
+
reportUnregisteredAttempt(config, {
|
|
1049
|
+
counterpartyUrl: counterpartyUrl2,
|
|
1050
|
+
counterpartyType: config.counterpartyType || "website",
|
|
1051
|
+
sourceIp: request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || void 0,
|
|
1052
|
+
userAgent: request.headers.get("user-agent") || void 0,
|
|
1053
|
+
requestPath: pathname,
|
|
1054
|
+
requestMethod: request.method
|
|
1055
|
+
}).catch(() => {
|
|
1056
|
+
});
|
|
919
1057
|
const result2 = {
|
|
920
1058
|
verified: false,
|
|
921
1059
|
accessLevel: "none",
|
|
@@ -953,7 +1091,54 @@ function createMiddleware2(options) {
|
|
|
953
1091
|
return NextResponse.redirect(new URL(registerUrl, request.url));
|
|
954
1092
|
}
|
|
955
1093
|
const counterpartyUrl = config.counterpartyUrl || request.nextUrl.origin;
|
|
956
|
-
const purpose =
|
|
1094
|
+
const purpose = extractPurpose(request);
|
|
1095
|
+
const astraCreds = extractAstraSyncCredentialsFromNextRequest(request);
|
|
1096
|
+
const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
|
|
1097
|
+
if (preCheckFailures.length > 0) {
|
|
1098
|
+
const preCheckResult = {
|
|
1099
|
+
verified: false,
|
|
1100
|
+
accessLevel: "none",
|
|
1101
|
+
denialReasons: preCheckFailures.map((f) => f.message),
|
|
1102
|
+
guidance: {
|
|
1103
|
+
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
1104
|
+
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
|
|
1105
|
+
documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
|
|
1106
|
+
},
|
|
1107
|
+
verifiedAt: /* @__PURE__ */ new Date()
|
|
1108
|
+
};
|
|
1109
|
+
reportCounterpartyPreCheckFailure(config, {
|
|
1110
|
+
agentId: astraCreds?.agentId || credentials.astraId || "unknown",
|
|
1111
|
+
counterpartyUrl,
|
|
1112
|
+
counterpartyType: config.counterpartyType || "website",
|
|
1113
|
+
failures: preCheckFailures,
|
|
1114
|
+
requestPath: pathname,
|
|
1115
|
+
requestMethod: request.method
|
|
1116
|
+
}).catch(() => {
|
|
1117
|
+
});
|
|
1118
|
+
if (pathname.startsWith("/api/")) {
|
|
1119
|
+
return NextResponse.json(
|
|
1120
|
+
{
|
|
1121
|
+
success: false,
|
|
1122
|
+
error: {
|
|
1123
|
+
code: "PDLSS_PRE_CHECK_FAILED",
|
|
1124
|
+
message: preCheckResult.denialReasons?.[0] || "PDLSS pre-check failed",
|
|
1125
|
+
guidance: preCheckResult.guidance
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
{ status: 403 }
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
if (showCommerceShield) {
|
|
1132
|
+
return new NextResponse(generateCommerceShieldHtml(preCheckResult, options), {
|
|
1133
|
+
status: 200,
|
|
1134
|
+
headers: {
|
|
1135
|
+
"Content-Type": "text/html",
|
|
1136
|
+
"X-AstraSync-Verification": "commerce-shield"
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
return NextResponse.redirect(new URL("/unauthorized", request.url));
|
|
1141
|
+
}
|
|
957
1142
|
const result = await verify(config, {
|
|
958
1143
|
credentials,
|
|
959
1144
|
purpose,
|
|
@@ -962,7 +1147,9 @@ function createMiddleware2(options) {
|
|
|
962
1147
|
clientIp: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || void 0,
|
|
963
1148
|
userAgent: request.headers.get("user-agent") || void 0,
|
|
964
1149
|
counterpartyUrl,
|
|
965
|
-
counterpartyType: config.counterpartyType || "website"
|
|
1150
|
+
counterpartyType: config.counterpartyType || "website",
|
|
1151
|
+
enableRuntimeChallenge,
|
|
1152
|
+
durationRequired: astraCreds?.pdlss?.duration?.maxSessionDuration
|
|
966
1153
|
});
|
|
967
1154
|
if (!hasMinimumAccess(result.accessLevel, routeConfig.minAccessLevel)) {
|
|
968
1155
|
if (pathname.startsWith("/api/")) {
|
|
@@ -1141,15 +1328,64 @@ async function verifyOnce(options) {
|
|
|
1141
1328
|
// src/transport/index.ts
|
|
1142
1329
|
var transport_exports = {};
|
|
1143
1330
|
__export(transport_exports, {
|
|
1331
|
+
STRIPE_WEBHOOK_INFORMATIONAL_EVENTS: () => STRIPE_WEBHOOK_INFORMATIONAL_EVENTS,
|
|
1144
1332
|
applyCredentials: () => applyCredentials,
|
|
1333
|
+
bindIdentity: () => bindIdentity,
|
|
1334
|
+
claim: () => claim,
|
|
1335
|
+
clearTransportExtractors: () => clearTransportExtractors,
|
|
1336
|
+
createMastercardRegistry: () => createMastercardRegistry,
|
|
1337
|
+
createVisaRegistry: () => createVisaRegistry,
|
|
1338
|
+
createWebBotAuthRegistry: () => createWebBotAuthRegistry,
|
|
1145
1339
|
detectProtocol: () => detectProtocol,
|
|
1340
|
+
evaluatePaymentMethodAllowlist: () => evaluatePaymentMethodAllowlist,
|
|
1341
|
+
evaluateSpendingLimit: () => evaluateSpendingLimit,
|
|
1342
|
+
evaluateVIConstraints: () => evaluateVIConstraints,
|
|
1146
1343
|
extractA2ACredentials: () => extractA2ACredentials,
|
|
1344
|
+
extractACPContext: () => extractACPContext,
|
|
1345
|
+
extractACPTransactionValue: () => extractACPTransactionValue,
|
|
1346
|
+
extractAP2Mandate: () => extractAP2Mandate,
|
|
1347
|
+
extractAP2Mandates: () => extractAP2Mandates,
|
|
1348
|
+
extractAP2TransactionValue: () => extractAP2TransactionValue,
|
|
1147
1349
|
extractCredentialsFromProtocol: () => extractCredentialsFromProtocol,
|
|
1148
1350
|
extractHttpCredentials: () => extractHttpCredentials,
|
|
1351
|
+
extractMPPContext: () => extractMPPContext,
|
|
1352
|
+
extractMPPFromRequest: () => extractMPPFromRequest,
|
|
1353
|
+
extractMPPFromResponse: () => extractMPPFromResponse,
|
|
1354
|
+
extractMPPTransactionValue: () => extractMPPTransactionValue,
|
|
1149
1355
|
extractMcpCredentials: () => extractMcpCredentials,
|
|
1356
|
+
extractUCPContext: () => extractUCPContext,
|
|
1357
|
+
extractUCPTransactionValue: () => extractUCPTransactionValue,
|
|
1358
|
+
extractVIClaims: () => extractVIClaims,
|
|
1359
|
+
extractVITransactionValue: () => extractVITransactionValue,
|
|
1360
|
+
extractX402Context: () => extractX402Context,
|
|
1361
|
+
extractX402FromRequest: () => extractX402FromRequest,
|
|
1362
|
+
extractX402FromResponse: () => extractX402FromResponse,
|
|
1363
|
+
extractX402TransactionValue: () => extractX402TransactionValue,
|
|
1364
|
+
fetchUCPManifest: () => fetchUCPManifest,
|
|
1365
|
+
getTransportExtractor: () => getTransportExtractor,
|
|
1366
|
+
getTransportExtractors: () => getTransportExtractors,
|
|
1367
|
+
isStripeWebhookInformational: () => isStripeWebhookInformational,
|
|
1368
|
+
mapACPRequestToPurpose: () => mapACPRequestToPurpose,
|
|
1369
|
+
mapAP2MandateToPurpose: () => mapAP2MandateToPurpose,
|
|
1370
|
+
mapMPPRequestToPurpose: () => mapMPPRequestToPurpose,
|
|
1371
|
+
mapRFC9421TagToPurpose: () => mapRFC9421TagToPurpose,
|
|
1372
|
+
mapUCPRequestToPurpose: () => mapUCPRequestToPurpose,
|
|
1373
|
+
mapVIMandateToPurpose: () => mapVIMandateToPurpose,
|
|
1374
|
+
mapX402RequestToPurpose: () => mapX402RequestToPurpose,
|
|
1375
|
+
parseRFC9421: () => parseRFC9421,
|
|
1376
|
+
registerTransportExtractor: () => registerTransportExtractor,
|
|
1377
|
+
runCommercePipeline: () => runCommercePipeline,
|
|
1378
|
+
runMatchingExtractors: () => runMatchingExtractors,
|
|
1150
1379
|
setA2AMetadata: () => setA2AMetadata,
|
|
1151
1380
|
setHttpHeaders: () => setHttpHeaders,
|
|
1152
|
-
setMcpMeta: () => setMcpMeta
|
|
1381
|
+
setMcpMeta: () => setMcpMeta,
|
|
1382
|
+
validateUCPManifest: () => validateUCPManifest,
|
|
1383
|
+
verifyACPSignature: () => verifyACPSignature,
|
|
1384
|
+
verifyAP2Chain: () => verifyAP2Chain,
|
|
1385
|
+
verifyMPP: () => verifyMPP,
|
|
1386
|
+
verifyRFC9421: () => verifyRFC9421,
|
|
1387
|
+
verifyStripeWebhook: () => verifyStripeWebhook,
|
|
1388
|
+
verifyVIChain: () => verifyVIChain
|
|
1153
1389
|
});
|
|
1154
1390
|
|
|
1155
1391
|
// src/transport/a2a.ts
|
|
@@ -1222,6 +1458,2280 @@ function extractMcpCredentials(params) {
|
|
|
1222
1458
|
return credentials;
|
|
1223
1459
|
}
|
|
1224
1460
|
|
|
1461
|
+
// src/transport/purpose-mapping.ts
|
|
1462
|
+
var UCP_ROUTES = [
|
|
1463
|
+
{ method: "POST", pattern: /^\/checkout[-_]sessions\/?$/, purpose: "commerce.checkout.create" },
|
|
1464
|
+
{
|
|
1465
|
+
method: "PUT",
|
|
1466
|
+
pattern: /^\/checkout[-_]sessions\/[^/]+\/?$/,
|
|
1467
|
+
purpose: "commerce.checkout.update"
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
method: "POST",
|
|
1471
|
+
pattern: /^\/checkout[-_]sessions\/[^/]+\/complete\/?$/,
|
|
1472
|
+
purpose: "commerce.payment.execute"
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
method: "POST",
|
|
1476
|
+
pattern: /^\/checkout[-_]sessions\/[^/]+\/cancel\/?$/,
|
|
1477
|
+
purpose: "commerce.checkout.cancel"
|
|
1478
|
+
}
|
|
1479
|
+
];
|
|
1480
|
+
var ACP_ROUTES = [
|
|
1481
|
+
{ method: "POST", pattern: /^\/checkout_sessions\/?$/, purpose: "commerce.checkout.create" },
|
|
1482
|
+
{
|
|
1483
|
+
method: "POST",
|
|
1484
|
+
pattern: /^\/checkout_sessions\/[^/]+\/?$/,
|
|
1485
|
+
purpose: "commerce.checkout.update"
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
method: "POST",
|
|
1489
|
+
pattern: /^\/checkout_sessions\/[^/]+\/complete\/?$/,
|
|
1490
|
+
purpose: "commerce.payment.execute"
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
method: "POST",
|
|
1494
|
+
pattern: /^\/checkout_sessions\/[^/]+\/cancel\/?$/,
|
|
1495
|
+
purpose: "commerce.checkout.cancel"
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
method: "POST",
|
|
1499
|
+
pattern: /^\/agentic_commerce\/delegate_payment\/?$/,
|
|
1500
|
+
purpose: "commerce.delegation.payment"
|
|
1501
|
+
}
|
|
1502
|
+
];
|
|
1503
|
+
function mapUCPRequestToPurpose(method, path) {
|
|
1504
|
+
const normalizedMethod = method.toUpperCase();
|
|
1505
|
+
const normalizedPath = stripQuery(path);
|
|
1506
|
+
for (const route of UCP_ROUTES) {
|
|
1507
|
+
if (route.method === normalizedMethod && route.pattern.test(normalizedPath)) {
|
|
1508
|
+
return route.purpose;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1513
|
+
function mapACPRequestToPurpose(method, path) {
|
|
1514
|
+
const normalizedMethod = method.toUpperCase();
|
|
1515
|
+
const normalizedPath = stripQuery(path);
|
|
1516
|
+
for (const route of ACP_ROUTES) {
|
|
1517
|
+
if (route.method === normalizedMethod && route.pattern.test(normalizedPath)) {
|
|
1518
|
+
return route.purpose;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
function mapAP2MandateToPurpose(mandateType) {
|
|
1524
|
+
switch (mandateType) {
|
|
1525
|
+
case "intent_mandate":
|
|
1526
|
+
return "commerce.delegation.intent";
|
|
1527
|
+
case "cart_mandate":
|
|
1528
|
+
return "commerce.checkout.confirm";
|
|
1529
|
+
case "payment_mandate":
|
|
1530
|
+
return "commerce.payment.execute";
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
function mapVIMandateToPurpose(mandateType) {
|
|
1534
|
+
switch (mandateType) {
|
|
1535
|
+
case "checkout":
|
|
1536
|
+
return "commerce.checkout.confirm";
|
|
1537
|
+
case "payment":
|
|
1538
|
+
return "commerce.payment.execute";
|
|
1539
|
+
case "checkout.open":
|
|
1540
|
+
return "commerce.delegation.checkout";
|
|
1541
|
+
case "payment.open":
|
|
1542
|
+
return "commerce.delegation.payment";
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
function mapRFC9421TagToPurpose(tag) {
|
|
1546
|
+
if (tag === "purchase") return "commerce.payment.execute";
|
|
1547
|
+
return "commerce.browsing";
|
|
1548
|
+
}
|
|
1549
|
+
function mapMPPRequestToPurpose(intent, amount) {
|
|
1550
|
+
if (typeof amount === "number" && amount === 0) return "commerce.identity_probe";
|
|
1551
|
+
if (intent === "session") return "commerce.payment.stream";
|
|
1552
|
+
return "commerce.payment.execute";
|
|
1553
|
+
}
|
|
1554
|
+
function mapX402RequestToPurpose(amount) {
|
|
1555
|
+
if (typeof amount === "number" && amount === 0) return "commerce.identity_probe";
|
|
1556
|
+
return "commerce.payment.execute";
|
|
1557
|
+
}
|
|
1558
|
+
function stripQuery(path) {
|
|
1559
|
+
const q = path.indexOf("?");
|
|
1560
|
+
return q === -1 ? path : path.slice(0, q);
|
|
1561
|
+
}
|
|
1562
|
+
var STRIPE_WEBHOOK_INFORMATIONAL_EVENTS = [
|
|
1563
|
+
"payment_intent.succeeded",
|
|
1564
|
+
"payment_intent.payment_failed",
|
|
1565
|
+
"charge.refunded",
|
|
1566
|
+
"checkout.session.completed",
|
|
1567
|
+
"customer.subscription.created"
|
|
1568
|
+
];
|
|
1569
|
+
function isStripeWebhookInformational(eventType) {
|
|
1570
|
+
return STRIPE_WEBHOOK_INFORMATIONAL_EVENTS.includes(eventType);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/transport/transaction-value.ts
|
|
1574
|
+
function extractUCPTransactionValue(input) {
|
|
1575
|
+
const totals = input.totals ?? [];
|
|
1576
|
+
const total = totals.find((t) => t.type === "total") ?? totals[0];
|
|
1577
|
+
if (!total || typeof total.amount !== "number" || !total.currency) return null;
|
|
1578
|
+
return {
|
|
1579
|
+
protocol: "ucp",
|
|
1580
|
+
amount: total.amount / 100,
|
|
1581
|
+
currency: total.currency,
|
|
1582
|
+
source: `totals[type=${total.type ?? "unknown"}].amount`
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
function extractACPTransactionValue(input) {
|
|
1586
|
+
const totals = input.totals ?? [];
|
|
1587
|
+
const total = totals.find((t) => t.type === "total") ?? totals[0];
|
|
1588
|
+
if (!total || typeof total.amount !== "number" || !total.currency) return null;
|
|
1589
|
+
return {
|
|
1590
|
+
protocol: "acp",
|
|
1591
|
+
amount: total.amount / 100,
|
|
1592
|
+
currency: total.currency,
|
|
1593
|
+
source: `totals[type=${total.type ?? "unknown"}].amount`
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
function extractVITransactionValue(claims) {
|
|
1597
|
+
const l3a = claims.l3aPaymentAmount;
|
|
1598
|
+
if (l3a && typeof l3a.amount === "number" && l3a.currency) {
|
|
1599
|
+
return {
|
|
1600
|
+
protocol: "vi",
|
|
1601
|
+
amount: l3a.amount,
|
|
1602
|
+
currency: l3a.currency,
|
|
1603
|
+
source: "L3a.payment.amount"
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
const bound = claims.constraints?.paymentAmount;
|
|
1607
|
+
if (bound && typeof bound.max === "number" && bound.currency) {
|
|
1608
|
+
return {
|
|
1609
|
+
protocol: "vi",
|
|
1610
|
+
amount: bound.max,
|
|
1611
|
+
currency: bound.currency,
|
|
1612
|
+
source: "L2.payment.constraints.amount.max"
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1617
|
+
function extractAP2TransactionValue(mandate) {
|
|
1618
|
+
const amt = mandate?.payment_details_total?.amount;
|
|
1619
|
+
if (!amt || !amt.currency) return null;
|
|
1620
|
+
const n = typeof amt.value === "string" ? Number(amt.value) : amt.value;
|
|
1621
|
+
if (typeof n !== "number" || !Number.isFinite(n)) return null;
|
|
1622
|
+
return {
|
|
1623
|
+
protocol: "ap2",
|
|
1624
|
+
amount: n,
|
|
1625
|
+
currency: amt.currency,
|
|
1626
|
+
source: "payment_mandate.payment_details_total.amount"
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
function extractMPPTransactionValue(challenge) {
|
|
1630
|
+
const req = challenge.request;
|
|
1631
|
+
if (!req || typeof req.amount !== "number" || !req.currency) return null;
|
|
1632
|
+
return {
|
|
1633
|
+
protocol: "mpp",
|
|
1634
|
+
amount: req.amount,
|
|
1635
|
+
currency: req.currency,
|
|
1636
|
+
source: `challenge.request.amount (method=${challenge.method ?? "unknown"})`
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function extractX402TransactionValue(req) {
|
|
1640
|
+
const amount = req.maxAmountRequired ?? req.amount;
|
|
1641
|
+
const currency = req.currency ?? req.asset;
|
|
1642
|
+
if (typeof amount !== "number" || !currency) return null;
|
|
1643
|
+
return {
|
|
1644
|
+
protocol: "x402",
|
|
1645
|
+
amount,
|
|
1646
|
+
currency,
|
|
1647
|
+
source: req.maxAmountRequired !== void 0 ? "maxAmountRequired" : "amount"
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
// src/transport/rfc9421.ts
|
|
1652
|
+
var import_structured_headers = require("structured-headers");
|
|
1653
|
+
function parseRFC9421(headers) {
|
|
1654
|
+
const sigInput = readHeader(headers, "signature-input");
|
|
1655
|
+
const sig = readHeader(headers, "signature");
|
|
1656
|
+
if (!sigInput || !sig) return null;
|
|
1657
|
+
let inputDict;
|
|
1658
|
+
let sigDict;
|
|
1659
|
+
try {
|
|
1660
|
+
inputDict = (0, import_structured_headers.parseDictionary)(sigInput);
|
|
1661
|
+
sigDict = (0, import_structured_headers.parseDictionary)(sig);
|
|
1662
|
+
} catch {
|
|
1663
|
+
return null;
|
|
1664
|
+
}
|
|
1665
|
+
const signatures = [];
|
|
1666
|
+
for (const [label, entry] of inputDict) {
|
|
1667
|
+
const innerList = Array.isArray(entry) ? entry[0] : entry.value;
|
|
1668
|
+
const params = Array.isArray(entry) ? entry[1] : entry.params;
|
|
1669
|
+
if (!Array.isArray(innerList) || !params) continue;
|
|
1670
|
+
const covered = [];
|
|
1671
|
+
for (const item of innerList) {
|
|
1672
|
+
const [bare] = Array.isArray(item) ? item : [item];
|
|
1673
|
+
if (typeof bare === "string") covered.push(bare);
|
|
1674
|
+
else if (bare && typeof bare === "object" && "toString" in bare) covered.push(String(bare));
|
|
1675
|
+
}
|
|
1676
|
+
const paramsMap = params;
|
|
1677
|
+
const kid = coerceString(paramsMap.get("keyid"));
|
|
1678
|
+
if (!kid) continue;
|
|
1679
|
+
const sigEntry = sigDict.get(label);
|
|
1680
|
+
if (!sigEntry) continue;
|
|
1681
|
+
const sigBare = Array.isArray(sigEntry) ? sigEntry[0] : sigEntry.value;
|
|
1682
|
+
const signatureBase64 = extractBase64(sigBare);
|
|
1683
|
+
if (!signatureBase64) continue;
|
|
1684
|
+
signatures.push({
|
|
1685
|
+
label,
|
|
1686
|
+
kid,
|
|
1687
|
+
alg: coerceString(paramsMap.get("alg")),
|
|
1688
|
+
covered,
|
|
1689
|
+
signatureBase64,
|
|
1690
|
+
created: coerceNumber(paramsMap.get("created")),
|
|
1691
|
+
expires: coerceNumber(paramsMap.get("expires")),
|
|
1692
|
+
nonce: coerceString(paramsMap.get("nonce")),
|
|
1693
|
+
tag: coerceString(paramsMap.get("tag"))
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
if (signatures.length === 0) return null;
|
|
1697
|
+
return { signatures };
|
|
1698
|
+
}
|
|
1699
|
+
function readHeader(headers, name) {
|
|
1700
|
+
for (const key of Object.keys(headers)) {
|
|
1701
|
+
if (key.toLowerCase() === name) {
|
|
1702
|
+
const raw = headers[key];
|
|
1703
|
+
if (typeof raw === "string") return raw;
|
|
1704
|
+
if (Array.isArray(raw)) return raw.join(", ");
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
function coerceString(value) {
|
|
1711
|
+
if (typeof value === "string") return value;
|
|
1712
|
+
if (value == null) return void 0;
|
|
1713
|
+
if (typeof value === "object" && "toString" in value) {
|
|
1714
|
+
const s = String(value);
|
|
1715
|
+
return s.length > 0 ? s : void 0;
|
|
1716
|
+
}
|
|
1717
|
+
return void 0;
|
|
1718
|
+
}
|
|
1719
|
+
function coerceNumber(value) {
|
|
1720
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1721
|
+
if (typeof value === "bigint") return Number(value);
|
|
1722
|
+
return void 0;
|
|
1723
|
+
}
|
|
1724
|
+
function extractBase64(value) {
|
|
1725
|
+
if (value instanceof Uint8Array) return bufferToBase64(value);
|
|
1726
|
+
if (value instanceof ArrayBuffer) return bufferToBase64(new Uint8Array(value));
|
|
1727
|
+
if (ArrayBuffer.isView(value)) {
|
|
1728
|
+
const v = value;
|
|
1729
|
+
return bufferToBase64(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));
|
|
1730
|
+
}
|
|
1731
|
+
if (typeof value === "string") {
|
|
1732
|
+
if (value.startsWith(":") && value.endsWith(":")) return value.slice(1, -1);
|
|
1733
|
+
return value;
|
|
1734
|
+
}
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
function bufferToBase64(bytes) {
|
|
1738
|
+
return Buffer.from(bytes).toString("base64");
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/transport/rfc9421-verify.ts
|
|
1742
|
+
var import_http_message_signatures = require("http-message-signatures");
|
|
1743
|
+
async function verifyRFC9421(request, options) {
|
|
1744
|
+
const { resolver } = options;
|
|
1745
|
+
const tolerance = options.clockSkewSec ?? 300;
|
|
1746
|
+
const nowSec = options.now ? options.now() : Math.floor(Date.now() / 1e3);
|
|
1747
|
+
let resolvedKid;
|
|
1748
|
+
let resolvedAlg;
|
|
1749
|
+
const keyLookup = async (parameters) => {
|
|
1750
|
+
const kid = typeof parameters.keyid === "string" ? parameters.keyid : void 0;
|
|
1751
|
+
if (!kid) return null;
|
|
1752
|
+
resolvedKid = kid;
|
|
1753
|
+
const alg = typeof parameters.alg === "string" ? parameters.alg : void 0;
|
|
1754
|
+
if (alg) resolvedAlg = alg;
|
|
1755
|
+
const origin = safeOrigin(request.url);
|
|
1756
|
+
const jwk = await resolver.resolve(kid, { origin, algorithm: alg });
|
|
1757
|
+
if (!jwk) return null;
|
|
1758
|
+
const created = toUnixSeconds(parameters.created);
|
|
1759
|
+
const expires = toUnixSeconds(parameters.expires);
|
|
1760
|
+
if (created !== void 0 && Math.abs(nowSec - created) > tolerance) return null;
|
|
1761
|
+
if (expires !== void 0 && nowSec > expires + tolerance) return null;
|
|
1762
|
+
return jwkToVerifyingKey(kid, jwk, alg);
|
|
1763
|
+
};
|
|
1764
|
+
try {
|
|
1765
|
+
const result = await import_http_message_signatures.httpbis.verifyMessage(
|
|
1766
|
+
{
|
|
1767
|
+
keyLookup
|
|
1768
|
+
},
|
|
1769
|
+
normalizeRequest(request)
|
|
1770
|
+
);
|
|
1771
|
+
if (result === true) {
|
|
1772
|
+
return {
|
|
1773
|
+
ok: true,
|
|
1774
|
+
kid: resolvedKid,
|
|
1775
|
+
registry: resolver.name,
|
|
1776
|
+
algorithm: resolvedAlg
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
return {
|
|
1780
|
+
ok: false,
|
|
1781
|
+
kid: resolvedKid,
|
|
1782
|
+
registry: resolver.name,
|
|
1783
|
+
algorithm: resolvedAlg,
|
|
1784
|
+
error: result === false ? "signature invalid" : "no signature found"
|
|
1785
|
+
};
|
|
1786
|
+
} catch (err) {
|
|
1787
|
+
return {
|
|
1788
|
+
ok: false,
|
|
1789
|
+
kid: resolvedKid,
|
|
1790
|
+
registry: resolver.name,
|
|
1791
|
+
algorithm: resolvedAlg,
|
|
1792
|
+
error: err instanceof Error ? err.message : "verification error"
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
function normalizeRequest(request) {
|
|
1797
|
+
return {
|
|
1798
|
+
method: request.method.toUpperCase(),
|
|
1799
|
+
url: request.url,
|
|
1800
|
+
headers: request.headers
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
function safeOrigin(url) {
|
|
1804
|
+
try {
|
|
1805
|
+
return new URL(url).origin;
|
|
1806
|
+
} catch {
|
|
1807
|
+
return void 0;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
async function jwkToVerifyingKey(id, jwk, alg) {
|
|
1811
|
+
const algorithm = alg ?? inferAlgFromJwk(jwk);
|
|
1812
|
+
const { subtle } = await getCrypto();
|
|
1813
|
+
const importAlg = webCryptoImportAlgFor(algorithm);
|
|
1814
|
+
const verifyAlg = webCryptoAlgFor(algorithm);
|
|
1815
|
+
if (!importAlg || !verifyAlg) {
|
|
1816
|
+
return {
|
|
1817
|
+
id,
|
|
1818
|
+
algs: alg ? [alg] : void 0,
|
|
1819
|
+
verify: async () => false
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
const key = await subtle.importKey("jwk", jwk, importAlg, false, ["verify"]);
|
|
1823
|
+
return {
|
|
1824
|
+
id,
|
|
1825
|
+
algs: alg ? [alg] : void 0,
|
|
1826
|
+
verify: async (data, signature) => {
|
|
1827
|
+
try {
|
|
1828
|
+
return await subtle.verify(verifyAlg, key, toArrayBuffer(signature), toArrayBuffer(data));
|
|
1829
|
+
} catch {
|
|
1830
|
+
return false;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function inferAlgFromJwk(jwk) {
|
|
1836
|
+
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
|
|
1837
|
+
if (jwk.kty === "EC" && jwk.crv === "P-256") return "ecdsa-p256-sha256";
|
|
1838
|
+
if (jwk.kty === "EC" && jwk.crv === "P-384") return "ecdsa-p384-sha384";
|
|
1839
|
+
if (jwk.kty === "RSA") return "rsa-v1_5-sha256";
|
|
1840
|
+
return "ecdsa-p256-sha256";
|
|
1841
|
+
}
|
|
1842
|
+
function webCryptoAlgFor(rfc9421Alg) {
|
|
1843
|
+
switch (rfc9421Alg) {
|
|
1844
|
+
case "ed25519":
|
|
1845
|
+
return { name: "Ed25519" };
|
|
1846
|
+
case "ecdsa-p256-sha256":
|
|
1847
|
+
return { name: "ECDSA", hash: "SHA-256" };
|
|
1848
|
+
case "ecdsa-p384-sha384":
|
|
1849
|
+
return { name: "ECDSA", hash: "SHA-384" };
|
|
1850
|
+
case "rsa-v1_5-sha256":
|
|
1851
|
+
return { name: "RSASSA-PKCS1-v1_5" };
|
|
1852
|
+
case "rsa-pss-sha512":
|
|
1853
|
+
return { name: "RSA-PSS", saltLength: 64 };
|
|
1854
|
+
default:
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
function webCryptoImportAlgFor(rfc9421Alg) {
|
|
1859
|
+
switch (rfc9421Alg) {
|
|
1860
|
+
case "ed25519":
|
|
1861
|
+
return { name: "Ed25519" };
|
|
1862
|
+
case "ecdsa-p256-sha256":
|
|
1863
|
+
return { name: "ECDSA", namedCurve: "P-256" };
|
|
1864
|
+
case "ecdsa-p384-sha384":
|
|
1865
|
+
return { name: "ECDSA", namedCurve: "P-384" };
|
|
1866
|
+
case "rsa-v1_5-sha256":
|
|
1867
|
+
return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
|
|
1868
|
+
case "rsa-pss-sha512":
|
|
1869
|
+
return { name: "RSA-PSS", hash: "SHA-512" };
|
|
1870
|
+
default:
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
function toArrayBuffer(buf) {
|
|
1875
|
+
const out = new ArrayBuffer(buf.byteLength);
|
|
1876
|
+
new Uint8Array(out).set(buf);
|
|
1877
|
+
return out;
|
|
1878
|
+
}
|
|
1879
|
+
function toUnixSeconds(v) {
|
|
1880
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1881
|
+
if (v instanceof Date) return Math.floor(v.getTime() / 1e3);
|
|
1882
|
+
if (typeof v === "string") {
|
|
1883
|
+
const parsed = Date.parse(v);
|
|
1884
|
+
if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
|
|
1885
|
+
}
|
|
1886
|
+
return void 0;
|
|
1887
|
+
}
|
|
1888
|
+
async function getCrypto() {
|
|
1889
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
1890
|
+
return { subtle: globalThis.crypto.subtle };
|
|
1891
|
+
}
|
|
1892
|
+
const nodeCrypto = await import("crypto");
|
|
1893
|
+
return { subtle: nodeCrypto.webcrypto.subtle };
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// src/transport/ucp.ts
|
|
1897
|
+
function extractUCPContext(request) {
|
|
1898
|
+
const { method, url } = request;
|
|
1899
|
+
if (!method || !url) return null;
|
|
1900
|
+
const parsedUrl = safeParseUrl(url);
|
|
1901
|
+
const path = parsedUrl?.pathname ?? url.split("?")[0];
|
|
1902
|
+
const purpose = mapUCPRequestToPurpose(method, path);
|
|
1903
|
+
const endpoint = `${method.toUpperCase()} ${path}`;
|
|
1904
|
+
const sessionId = extractSessionId(path);
|
|
1905
|
+
const body = request.body ?? {};
|
|
1906
|
+
const totals = Array.isArray(body.totals) ? body.totals : void 0;
|
|
1907
|
+
const paymentMethod = coerceString2(body.payment_method ?? body.paymentMethod);
|
|
1908
|
+
const manifestUrl = coerceString2(body.manifest_url ?? body.manifestUrl);
|
|
1909
|
+
const merchantDomain = extractMerchantDomain(body, parsedUrl);
|
|
1910
|
+
return {
|
|
1911
|
+
sessionId,
|
|
1912
|
+
endpoint,
|
|
1913
|
+
purpose,
|
|
1914
|
+
merchantDomain,
|
|
1915
|
+
totals,
|
|
1916
|
+
paymentMethod,
|
|
1917
|
+
manifestUrl
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
async function fetchUCPManifest(manifestUrl, options = {}) {
|
|
1921
|
+
const timeoutMs = options.timeoutMs ?? 3e3;
|
|
1922
|
+
const controller = new AbortController();
|
|
1923
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1924
|
+
try {
|
|
1925
|
+
const res = await fetch(manifestUrl, { signal: controller.signal });
|
|
1926
|
+
if (!res.ok) return null;
|
|
1927
|
+
return await res.json();
|
|
1928
|
+
} catch {
|
|
1929
|
+
return null;
|
|
1930
|
+
} finally {
|
|
1931
|
+
clearTimeout(timer);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
function validateUCPManifest(manifest, options = {}) {
|
|
1935
|
+
if (options.validator) return options.validator(manifest);
|
|
1936
|
+
const errors = [];
|
|
1937
|
+
if (!manifest || typeof manifest !== "object") {
|
|
1938
|
+
return { ok: false, errors: ["manifest is not an object"] };
|
|
1939
|
+
}
|
|
1940
|
+
const m = manifest;
|
|
1941
|
+
if (typeof m.version !== "string") errors.push("version is required and must be a string");
|
|
1942
|
+
if (!Array.isArray(m.capabilities)) errors.push("capabilities must be an array");
|
|
1943
|
+
if (!m.endpoints || typeof m.endpoints !== "object") errors.push("endpoints must be an object");
|
|
1944
|
+
return { ok: errors.length === 0, errors };
|
|
1945
|
+
}
|
|
1946
|
+
function safeParseUrl(url) {
|
|
1947
|
+
try {
|
|
1948
|
+
return new URL(url, "http://placeholder.invalid");
|
|
1949
|
+
} catch {
|
|
1950
|
+
return null;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
function extractSessionId(path) {
|
|
1954
|
+
const match = path.match(/\/checkout[-_]sessions\/([^/?#]+)/);
|
|
1955
|
+
return match?.[1];
|
|
1956
|
+
}
|
|
1957
|
+
function extractMerchantDomain(body, parsedUrl) {
|
|
1958
|
+
const explicit = coerceString2(body.merchant_domain ?? body.merchantDomain);
|
|
1959
|
+
if (explicit) return explicit;
|
|
1960
|
+
if (parsedUrl && parsedUrl.hostname !== "placeholder.invalid") return parsedUrl.hostname;
|
|
1961
|
+
return void 0;
|
|
1962
|
+
}
|
|
1963
|
+
function coerceString2(v) {
|
|
1964
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// src/transport/acp.ts
|
|
1968
|
+
function extractACPContext(request) {
|
|
1969
|
+
const { method, url } = request;
|
|
1970
|
+
if (!method || !url) return null;
|
|
1971
|
+
const path = stripQuery2(url.startsWith("http") ? new URL(url).pathname : url);
|
|
1972
|
+
const endpoint = classifyEndpoint(method, path);
|
|
1973
|
+
const purpose = mapACPRequestToPurpose(method, path);
|
|
1974
|
+
const sessionId = extractSessionId2(path);
|
|
1975
|
+
const headers = request.headers ?? {};
|
|
1976
|
+
const signatureHeader = readHeader2(headers, "signature");
|
|
1977
|
+
const timestampHeader = readHeader2(headers, "timestamp");
|
|
1978
|
+
const idempotencyKey = readHeader2(headers, "idempotency-key");
|
|
1979
|
+
const apiVersion = readHeader2(headers, "api-version");
|
|
1980
|
+
const bearer = extractBearer(readHeader2(headers, "authorization"));
|
|
1981
|
+
const body = request.body ?? {};
|
|
1982
|
+
const merchantId = coerceString3(body.merchant_id ?? body.merchantId);
|
|
1983
|
+
const totals = Array.isArray(body.totals) ? body.totals : void 0;
|
|
1984
|
+
const fulfillmentOption = extractFulfillmentOption(body);
|
|
1985
|
+
const paymentToken = extractPaymentToken(body);
|
|
1986
|
+
return {
|
|
1987
|
+
endpoint,
|
|
1988
|
+
purpose,
|
|
1989
|
+
sessionId,
|
|
1990
|
+
merchantId,
|
|
1991
|
+
apiVersion,
|
|
1992
|
+
bearer,
|
|
1993
|
+
signatureHeader,
|
|
1994
|
+
timestampHeader,
|
|
1995
|
+
idempotencyKey,
|
|
1996
|
+
paymentToken,
|
|
1997
|
+
totals,
|
|
1998
|
+
fulfillmentOption,
|
|
1999
|
+
rawBody: request.rawBody
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
function classifyEndpoint(method, path) {
|
|
2003
|
+
const m = method.toUpperCase();
|
|
2004
|
+
if (m !== "POST") return "unknown";
|
|
2005
|
+
if (/^\/agentic_commerce\/delegate_payment\/?$/.test(path)) return "delegate_payment";
|
|
2006
|
+
if (/^\/checkout_sessions\/?$/.test(path)) return "checkout_sessions.create";
|
|
2007
|
+
if (/^\/checkout_sessions\/[^/]+\/?$/.test(path)) return "checkout_sessions.update";
|
|
2008
|
+
if (/^\/checkout_sessions\/[^/]+\/complete\/?$/.test(path)) return "checkout_sessions.complete";
|
|
2009
|
+
if (/^\/checkout_sessions\/[^/]+\/cancel\/?$/.test(path)) return "checkout_sessions.cancel";
|
|
2010
|
+
return "unknown";
|
|
2011
|
+
}
|
|
2012
|
+
function extractSessionId2(path) {
|
|
2013
|
+
const match = path.match(/\/checkout_sessions\/([^/?#]+)/);
|
|
2014
|
+
return match?.[1];
|
|
2015
|
+
}
|
|
2016
|
+
function extractBearer(authHeader) {
|
|
2017
|
+
if (!authHeader) return void 0;
|
|
2018
|
+
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
2019
|
+
return match ? match[1].trim() : void 0;
|
|
2020
|
+
}
|
|
2021
|
+
function extractPaymentToken(body) {
|
|
2022
|
+
const paymentData = body.payment_data;
|
|
2023
|
+
if (!paymentData) return void 0;
|
|
2024
|
+
const raw = coerceString3(paymentData.token);
|
|
2025
|
+
const provider = coerceString3(paymentData.provider);
|
|
2026
|
+
if (!raw) return { raw: void 0, type: null, provider };
|
|
2027
|
+
const type = classifyPaymentToken(raw);
|
|
2028
|
+
return { raw, type, provider };
|
|
2029
|
+
}
|
|
2030
|
+
function classifyPaymentToken(token) {
|
|
2031
|
+
if (token.startsWith("spt_")) return "stripe-spt";
|
|
2032
|
+
if (token.startsWith("vt_")) return "acp-vt";
|
|
2033
|
+
return "other";
|
|
2034
|
+
}
|
|
2035
|
+
function extractFulfillmentOption(body) {
|
|
2036
|
+
const direct = coerceString3(body.fulfillment_option ?? body.fulfillmentOption);
|
|
2037
|
+
if (direct) return direct;
|
|
2038
|
+
const options = body.fulfillment_options;
|
|
2039
|
+
if (Array.isArray(options) && options.length > 0) {
|
|
2040
|
+
const first = options[0];
|
|
2041
|
+
if (first && typeof first === "object") {
|
|
2042
|
+
const id = coerceString3(first.id);
|
|
2043
|
+
if (id) return id;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return void 0;
|
|
2047
|
+
}
|
|
2048
|
+
function readHeader2(headers, name) {
|
|
2049
|
+
for (const key of Object.keys(headers)) {
|
|
2050
|
+
if (key.toLowerCase() === name) {
|
|
2051
|
+
const raw = headers[key];
|
|
2052
|
+
if (typeof raw === "string") return raw;
|
|
2053
|
+
if (Array.isArray(raw)) return raw[0];
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
return void 0;
|
|
2057
|
+
}
|
|
2058
|
+
function stripQuery2(path) {
|
|
2059
|
+
const q = path.indexOf("?");
|
|
2060
|
+
return q === -1 ? path : path.slice(0, q);
|
|
2061
|
+
}
|
|
2062
|
+
function coerceString3(v) {
|
|
2063
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
// src/transport/vi.ts
|
|
2067
|
+
var import_decode = require("@sd-jwt/decode");
|
|
2068
|
+
var import_node_crypto = require("crypto");
|
|
2069
|
+
function extractVIClaims(sdJwtCompact) {
|
|
2070
|
+
if (!sdJwtCompact || typeof sdJwtCompact !== "string") return null;
|
|
2071
|
+
let decoded;
|
|
2072
|
+
try {
|
|
2073
|
+
decoded = (0, import_decode.decodeSdJwtSync)(sdJwtCompact, sha256Sync);
|
|
2074
|
+
} catch {
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
2077
|
+
const split = safeSplit(sdJwtCompact);
|
|
2078
|
+
const payload = decoded.jwt?.payload ?? {};
|
|
2079
|
+
const disclosures = decoded.disclosures ?? [];
|
|
2080
|
+
const claims = applyDisclosures(
|
|
2081
|
+
payload,
|
|
2082
|
+
disclosures
|
|
2083
|
+
);
|
|
2084
|
+
const mandateType = coerceMandateType(
|
|
2085
|
+
claims.mandate_type ?? claims.mandateType ?? payload.mandate_type ?? payload.mandateType
|
|
2086
|
+
);
|
|
2087
|
+
if (!mandateType) return null;
|
|
2088
|
+
const kid = coerceString4(
|
|
2089
|
+
decoded.jwt?.header?.kid ?? claims.kid ?? payload.kid
|
|
2090
|
+
);
|
|
2091
|
+
const executionMode = coerceExecutionMode(claims.execution_mode ?? claims.executionMode);
|
|
2092
|
+
const credentialProvider = coerceString4(claims.iss ?? payload.iss);
|
|
2093
|
+
const constraints = extractConstraints(
|
|
2094
|
+
claims.constraints ?? claims.default_constraints ?? {}
|
|
2095
|
+
);
|
|
2096
|
+
const transactionId = coerceString4(claims.transaction_id ?? claims.transactionId);
|
|
2097
|
+
const checkoutHash = coerceString4(
|
|
2098
|
+
claims.checkout_hash ?? claims.conditional_transaction_id ?? claims.payment_reference?.checkout_hash
|
|
2099
|
+
);
|
|
2100
|
+
return {
|
|
2101
|
+
mandateType,
|
|
2102
|
+
kid,
|
|
2103
|
+
executionMode,
|
|
2104
|
+
credentialProvider,
|
|
2105
|
+
constraints,
|
|
2106
|
+
checkoutHash,
|
|
2107
|
+
transactionId,
|
|
2108
|
+
rawLayers: split
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
function safeSplit(compact) {
|
|
2112
|
+
try {
|
|
2113
|
+
const { jwt, kbJwt } = (0, import_decode.splitSdJwt)(compact);
|
|
2114
|
+
return { l3: jwt, l2: kbJwt };
|
|
2115
|
+
} catch {
|
|
2116
|
+
return {};
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function applyDisclosures(payload, disclosures) {
|
|
2120
|
+
const result = { ...payload };
|
|
2121
|
+
for (const d of disclosures) {
|
|
2122
|
+
if (d.key && d.value !== void 0 && !(d.key in result)) {
|
|
2123
|
+
result[d.key] = d.value;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return result;
|
|
2127
|
+
}
|
|
2128
|
+
function extractConstraints(raw) {
|
|
2129
|
+
return {
|
|
2130
|
+
allowedMerchants: toAllowedPartyArray(raw.allowed_merchants ?? raw.allowedMerchants),
|
|
2131
|
+
allowedPayees: toAllowedPartyArray(raw.allowed_payees ?? raw.allowedPayees),
|
|
2132
|
+
lineItems: toLineItemArray(raw.line_items ?? raw.lineItems),
|
|
2133
|
+
paymentAmount: toPaymentAmount(raw.payment_amount ?? raw.paymentAmount),
|
|
2134
|
+
budgetLimit: toBudgetLimit(raw.budget_limit ?? raw.budgetLimit ?? raw.budget),
|
|
2135
|
+
recurrence: toRecurrence(raw.recurrence),
|
|
2136
|
+
agentRecurrence: toRecurrence(raw.agent_recurrence ?? raw.agentRecurrence)
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
function toAllowedPartyArray(v) {
|
|
2140
|
+
if (!Array.isArray(v)) return void 0;
|
|
2141
|
+
const out = [];
|
|
2142
|
+
for (const item of v) {
|
|
2143
|
+
if (item && typeof item === "object") {
|
|
2144
|
+
const r = item;
|
|
2145
|
+
out.push({
|
|
2146
|
+
id: coerceString4(r.id),
|
|
2147
|
+
name: coerceString4(r.name),
|
|
2148
|
+
website: coerceString4(r.website)
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
return out.length > 0 ? out : void 0;
|
|
2153
|
+
}
|
|
2154
|
+
function toLineItemArray(v) {
|
|
2155
|
+
if (!Array.isArray(v)) return void 0;
|
|
2156
|
+
const out = [];
|
|
2157
|
+
for (const item of v) {
|
|
2158
|
+
if (item && typeof item === "object") {
|
|
2159
|
+
const r = item;
|
|
2160
|
+
const acc = r.acceptable_items ?? r.acceptableItems;
|
|
2161
|
+
out.push({
|
|
2162
|
+
id: coerceString4(r.id),
|
|
2163
|
+
acceptableItems: Array.isArray(acc) ? acc.filter((a) => typeof a === "string") : void 0,
|
|
2164
|
+
quantity: coerceNumber2(r.quantity)
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
return out.length > 0 ? out : void 0;
|
|
2169
|
+
}
|
|
2170
|
+
function toPaymentAmount(v) {
|
|
2171
|
+
if (!v || typeof v !== "object") return void 0;
|
|
2172
|
+
const r = v;
|
|
2173
|
+
return {
|
|
2174
|
+
currency: coerceString4(r.currency),
|
|
2175
|
+
min: coerceNumber2(r.min),
|
|
2176
|
+
max: coerceNumber2(r.max)
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
function toBudgetLimit(v) {
|
|
2180
|
+
if (!v || typeof v !== "object") return void 0;
|
|
2181
|
+
const r = v;
|
|
2182
|
+
return {
|
|
2183
|
+
currency: coerceString4(r.currency),
|
|
2184
|
+
max: coerceNumber2(r.max)
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
function toRecurrence(v) {
|
|
2188
|
+
if (!v || typeof v !== "object") return void 0;
|
|
2189
|
+
const r = v;
|
|
2190
|
+
return {
|
|
2191
|
+
frequency: coerceString4(r.frequency),
|
|
2192
|
+
startDate: coerceString4(r.start_date ?? r.startDate),
|
|
2193
|
+
endDate: coerceString4(r.end_date ?? r.endDate),
|
|
2194
|
+
maxOccurrences: coerceNumber2(r.max_occurrences ?? r.maxOccurrences)
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
function coerceMandateType(v) {
|
|
2198
|
+
if (v === "checkout" || v === "payment" || v === "checkout.open" || v === "payment.open") {
|
|
2199
|
+
return v;
|
|
2200
|
+
}
|
|
2201
|
+
return null;
|
|
2202
|
+
}
|
|
2203
|
+
function coerceExecutionMode(v) {
|
|
2204
|
+
return v === "Immediate" || v === "Autonomous" || v === "Both" ? v : void 0;
|
|
2205
|
+
}
|
|
2206
|
+
function coerceString4(v) {
|
|
2207
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2208
|
+
}
|
|
2209
|
+
function coerceNumber2(v) {
|
|
2210
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
2211
|
+
if (typeof v === "string") {
|
|
2212
|
+
const n = Number(v);
|
|
2213
|
+
return Number.isFinite(n) ? n : void 0;
|
|
2214
|
+
}
|
|
2215
|
+
return void 0;
|
|
2216
|
+
}
|
|
2217
|
+
function sha256Sync(data) {
|
|
2218
|
+
const buf = typeof data === "string" ? Buffer.from(data, "utf-8") : Buffer.from(new Uint8Array(data));
|
|
2219
|
+
const hash = (0, import_node_crypto.createHash)("sha256").update(buf).digest();
|
|
2220
|
+
return new Uint8Array(hash.buffer, hash.byteOffset, hash.byteLength);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// src/transport/stripe-webhook.ts
|
|
2224
|
+
var import_node_crypto2 = require("crypto");
|
|
2225
|
+
function verifyStripeWebhook(payload, signatureHeader, secret, options = {}) {
|
|
2226
|
+
if (!signatureHeader) return { ok: false, error: "missing Stripe-Signature header" };
|
|
2227
|
+
if (!secret) return { ok: false, error: "missing webhook secret" };
|
|
2228
|
+
const parsed = parseStripeSignature(signatureHeader);
|
|
2229
|
+
if (!parsed.timestamp) return { ok: false, error: "malformed Stripe-Signature (missing t=)" };
|
|
2230
|
+
if (parsed.v1Signatures.length === 0) {
|
|
2231
|
+
return { ok: false, error: "malformed Stripe-Signature (no v1=)" };
|
|
2232
|
+
}
|
|
2233
|
+
const tolerance = options.toleranceSec ?? 300;
|
|
2234
|
+
const now = options.now ? options.now() : Math.floor(Date.now() / 1e3);
|
|
2235
|
+
if (Math.abs(now - parsed.timestamp) > tolerance) {
|
|
2236
|
+
return {
|
|
2237
|
+
ok: false,
|
|
2238
|
+
timestamp: parsed.timestamp,
|
|
2239
|
+
error: `timestamp outside tolerance (${tolerance}s)`
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
const signedPayload = `${parsed.timestamp}.${payload}`;
|
|
2243
|
+
const expected = (0, import_node_crypto2.createHmac)("sha256", secret).update(signedPayload).digest();
|
|
2244
|
+
for (const candidateHex of parsed.v1Signatures) {
|
|
2245
|
+
const candidate = hexToBuffer(candidateHex);
|
|
2246
|
+
if (!candidate) continue;
|
|
2247
|
+
if (candidate.length !== expected.length) continue;
|
|
2248
|
+
if ((0, import_node_crypto2.timingSafeEqual)(candidate, expected)) {
|
|
2249
|
+
return { ok: true, timestamp: parsed.timestamp };
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return { ok: false, timestamp: parsed.timestamp, error: "signature mismatch" };
|
|
2253
|
+
}
|
|
2254
|
+
function parseStripeSignature(header) {
|
|
2255
|
+
let timestamp = null;
|
|
2256
|
+
const v1Signatures = [];
|
|
2257
|
+
for (const part of header.split(",")) {
|
|
2258
|
+
const [rawKey, rawValue] = part.split("=");
|
|
2259
|
+
if (!rawKey || !rawValue) continue;
|
|
2260
|
+
const key = rawKey.trim();
|
|
2261
|
+
const value = rawValue.trim();
|
|
2262
|
+
if (key === "t") {
|
|
2263
|
+
const n = Number(value);
|
|
2264
|
+
if (Number.isFinite(n)) timestamp = n;
|
|
2265
|
+
} else if (key === "v1") {
|
|
2266
|
+
v1Signatures.push(value);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
return { timestamp, v1Signatures };
|
|
2270
|
+
}
|
|
2271
|
+
function hexToBuffer(hex) {
|
|
2272
|
+
if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0) return null;
|
|
2273
|
+
return Buffer.from(hex, "hex");
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// src/transport/constraint-eval.ts
|
|
2277
|
+
function evaluateVIConstraints(input) {
|
|
2278
|
+
const { constraints, transaction } = input;
|
|
2279
|
+
const results = {};
|
|
2280
|
+
if (constraints.allowedMerchants && constraints.allowedMerchants.length > 0) {
|
|
2281
|
+
results.merchant = evaluateAllowlist(
|
|
2282
|
+
"merchant",
|
|
2283
|
+
constraints.allowedMerchants,
|
|
2284
|
+
transaction.merchant
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
if (constraints.allowedPayees && constraints.allowedPayees.length > 0) {
|
|
2288
|
+
results.payee = evaluateAllowlist("payee", constraints.allowedPayees, transaction.payee);
|
|
2289
|
+
}
|
|
2290
|
+
if (constraints.lineItems && constraints.lineItems.length > 0) {
|
|
2291
|
+
results.lineItems = evaluateLineItems(constraints.lineItems, transaction.lineItems ?? []);
|
|
2292
|
+
}
|
|
2293
|
+
if (constraints.paymentAmount) {
|
|
2294
|
+
results.amount = evaluatePaymentAmount(constraints.paymentAmount, transaction);
|
|
2295
|
+
}
|
|
2296
|
+
const reasons = [];
|
|
2297
|
+
let ok = true;
|
|
2298
|
+
for (const [key, r] of Object.entries(results)) {
|
|
2299
|
+
if (!r.ok) {
|
|
2300
|
+
ok = false;
|
|
2301
|
+
reasons.push(r.reason ?? `${key} failed`);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
return { ok, results, reasons };
|
|
2305
|
+
}
|
|
2306
|
+
function evaluatePaymentMethodAllowlist(input) {
|
|
2307
|
+
const allow = input.allowedMethods ?? [];
|
|
2308
|
+
if (allow.length === 0) return { ok: true };
|
|
2309
|
+
if (!input.requestedMethod) {
|
|
2310
|
+
return { ok: false, reason: "no payment method in request; allowlist configured" };
|
|
2311
|
+
}
|
|
2312
|
+
const lowered = input.requestedMethod.toLowerCase();
|
|
2313
|
+
const allowed = allow.some((m) => m.toLowerCase() === lowered);
|
|
2314
|
+
if (!allowed) {
|
|
2315
|
+
return {
|
|
2316
|
+
ok: false,
|
|
2317
|
+
reason: `payment method "${input.requestedMethod}" not in allowlist [${allow.join(", ")}]`
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
return { ok: true };
|
|
2321
|
+
}
|
|
2322
|
+
function evaluateSpendingLimit(input) {
|
|
2323
|
+
const { limit, requested } = input;
|
|
2324
|
+
if (!limit || typeof limit.amount !== "number") return { ok: true };
|
|
2325
|
+
if (!requested || typeof requested.amount !== "number") return { ok: true };
|
|
2326
|
+
if (limit.currency && requested.currency && limit.currency !== requested.currency) {
|
|
2327
|
+
return {
|
|
2328
|
+
ok: false,
|
|
2329
|
+
reason: `currency mismatch: limit ${limit.currency} vs requested ${requested.currency}`
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
if (requested.amount > limit.amount) {
|
|
2333
|
+
return {
|
|
2334
|
+
ok: false,
|
|
2335
|
+
reason: `requested ${requested.amount} ${requested.currency ?? ""} exceeds limit ${limit.amount} ${limit.currency ?? ""}`.trim()
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
return { ok: true };
|
|
2339
|
+
}
|
|
2340
|
+
function evaluateAllowlist(kind, allowlist, actual) {
|
|
2341
|
+
if (!actual || !actual.id && !actual.website) {
|
|
2342
|
+
return { ok: false, reason: `no ${kind} in transaction; allowlist configured` };
|
|
2343
|
+
}
|
|
2344
|
+
for (const entry of allowlist) {
|
|
2345
|
+
if (entry.id && actual.id && entry.id === actual.id) return { ok: true };
|
|
2346
|
+
if (entry.website && actual.website && domainsMatch(entry.website, actual.website)) {
|
|
2347
|
+
return { ok: true };
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
const allowedDescriptors = allowlist.map(describeParty).join(", ");
|
|
2351
|
+
const actualDescriptor = describeParty(actual);
|
|
2352
|
+
return {
|
|
2353
|
+
ok: false,
|
|
2354
|
+
reason: `${kind} ${actualDescriptor} not in allowlist [${allowedDescriptors}]`
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
function evaluateLineItems(allowlist, actualItems) {
|
|
2358
|
+
if (actualItems.length === 0) {
|
|
2359
|
+
return { ok: false, reason: "no line items in transaction; allowlist configured" };
|
|
2360
|
+
}
|
|
2361
|
+
const reasons = [];
|
|
2362
|
+
for (const item of actualItems) {
|
|
2363
|
+
const match = allowlist.find(
|
|
2364
|
+
(a) => a.id && a.id === item.id || (a.acceptableItems ?? []).includes(item.id ?? "")
|
|
2365
|
+
);
|
|
2366
|
+
if (!match) {
|
|
2367
|
+
reasons.push(`line item "${item.id ?? "(unnamed)"}" not in allowlist`);
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2370
|
+
if (typeof match.quantity === "number" && typeof item.quantity === "number" && item.quantity > match.quantity) {
|
|
2371
|
+
reasons.push(
|
|
2372
|
+
`line item "${item.id}" quantity ${item.quantity} exceeds allowed ${match.quantity}`
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return reasons.length === 0 ? { ok: true } : { ok: false, reason: reasons.join("; ") };
|
|
2377
|
+
}
|
|
2378
|
+
function evaluatePaymentAmount(bound, transaction) {
|
|
2379
|
+
if (typeof transaction.amount !== "number") {
|
|
2380
|
+
return { ok: false, reason: "no amount in transaction; paymentAmount bound configured" };
|
|
2381
|
+
}
|
|
2382
|
+
if (bound.currency && transaction.currency && bound.currency !== transaction.currency) {
|
|
2383
|
+
return {
|
|
2384
|
+
ok: false,
|
|
2385
|
+
reason: `currency mismatch: bound ${bound.currency} vs transaction ${transaction.currency}`
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
if (typeof bound.min === "number" && transaction.amount < bound.min) {
|
|
2389
|
+
return {
|
|
2390
|
+
ok: false,
|
|
2391
|
+
reason: `amount ${transaction.amount} below min ${bound.min}`
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
if (typeof bound.max === "number" && transaction.amount > bound.max) {
|
|
2395
|
+
return {
|
|
2396
|
+
ok: false,
|
|
2397
|
+
reason: `amount ${transaction.amount} above max ${bound.max}`
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
return { ok: true };
|
|
2401
|
+
}
|
|
2402
|
+
function domainsMatch(allow, actual) {
|
|
2403
|
+
const a = normalizeDomain(allow);
|
|
2404
|
+
const b = normalizeDomain(actual);
|
|
2405
|
+
return a === b || b.endsWith(`.${a}`);
|
|
2406
|
+
}
|
|
2407
|
+
function normalizeDomain(value) {
|
|
2408
|
+
try {
|
|
2409
|
+
const withScheme = /^https?:\/\//.test(value) ? value : `https://${value}`;
|
|
2410
|
+
return new URL(withScheme).hostname.toLowerCase();
|
|
2411
|
+
} catch {
|
|
2412
|
+
return value.toLowerCase();
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
function describeParty(party) {
|
|
2416
|
+
if (party.id) return `id:${party.id}`;
|
|
2417
|
+
if (party.website) return party.website;
|
|
2418
|
+
if (party.name) return party.name;
|
|
2419
|
+
return "(unnamed)";
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
// src/transport/identity-binding.ts
|
|
2423
|
+
async function bindIdentity(claims, resolver) {
|
|
2424
|
+
const resolutions = [];
|
|
2425
|
+
for (const claim2 of claims) {
|
|
2426
|
+
if (!claim2.value) {
|
|
2427
|
+
resolutions.push({ claim: claim2, agentId: null });
|
|
2428
|
+
continue;
|
|
2429
|
+
}
|
|
2430
|
+
const agentId = await resolver(claim2);
|
|
2431
|
+
resolutions.push({ claim: claim2, agentId });
|
|
2432
|
+
}
|
|
2433
|
+
const resolvedIds = resolutions.map((r) => r.agentId).filter((id) => typeof id === "string" && id.length > 0);
|
|
2434
|
+
const unique = Array.from(new Set(resolvedIds));
|
|
2435
|
+
const mismatchAcrossLayers = unique.length > 1;
|
|
2436
|
+
const mappedAstraSyncAgentId = unique.length === 1 ? unique[0] : void 0;
|
|
2437
|
+
return {
|
|
2438
|
+
claims,
|
|
2439
|
+
mappedAstraSyncAgentId,
|
|
2440
|
+
mismatchAcrossLayers,
|
|
2441
|
+
resolutions
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
var claim = {
|
|
2445
|
+
viKid: (value) => ({ protocol: "vi", field: "kid", value }),
|
|
2446
|
+
ap2AgentId: (value) => ({ protocol: "ap2", field: "agent_id", value }),
|
|
2447
|
+
acpBearer: (value) => ({ protocol: "acp", field: "bearer", value }),
|
|
2448
|
+
mppSource: (value) => ({ protocol: "mpp", field: "source", value }),
|
|
2449
|
+
x402Wallet: (value) => ({ protocol: "x402", field: "wallet", value }),
|
|
2450
|
+
agentPayKid: (value) => ({ protocol: "agentpay", field: "kid", value }),
|
|
2451
|
+
tapKid: (value) => ({ protocol: "tap", field: "kid", value }),
|
|
2452
|
+
webBotAuthKid: (value) => ({
|
|
2453
|
+
protocol: "webbotauth",
|
|
2454
|
+
field: "kid",
|
|
2455
|
+
value
|
|
2456
|
+
})
|
|
2457
|
+
};
|
|
2458
|
+
|
|
2459
|
+
// src/transport/ap2.ts
|
|
2460
|
+
var import_decode2 = require("@sd-jwt/decode");
|
|
2461
|
+
var import_node_crypto3 = require("crypto");
|
|
2462
|
+
function extractAP2Mandate(sdJwtCompact) {
|
|
2463
|
+
if (!sdJwtCompact || typeof sdJwtCompact !== "string") return null;
|
|
2464
|
+
let decoded;
|
|
2465
|
+
try {
|
|
2466
|
+
decoded = (0, import_decode2.decodeSdJwtSync)(sdJwtCompact, sha256Sync2);
|
|
2467
|
+
} catch {
|
|
2468
|
+
return null;
|
|
2469
|
+
}
|
|
2470
|
+
const payload = decoded.jwt?.payload ?? {};
|
|
2471
|
+
const disclosures = decoded.disclosures ?? [];
|
|
2472
|
+
const claims = applyDisclosures2(
|
|
2473
|
+
payload,
|
|
2474
|
+
disclosures
|
|
2475
|
+
);
|
|
2476
|
+
const type = coerceMandateType2(claims.type ?? claims.mandate_type ?? claims.mandateType);
|
|
2477
|
+
if (!type) return null;
|
|
2478
|
+
if (type === "intent_mandate") return buildIntent(claims);
|
|
2479
|
+
if (type === "cart_mandate") return buildCart(claims);
|
|
2480
|
+
return buildPayment(claims);
|
|
2481
|
+
}
|
|
2482
|
+
function extractAP2Mandates(input) {
|
|
2483
|
+
const intent = input.intent ? extractAP2Mandate(input.intent) : null;
|
|
2484
|
+
const cart = input.cart ? extractAP2Mandate(input.cart) : null;
|
|
2485
|
+
const payment = input.payment ? extractAP2Mandate(input.payment) : null;
|
|
2486
|
+
return {
|
|
2487
|
+
intent: intent ?? void 0,
|
|
2488
|
+
cart: cart ?? void 0,
|
|
2489
|
+
payment: payment ?? void 0,
|
|
2490
|
+
rawLayers: {
|
|
2491
|
+
intentJwt: input.intent,
|
|
2492
|
+
cartJwt: input.cart,
|
|
2493
|
+
paymentJwt: input.payment
|
|
2494
|
+
}
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
function buildIntent(claims) {
|
|
2498
|
+
return {
|
|
2499
|
+
type: "intent_mandate",
|
|
2500
|
+
agent_id: coerceString5(claims.agent_id ?? claims.agentId),
|
|
2501
|
+
user_id: coerceString5(claims.user_id ?? claims.userId ?? claims.sub),
|
|
2502
|
+
merchant_category: coerceString5(claims.merchant_category ?? claims.merchantCategory),
|
|
2503
|
+
allowedMerchantDomains: toStringArray(
|
|
2504
|
+
claims.allowed_merchant_domains ?? claims.allowedMerchantDomains
|
|
2505
|
+
),
|
|
2506
|
+
paymentMethods: toStringArray(claims.payment_methods ?? claims.paymentMethods),
|
|
2507
|
+
expires: coerceString5(claims.expires ?? claims.exp),
|
|
2508
|
+
payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),
|
|
2509
|
+
raw: claims
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
function buildCart(claims) {
|
|
2513
|
+
return {
|
|
2514
|
+
type: "cart_mandate",
|
|
2515
|
+
agent_id: coerceString5(claims.agent_id ?? claims.agentId),
|
|
2516
|
+
intent_mandate_id: coerceString5(claims.intent_mandate_id ?? claims.intentMandateId),
|
|
2517
|
+
merchant_id: coerceString5(claims.merchant_id ?? claims.merchantId),
|
|
2518
|
+
line_items: toLineItems(claims.line_items ?? claims.lineItems),
|
|
2519
|
+
payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),
|
|
2520
|
+
expires: coerceString5(claims.expires ?? claims.exp),
|
|
2521
|
+
raw: claims
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
function buildPayment(claims) {
|
|
2525
|
+
return {
|
|
2526
|
+
type: "payment_mandate",
|
|
2527
|
+
agent_id: coerceString5(claims.agent_id ?? claims.agentId),
|
|
2528
|
+
cart_mandate_id: coerceString5(claims.cart_mandate_id ?? claims.cartMandateId),
|
|
2529
|
+
payment_method: coerceString5(claims.payment_method ?? claims.paymentMethod),
|
|
2530
|
+
payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),
|
|
2531
|
+
credential_provider: coerceString5(claims.credential_provider ?? claims.credentialProvider),
|
|
2532
|
+
raw: claims
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
function toPaymentDetails(v) {
|
|
2536
|
+
if (!v || typeof v !== "object") return void 0;
|
|
2537
|
+
const r = v;
|
|
2538
|
+
const amount = r.amount;
|
|
2539
|
+
return {
|
|
2540
|
+
amount: amount ? {
|
|
2541
|
+
value: coerceStringOrNumber(amount.value),
|
|
2542
|
+
currency: coerceString5(amount.currency)
|
|
2543
|
+
} : void 0,
|
|
2544
|
+
label: coerceString5(r.label)
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
function toLineItems(v) {
|
|
2548
|
+
if (!Array.isArray(v)) return void 0;
|
|
2549
|
+
const items = [];
|
|
2550
|
+
for (const item of v) {
|
|
2551
|
+
if (!item || typeof item !== "object") continue;
|
|
2552
|
+
const r = item;
|
|
2553
|
+
const price = r.price;
|
|
2554
|
+
items.push({
|
|
2555
|
+
id: coerceString5(r.id),
|
|
2556
|
+
quantity: coerceNumber3(r.quantity),
|
|
2557
|
+
price: price ? {
|
|
2558
|
+
value: coerceStringOrNumber(price.value),
|
|
2559
|
+
currency: coerceString5(price.currency)
|
|
2560
|
+
} : void 0
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
return items.length > 0 ? items : void 0;
|
|
2564
|
+
}
|
|
2565
|
+
function toStringArray(v) {
|
|
2566
|
+
if (!Array.isArray(v)) return void 0;
|
|
2567
|
+
const out = v.filter((i) => typeof i === "string" && i.length > 0);
|
|
2568
|
+
return out.length > 0 ? out : void 0;
|
|
2569
|
+
}
|
|
2570
|
+
function applyDisclosures2(payload, disclosures) {
|
|
2571
|
+
const result = { ...payload };
|
|
2572
|
+
for (const d of disclosures) {
|
|
2573
|
+
if (d.key && d.value !== void 0 && !(d.key in result)) {
|
|
2574
|
+
result[d.key] = d.value;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
return result;
|
|
2578
|
+
}
|
|
2579
|
+
function coerceMandateType2(v) {
|
|
2580
|
+
if (v === "intent_mandate" || v === "cart_mandate" || v === "payment_mandate") return v;
|
|
2581
|
+
return null;
|
|
2582
|
+
}
|
|
2583
|
+
function coerceString5(v) {
|
|
2584
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2585
|
+
}
|
|
2586
|
+
function coerceNumber3(v) {
|
|
2587
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
2588
|
+
if (typeof v === "string") {
|
|
2589
|
+
const n = Number(v);
|
|
2590
|
+
return Number.isFinite(n) ? n : void 0;
|
|
2591
|
+
}
|
|
2592
|
+
return void 0;
|
|
2593
|
+
}
|
|
2594
|
+
function coerceStringOrNumber(v) {
|
|
2595
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
2596
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
2597
|
+
return void 0;
|
|
2598
|
+
}
|
|
2599
|
+
function sha256Sync2(data) {
|
|
2600
|
+
const buf = typeof data === "string" ? Buffer.from(data, "utf-8") : Buffer.from(new Uint8Array(data));
|
|
2601
|
+
const hash = (0, import_node_crypto3.createHash)("sha256").update(buf).digest();
|
|
2602
|
+
return new Uint8Array(hash.buffer, hash.byteOffset, hash.byteLength);
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// src/transport/ap2-verify.ts
|
|
2606
|
+
function verifyAP2Chain(input) {
|
|
2607
|
+
const { triple } = input;
|
|
2608
|
+
const errors = [];
|
|
2609
|
+
const intentPresent = triple.intent !== void 0;
|
|
2610
|
+
const cartRefOk = checkCartRef(triple, errors);
|
|
2611
|
+
const paymentRefOk = checkPaymentRef(triple, errors);
|
|
2612
|
+
const { ok: agentIdContinuity, agentId } = checkAgentContinuity(triple, errors);
|
|
2613
|
+
const paymentMethodAllowed = checkPaymentMethod(triple, errors);
|
|
2614
|
+
const totalsConsistent = checkTotals(triple, errors);
|
|
2615
|
+
const expiryOk = checkExpiries(triple, input.clockSkewSec ?? 300, input.now, errors);
|
|
2616
|
+
const ok = cartRefOk && paymentRefOk && agentIdContinuity && paymentMethodAllowed && totalsConsistent && expiryOk;
|
|
2617
|
+
return {
|
|
2618
|
+
ok,
|
|
2619
|
+
checks: {
|
|
2620
|
+
intentPresent,
|
|
2621
|
+
cartRefOk,
|
|
2622
|
+
paymentRefOk,
|
|
2623
|
+
agentIdContinuity,
|
|
2624
|
+
paymentMethodAllowed,
|
|
2625
|
+
totalsConsistent,
|
|
2626
|
+
expiryOk
|
|
2627
|
+
},
|
|
2628
|
+
agentId,
|
|
2629
|
+
errors
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
function checkCartRef(triple, errors) {
|
|
2633
|
+
const cart = triple.cart;
|
|
2634
|
+
if (!cart) return true;
|
|
2635
|
+
if (!cart.intent_mandate_id) return true;
|
|
2636
|
+
const intentId = triple.intent?.raw?.id;
|
|
2637
|
+
if (intentId && cart.intent_mandate_id !== intentId) {
|
|
2638
|
+
errors.push(
|
|
2639
|
+
`cart.intent_mandate_id (${cart.intent_mandate_id}) does not match intent.id (${intentId})`
|
|
2640
|
+
);
|
|
2641
|
+
return false;
|
|
2642
|
+
}
|
|
2643
|
+
return true;
|
|
2644
|
+
}
|
|
2645
|
+
function checkPaymentRef(triple, errors) {
|
|
2646
|
+
const payment = triple.payment;
|
|
2647
|
+
if (!payment) return true;
|
|
2648
|
+
if (!payment.cart_mandate_id) return true;
|
|
2649
|
+
const cartId = triple.cart?.raw?.id;
|
|
2650
|
+
if (cartId && payment.cart_mandate_id !== cartId) {
|
|
2651
|
+
errors.push(
|
|
2652
|
+
`payment.cart_mandate_id (${payment.cart_mandate_id}) does not match cart.id (${cartId})`
|
|
2653
|
+
);
|
|
2654
|
+
return false;
|
|
2655
|
+
}
|
|
2656
|
+
return true;
|
|
2657
|
+
}
|
|
2658
|
+
function checkAgentContinuity(triple, errors) {
|
|
2659
|
+
const ids = [triple.intent?.agent_id, triple.cart?.agent_id, triple.payment?.agent_id].filter(
|
|
2660
|
+
(id) => typeof id === "string" && id.length > 0
|
|
2661
|
+
);
|
|
2662
|
+
if (ids.length === 0) return { ok: true };
|
|
2663
|
+
const unique = new Set(ids);
|
|
2664
|
+
if (unique.size > 1) {
|
|
2665
|
+
errors.push(`agent_id mismatch across mandates: ${Array.from(unique).join(", ")}`);
|
|
2666
|
+
return { ok: false, agentId: void 0 };
|
|
2667
|
+
}
|
|
2668
|
+
return { ok: true, agentId: ids[0] };
|
|
2669
|
+
}
|
|
2670
|
+
function checkPaymentMethod(triple, errors) {
|
|
2671
|
+
const paymentMethod = triple.payment?.payment_method;
|
|
2672
|
+
const allowed = triple.intent?.paymentMethods;
|
|
2673
|
+
if (!paymentMethod || !allowed || allowed.length === 0) return true;
|
|
2674
|
+
if (!allowed.includes(paymentMethod)) {
|
|
2675
|
+
errors.push(
|
|
2676
|
+
`payment_method "${paymentMethod}" not in intent.paymentMethods [${allowed.join(", ")}]`
|
|
2677
|
+
);
|
|
2678
|
+
return false;
|
|
2679
|
+
}
|
|
2680
|
+
return true;
|
|
2681
|
+
}
|
|
2682
|
+
function checkTotals(triple, errors) {
|
|
2683
|
+
const intentTotal = toNumericAmount(triple.intent?.payment_details_total);
|
|
2684
|
+
const cartTotal = toNumericAmount(triple.cart?.payment_details_total);
|
|
2685
|
+
const paymentTotal = toNumericAmount(triple.payment?.payment_details_total);
|
|
2686
|
+
if (intentTotal && cartTotal && intentTotal.currency === cartTotal.currency) {
|
|
2687
|
+
if (cartTotal.value > intentTotal.value) {
|
|
2688
|
+
errors.push(
|
|
2689
|
+
`cart total ${cartTotal.value} ${cartTotal.currency} exceeds intent cap ${intentTotal.value}`
|
|
2690
|
+
);
|
|
2691
|
+
return false;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
if (cartTotal && paymentTotal && cartTotal.currency === paymentTotal.currency) {
|
|
2695
|
+
if (paymentTotal.value > cartTotal.value) {
|
|
2696
|
+
errors.push(
|
|
2697
|
+
`payment total ${paymentTotal.value} ${paymentTotal.currency} exceeds cart total ${cartTotal.value}`
|
|
2698
|
+
);
|
|
2699
|
+
return false;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
return true;
|
|
2703
|
+
}
|
|
2704
|
+
function checkExpiries(triple, toleranceSec, nowFn, errors) {
|
|
2705
|
+
const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
|
|
2706
|
+
let ok = true;
|
|
2707
|
+
for (const [name, mandate] of [
|
|
2708
|
+
["intent", triple.intent],
|
|
2709
|
+
["cart", triple.cart]
|
|
2710
|
+
]) {
|
|
2711
|
+
if (!mandate?.expires) continue;
|
|
2712
|
+
const parsed = parseExpiry(mandate.expires);
|
|
2713
|
+
if (parsed === null) {
|
|
2714
|
+
errors.push(`${name}.expires unparseable`);
|
|
2715
|
+
ok = false;
|
|
2716
|
+
continue;
|
|
2717
|
+
}
|
|
2718
|
+
if (now > parsed + toleranceSec) {
|
|
2719
|
+
errors.push(`${name} mandate expired at ${mandate.expires}`);
|
|
2720
|
+
ok = false;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
return ok;
|
|
2724
|
+
}
|
|
2725
|
+
function toNumericAmount(total) {
|
|
2726
|
+
if (!total?.amount?.value || !total.amount.currency) return null;
|
|
2727
|
+
const n = typeof total.amount.value === "string" ? Number(total.amount.value) : total.amount.value;
|
|
2728
|
+
if (!Number.isFinite(n)) return null;
|
|
2729
|
+
return { value: n, currency: total.amount.currency };
|
|
2730
|
+
}
|
|
2731
|
+
function parseExpiry(value) {
|
|
2732
|
+
const asInt = Number(value);
|
|
2733
|
+
if (Number.isFinite(asInt) && asInt > 0) {
|
|
2734
|
+
return asInt >= 1e12 ? Math.floor(asInt / 1e3) : Math.floor(asInt);
|
|
2735
|
+
}
|
|
2736
|
+
const parsedDate = Date.parse(value);
|
|
2737
|
+
if (Number.isFinite(parsedDate)) return Math.floor(parsedDate / 1e3);
|
|
2738
|
+
return null;
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
// src/transport/acp-verify.ts
|
|
2742
|
+
async function verifyACPSignature(input) {
|
|
2743
|
+
if (!input.signatureHeader) {
|
|
2744
|
+
return { ok: false, error: "missing Signature header" };
|
|
2745
|
+
}
|
|
2746
|
+
const freshness = checkTimestamp(input.timestampHeader, input.clockSkewSec ?? 300, input.now);
|
|
2747
|
+
if (!freshness.ok) {
|
|
2748
|
+
return { ok: false, error: freshness.error, timestampStale: true };
|
|
2749
|
+
}
|
|
2750
|
+
const signatureBytes = decodeBase64(input.signatureHeader);
|
|
2751
|
+
if (!signatureBytes) {
|
|
2752
|
+
return { ok: false, error: "signature header is not valid base64" };
|
|
2753
|
+
}
|
|
2754
|
+
const bodyBytes = new TextEncoder().encode(input.rawBody);
|
|
2755
|
+
const { subtle } = await getSubtle();
|
|
2756
|
+
for (const candidate of input.candidateKeys) {
|
|
2757
|
+
const declaredAlg = normalizeAlgorithm(candidate.alg);
|
|
2758
|
+
const algsToTry = declaredAlg && declaredAlg !== "unsupported" ? [declaredAlg] : ["ed25519", "es256"];
|
|
2759
|
+
for (const alg of algsToTry) {
|
|
2760
|
+
try {
|
|
2761
|
+
const verified = await tryVerify(subtle, candidate.jwk, signatureBytes, bodyBytes, alg);
|
|
2762
|
+
if (verified) return { ok: true, algorithm: alg };
|
|
2763
|
+
} catch {
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
return {
|
|
2768
|
+
ok: false,
|
|
2769
|
+
algorithm: "unsupported",
|
|
2770
|
+
error: "no candidate key verified the signature under Ed25519 or ES256"
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
async function tryVerify(subtle, jwk, signature, body, alg) {
|
|
2774
|
+
if (alg === "ed25519") {
|
|
2775
|
+
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519") return false;
|
|
2776
|
+
const key = await subtle.importKey("jwk", jwk, { name: "Ed25519" }, false, [
|
|
2777
|
+
"verify"
|
|
2778
|
+
]);
|
|
2779
|
+
return await subtle.verify({ name: "Ed25519" }, key, toBuf(signature), toBuf(body));
|
|
2780
|
+
}
|
|
2781
|
+
if (alg === "es256") {
|
|
2782
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256") return false;
|
|
2783
|
+
const key = await subtle.importKey(
|
|
2784
|
+
"jwk",
|
|
2785
|
+
jwk,
|
|
2786
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
2787
|
+
false,
|
|
2788
|
+
["verify"]
|
|
2789
|
+
);
|
|
2790
|
+
return await subtle.verify(
|
|
2791
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
2792
|
+
key,
|
|
2793
|
+
toBuf(signature),
|
|
2794
|
+
toBuf(body)
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
return false;
|
|
2798
|
+
}
|
|
2799
|
+
function toBuf(bytes) {
|
|
2800
|
+
const out = new ArrayBuffer(bytes.byteLength);
|
|
2801
|
+
new Uint8Array(out).set(bytes);
|
|
2802
|
+
return out;
|
|
2803
|
+
}
|
|
2804
|
+
function checkTimestamp(headerValue, toleranceSec, nowFn) {
|
|
2805
|
+
if (!headerValue) return { ok: false, error: "missing Timestamp header" };
|
|
2806
|
+
const ts = parseTimestamp(headerValue);
|
|
2807
|
+
if (ts === null) return { ok: false, error: "unparseable Timestamp header" };
|
|
2808
|
+
const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
|
|
2809
|
+
if (Math.abs(now - ts) > toleranceSec) {
|
|
2810
|
+
return { ok: false, error: `timestamp outside ${toleranceSec}s tolerance` };
|
|
2811
|
+
}
|
|
2812
|
+
return { ok: true };
|
|
2813
|
+
}
|
|
2814
|
+
function parseTimestamp(value) {
|
|
2815
|
+
const asInt = Number(value);
|
|
2816
|
+
if (Number.isFinite(asInt) && asInt > 0) {
|
|
2817
|
+
return asInt >= 1e12 ? Math.floor(asInt / 1e3) : Math.floor(asInt);
|
|
2818
|
+
}
|
|
2819
|
+
const parsedDate = Date.parse(value);
|
|
2820
|
+
if (Number.isFinite(parsedDate)) return Math.floor(parsedDate / 1e3);
|
|
2821
|
+
return null;
|
|
2822
|
+
}
|
|
2823
|
+
function normalizeAlgorithm(alg) {
|
|
2824
|
+
if (!alg) return void 0;
|
|
2825
|
+
const lowered = alg.toLowerCase();
|
|
2826
|
+
if (lowered === "ed25519" || lowered === "eddsa") return "ed25519";
|
|
2827
|
+
if (lowered === "es256" || lowered.startsWith("ecdsa-p256")) return "es256";
|
|
2828
|
+
return "unsupported";
|
|
2829
|
+
}
|
|
2830
|
+
function decodeBase64(value) {
|
|
2831
|
+
try {
|
|
2832
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
2833
|
+
const pad = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
|
|
2834
|
+
return new Uint8Array(Buffer.from(normalized + pad, "base64"));
|
|
2835
|
+
} catch {
|
|
2836
|
+
return null;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
async function getSubtle() {
|
|
2840
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
2841
|
+
return { subtle: globalThis.crypto.subtle };
|
|
2842
|
+
}
|
|
2843
|
+
const nodeCrypto = await import("crypto");
|
|
2844
|
+
return { subtle: nodeCrypto.webcrypto.subtle };
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// src/transport/mpp.ts
|
|
2848
|
+
var import_mppx = require("mppx");
|
|
2849
|
+
function extractMPPFromRequest(request) {
|
|
2850
|
+
const auth = readHeader3(request.headers, "authorization");
|
|
2851
|
+
if (!auth || !/^\s*Payment\s+/i.test(auth)) return null;
|
|
2852
|
+
try {
|
|
2853
|
+
const credential = import_mppx.Credential.deserialize(auth);
|
|
2854
|
+
return {
|
|
2855
|
+
kind: "credential",
|
|
2856
|
+
credential: {
|
|
2857
|
+
challenge: summarizeChallenge(credential.challenge),
|
|
2858
|
+
source: credential.source,
|
|
2859
|
+
payload: credential.payload
|
|
2860
|
+
},
|
|
2861
|
+
rawBody: request.rawBody
|
|
2862
|
+
};
|
|
2863
|
+
} catch {
|
|
2864
|
+
return { kind: "error", error: { type: "invalid-credential-encoding" } };
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
function extractMPPFromResponse(response) {
|
|
2868
|
+
if (response.status === 402) {
|
|
2869
|
+
const challenges = collectChallenges(response);
|
|
2870
|
+
if (challenges.length === 0) return null;
|
|
2871
|
+
const methods = Array.from(new Set(challenges.map((c) => c.method)));
|
|
2872
|
+
return {
|
|
2873
|
+
kind: "challenge",
|
|
2874
|
+
challenges,
|
|
2875
|
+
offeredMethods: methods
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
const receiptHeader = readHeader3(response.headers, "payment-receipt");
|
|
2879
|
+
if (receiptHeader) {
|
|
2880
|
+
try {
|
|
2881
|
+
const parsed = import_mppx.Receipt.deserialize(receiptHeader);
|
|
2882
|
+
const r = parsed;
|
|
2883
|
+
return {
|
|
2884
|
+
kind: "receipt",
|
|
2885
|
+
receipt: {
|
|
2886
|
+
method: coerceString6(r.method),
|
|
2887
|
+
reference: coerceString6(r.reference),
|
|
2888
|
+
externalId: coerceString6(r.externalId ?? r.external_id),
|
|
2889
|
+
status: coerceString6(r.status),
|
|
2890
|
+
timestamp: coerceString6(r.timestamp),
|
|
2891
|
+
raw: r
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
} catch {
|
|
2895
|
+
return { kind: "error", error: { type: "invalid-receipt-encoding" } };
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
const contentType = readHeader3(response.headers, "content-type");
|
|
2899
|
+
if (contentType && /application\/problem\+json/i.test(contentType)) {
|
|
2900
|
+
const body = typeof response.body === "object" && response.body !== null ? response.body : {};
|
|
2901
|
+
return {
|
|
2902
|
+
kind: "error",
|
|
2903
|
+
error: {
|
|
2904
|
+
type: coerceString6(body.type),
|
|
2905
|
+
title: coerceString6(body.title),
|
|
2906
|
+
detail: coerceString6(body.detail)
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
return null;
|
|
2911
|
+
}
|
|
2912
|
+
function extractMPPContext(message) {
|
|
2913
|
+
if ("request" in message) return extractMPPFromRequest(message.request);
|
|
2914
|
+
if ("response" in message) return extractMPPFromResponse(message.response);
|
|
2915
|
+
if (typeof message.status === "number") {
|
|
2916
|
+
return extractMPPFromResponse(message);
|
|
2917
|
+
}
|
|
2918
|
+
return extractMPPFromRequest(message);
|
|
2919
|
+
}
|
|
2920
|
+
function collectChallenges(response) {
|
|
2921
|
+
const wwwAuth = readHeader3(response.headers, "www-authenticate");
|
|
2922
|
+
if (!wwwAuth) return [];
|
|
2923
|
+
const headers = new Headers();
|
|
2924
|
+
headers.set("www-authenticate", wwwAuth);
|
|
2925
|
+
const out = [];
|
|
2926
|
+
try {
|
|
2927
|
+
const list = import_mppx.Challenge.fromHeadersList(headers);
|
|
2928
|
+
for (const ch of list) {
|
|
2929
|
+
out.push(summarizeChallenge(ch));
|
|
2930
|
+
}
|
|
2931
|
+
} catch {
|
|
2932
|
+
}
|
|
2933
|
+
return out;
|
|
2934
|
+
}
|
|
2935
|
+
function summarizeChallenge(ch) {
|
|
2936
|
+
const raw = ch;
|
|
2937
|
+
return {
|
|
2938
|
+
id: coerceString6(raw.id) ?? "",
|
|
2939
|
+
realm: coerceString6(raw.realm) ?? "",
|
|
2940
|
+
method: coerceString6(raw.method) ?? "",
|
|
2941
|
+
intent: coerceString6(raw.intent) ?? "",
|
|
2942
|
+
request: raw.request ?? {},
|
|
2943
|
+
expires: coerceString6(raw.expires),
|
|
2944
|
+
digest: coerceString6(raw.digest),
|
|
2945
|
+
description: coerceString6(raw.description),
|
|
2946
|
+
opaque: raw.opaque
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
function readHeader3(headers, name) {
|
|
2950
|
+
for (const key of Object.keys(headers)) {
|
|
2951
|
+
if (key.toLowerCase() === name) {
|
|
2952
|
+
const raw = headers[key];
|
|
2953
|
+
if (typeof raw === "string") return raw;
|
|
2954
|
+
if (Array.isArray(raw)) return raw.join(", ");
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return void 0;
|
|
2958
|
+
}
|
|
2959
|
+
function coerceString6(v) {
|
|
2960
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// src/transport/mpp-verify.ts
|
|
2964
|
+
var import_mppx2 = require("mppx");
|
|
2965
|
+
function verifyMPP(input) {
|
|
2966
|
+
const { context } = input;
|
|
2967
|
+
const tolerance = input.clockSkewSec ?? 300;
|
|
2968
|
+
const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
2969
|
+
const challenge = context.credential?.challenge ?? (context.challenges && context.challenges[0]);
|
|
2970
|
+
const source = context.credential?.source;
|
|
2971
|
+
const method = challenge?.method;
|
|
2972
|
+
let expiryOk = true;
|
|
2973
|
+
if (challenge?.expires) {
|
|
2974
|
+
const parsedExpiry = Date.parse(challenge.expires);
|
|
2975
|
+
if (!Number.isFinite(parsedExpiry)) {
|
|
2976
|
+
return {
|
|
2977
|
+
ok: false,
|
|
2978
|
+
expiryOk: false,
|
|
2979
|
+
bodyDigestOk: null,
|
|
2980
|
+
source,
|
|
2981
|
+
method,
|
|
2982
|
+
error: "unparseable challenge.expires"
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2985
|
+
const expiresSec = Math.floor(parsedExpiry / 1e3);
|
|
2986
|
+
if (nowSec > expiresSec + tolerance) {
|
|
2987
|
+
expiryOk = false;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
let bodyDigestOk = null;
|
|
2991
|
+
if (challenge?.digest && input.rawBody !== void 0) {
|
|
2992
|
+
try {
|
|
2993
|
+
if (!/^sha-256=/.test(challenge.digest)) {
|
|
2994
|
+
bodyDigestOk = false;
|
|
2995
|
+
} else {
|
|
2996
|
+
bodyDigestOk = import_mppx2.BodyDigest.verify(challenge.digest, input.rawBody);
|
|
2997
|
+
}
|
|
2998
|
+
} catch {
|
|
2999
|
+
bodyDigestOk = false;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
const ok = expiryOk && (bodyDigestOk === null || bodyDigestOk === true);
|
|
3003
|
+
const errors = [];
|
|
3004
|
+
if (!expiryOk) errors.push("challenge expired");
|
|
3005
|
+
if (bodyDigestOk === false) errors.push("body digest mismatch");
|
|
3006
|
+
return {
|
|
3007
|
+
ok,
|
|
3008
|
+
expiryOk,
|
|
3009
|
+
bodyDigestOk,
|
|
3010
|
+
source,
|
|
3011
|
+
method,
|
|
3012
|
+
error: errors.length > 0 ? errors.join("; ") : void 0
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// src/transport/x402.ts
|
|
3017
|
+
var import_schemas = require("@x402/core/schemas");
|
|
3018
|
+
var import_utils = require("@x402/core/utils");
|
|
3019
|
+
function extractX402FromRequest(request) {
|
|
3020
|
+
const headerValue = readHeader4(request.headers, "x-payment");
|
|
3021
|
+
if (request.body && typeof request.body === "object") {
|
|
3022
|
+
const parsed = tryParsePayload(request.body);
|
|
3023
|
+
if (parsed) return buildPayloadContext(parsed, "body");
|
|
3024
|
+
}
|
|
3025
|
+
if (headerValue) {
|
|
3026
|
+
try {
|
|
3027
|
+
const decoded = (0, import_utils.safeBase64Decode)(headerValue);
|
|
3028
|
+
if (decoded) {
|
|
3029
|
+
const json = JSON.parse(decoded);
|
|
3030
|
+
const parsed = tryParsePayload(json);
|
|
3031
|
+
if (parsed) return buildPayloadContext(parsed, "header");
|
|
3032
|
+
}
|
|
3033
|
+
} catch {
|
|
3034
|
+
return {
|
|
3035
|
+
kind: "error",
|
|
3036
|
+
version: 1,
|
|
3037
|
+
source: "header",
|
|
3038
|
+
error: { type: "invalid-x402-payload" }
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
return null;
|
|
3043
|
+
}
|
|
3044
|
+
function extractX402FromResponse(response) {
|
|
3045
|
+
if (response.status !== 402) return null;
|
|
3046
|
+
if (response.body && typeof response.body === "object") {
|
|
3047
|
+
const parsed = tryParseRequired(response.body);
|
|
3048
|
+
if (parsed) return buildRequiredContext(parsed, "body");
|
|
3049
|
+
}
|
|
3050
|
+
const headerValue = readHeader4(response.headers, "x-payment-required");
|
|
3051
|
+
if (headerValue) {
|
|
3052
|
+
try {
|
|
3053
|
+
const decoded = (0, import_utils.safeBase64Decode)(headerValue);
|
|
3054
|
+
if (decoded) {
|
|
3055
|
+
const json = JSON.parse(decoded);
|
|
3056
|
+
const parsed = tryParseRequired(json);
|
|
3057
|
+
if (parsed) return buildRequiredContext(parsed, "header");
|
|
3058
|
+
}
|
|
3059
|
+
} catch {
|
|
3060
|
+
return {
|
|
3061
|
+
kind: "error",
|
|
3062
|
+
version: 1,
|
|
3063
|
+
source: "header",
|
|
3064
|
+
error: { type: "invalid-x402-required" }
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
return null;
|
|
3069
|
+
}
|
|
3070
|
+
function extractX402Context(message) {
|
|
3071
|
+
if ("request" in message) return extractX402FromRequest(message.request);
|
|
3072
|
+
if ("response" in message) return extractX402FromResponse(message.response);
|
|
3073
|
+
if (typeof message.status === "number") {
|
|
3074
|
+
return extractX402FromResponse(message);
|
|
3075
|
+
}
|
|
3076
|
+
return extractX402FromRequest(message);
|
|
3077
|
+
}
|
|
3078
|
+
function tryParseRequired(data) {
|
|
3079
|
+
try {
|
|
3080
|
+
return (0, import_schemas.validatePaymentRequired)(data);
|
|
3081
|
+
} catch {
|
|
3082
|
+
return null;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
function tryParsePayload(data) {
|
|
3086
|
+
try {
|
|
3087
|
+
return (0, import_schemas.validatePaymentPayload)(data);
|
|
3088
|
+
} catch {
|
|
3089
|
+
return null;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
function buildRequiredContext(parsed, source) {
|
|
3093
|
+
const asRecord = parsed;
|
|
3094
|
+
const version = coerceVersion(asRecord.x402Version);
|
|
3095
|
+
const accepts = asRecord.accepts ?? [];
|
|
3096
|
+
return {
|
|
3097
|
+
kind: "required",
|
|
3098
|
+
version,
|
|
3099
|
+
source,
|
|
3100
|
+
paymentRequired: {
|
|
3101
|
+
resource: resolveResource(asRecord.resource),
|
|
3102
|
+
accepts: accepts.map(summarizeRequirement),
|
|
3103
|
+
extensions: asRecord.extensions,
|
|
3104
|
+
error: typeof asRecord.error === "string" ? asRecord.error : void 0
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
function buildPayloadContext(parsed, source) {
|
|
3109
|
+
const asRecord = parsed;
|
|
3110
|
+
const version = coerceVersion(asRecord.x402Version);
|
|
3111
|
+
const accepted = asRecord.accepted;
|
|
3112
|
+
const payload = asRecord.payload ?? {};
|
|
3113
|
+
return {
|
|
3114
|
+
kind: "payload",
|
|
3115
|
+
version,
|
|
3116
|
+
source,
|
|
3117
|
+
paymentPayload: {
|
|
3118
|
+
scheme: accepted?.scheme ?? (typeof asRecord.scheme === "string" ? asRecord.scheme : ""),
|
|
3119
|
+
network: accepted?.network ?? (typeof asRecord.network === "string" ? asRecord.network : ""),
|
|
3120
|
+
payload,
|
|
3121
|
+
extensions: asRecord.extensions
|
|
3122
|
+
}
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
function summarizeRequirement(req) {
|
|
3126
|
+
const r = req;
|
|
3127
|
+
const amount = r.amount ?? r.maxAmountRequired ?? "0";
|
|
3128
|
+
return {
|
|
3129
|
+
scheme: r.scheme ?? "",
|
|
3130
|
+
network: r.network ?? "",
|
|
3131
|
+
asset: r.asset ?? "",
|
|
3132
|
+
amount: String(amount),
|
|
3133
|
+
payTo: r.payTo ?? "",
|
|
3134
|
+
maxTimeoutSeconds: typeof r.maxTimeoutSeconds === "number" ? r.maxTimeoutSeconds : void 0,
|
|
3135
|
+
resource: typeof r.resource === "string" ? r.resource : void 0,
|
|
3136
|
+
description: typeof r.description === "string" ? r.description : void 0
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
function resolveResource(v) {
|
|
3140
|
+
if (typeof v === "string") return v;
|
|
3141
|
+
if (v && typeof v === "object" && "url" in v && typeof v.url === "string") {
|
|
3142
|
+
return v.url;
|
|
3143
|
+
}
|
|
3144
|
+
return "";
|
|
3145
|
+
}
|
|
3146
|
+
function coerceVersion(v) {
|
|
3147
|
+
if (v === 1 || v === 2) return v;
|
|
3148
|
+
return null;
|
|
3149
|
+
}
|
|
3150
|
+
function readHeader4(headers, name) {
|
|
3151
|
+
if (!headers) return void 0;
|
|
3152
|
+
for (const key of Object.keys(headers)) {
|
|
3153
|
+
if (key.toLowerCase() === name) {
|
|
3154
|
+
const raw = headers[key];
|
|
3155
|
+
if (typeof raw === "string") return raw;
|
|
3156
|
+
if (Array.isArray(raw)) return raw[0];
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
return void 0;
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
// src/transport/vi-verify.ts
|
|
3163
|
+
var import_node_crypto4 = require("crypto");
|
|
3164
|
+
async function verifyVIChain(input) {
|
|
3165
|
+
const errors = [];
|
|
3166
|
+
const tolerance = input.clockSkewSec ?? 300;
|
|
3167
|
+
const now = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3168
|
+
const { l1, l2, l3a, l3b } = input.layers;
|
|
3169
|
+
const l1SigOk = l1 ? await input.verifySignature(l1, null) : null;
|
|
3170
|
+
if (l1 && !l1SigOk) errors.push("L1 signature invalid");
|
|
3171
|
+
const l1Cnf = extractCnfJwk(l1?.payload);
|
|
3172
|
+
const l2SigOk = await input.verifySignature(l2, l1Cnf ?? null);
|
|
3173
|
+
if (!l2SigOk) errors.push("L2 signature invalid");
|
|
3174
|
+
const l2Cnf = extractCnfJwk(l2.payload);
|
|
3175
|
+
const l3aSigOk = l3a ? await input.verifySignature(l3a, l2Cnf ?? null) : null;
|
|
3176
|
+
if (l3a && !l3aSigOk) errors.push("L3a signature invalid");
|
|
3177
|
+
const l3bSigOk = l3b ? await input.verifySignature(l3b, l2Cnf ?? null) : null;
|
|
3178
|
+
if (l3b && !l3bSigOk) errors.push("L3b signature invalid");
|
|
3179
|
+
let l1BindsL2 = true;
|
|
3180
|
+
if (l1Cnf) {
|
|
3181
|
+
const l2KeyFromHeader = await jwkForLayer(l2);
|
|
3182
|
+
l1BindsL2 = l2KeyFromHeader ? await thumbprintsMatch(l1Cnf, l2KeyFromHeader) : false;
|
|
3183
|
+
if (!l1BindsL2) errors.push("L1.cnf.jwk does not bind L2 signing key");
|
|
3184
|
+
}
|
|
3185
|
+
let l2BindsL3 = true;
|
|
3186
|
+
if (l2Cnf && (l3a || l3b)) {
|
|
3187
|
+
const l3Layer = l3a ?? l3b;
|
|
3188
|
+
const l3KeyFromHeader = await jwkForLayer(l3Layer);
|
|
3189
|
+
l2BindsL3 = l3KeyFromHeader ? await thumbprintsMatch(l2Cnf, l3KeyFromHeader) : false;
|
|
3190
|
+
if (!l2BindsL3) errors.push("L2.cnf.jwk does not bind L3 signing key");
|
|
3191
|
+
}
|
|
3192
|
+
let l3aL3bTxnIdMatch = null;
|
|
3193
|
+
if (l3a && l3b) {
|
|
3194
|
+
const a = coerceString7(l3a.payload.transaction_id ?? l3a.payload.transactionId);
|
|
3195
|
+
const b = coerceString7(l3b.payload.transaction_id ?? l3b.payload.transactionId);
|
|
3196
|
+
if (a && b) {
|
|
3197
|
+
l3aL3bTxnIdMatch = a === b;
|
|
3198
|
+
if (!l3aL3bTxnIdMatch) {
|
|
3199
|
+
errors.push(`L3a.transaction_id (${a}) does not match L3b.transaction_id (${b})`);
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
let checkoutHashOk = null;
|
|
3204
|
+
if (l3b) {
|
|
3205
|
+
const declaredHash = coerceString7(
|
|
3206
|
+
l3b.payload.checkout_hash ?? l3b.payload.conditional_transaction_id ?? l3b.payload.payment_reference?.checkout_hash
|
|
3207
|
+
);
|
|
3208
|
+
if (declaredHash) {
|
|
3209
|
+
const computed = computeCheckoutHashFromL2(l2);
|
|
3210
|
+
checkoutHashOk = computed ? declaredHash === computed : false;
|
|
3211
|
+
if (!checkoutHashOk) {
|
|
3212
|
+
errors.push("L3b.checkout_hash does not match SHA-256 of L2 checkout disclosure");
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
const expiryOk = checkExpiryAcross([l1, l2, l3a, l3b], tolerance, now, errors);
|
|
3217
|
+
const ok = l1SigOk !== false && l2SigOk && l3aSigOk !== false && l3bSigOk !== false && l1BindsL2 && l2BindsL3 && l3aL3bTxnIdMatch !== false && checkoutHashOk !== false && expiryOk;
|
|
3218
|
+
return {
|
|
3219
|
+
ok,
|
|
3220
|
+
checks: {
|
|
3221
|
+
l1SigOk,
|
|
3222
|
+
l2SigOk,
|
|
3223
|
+
l3aSigOk,
|
|
3224
|
+
l3bSigOk,
|
|
3225
|
+
l1BindsL2,
|
|
3226
|
+
l2BindsL3,
|
|
3227
|
+
l3aL3bTxnIdMatch,
|
|
3228
|
+
checkoutHashOk,
|
|
3229
|
+
expiryOk
|
|
3230
|
+
},
|
|
3231
|
+
errors
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
function extractCnfJwk(payload) {
|
|
3235
|
+
if (!payload) return null;
|
|
3236
|
+
const cnf = payload.cnf;
|
|
3237
|
+
if (!cnf) return null;
|
|
3238
|
+
const jwk = cnf.jwk;
|
|
3239
|
+
return jwk ?? null;
|
|
3240
|
+
}
|
|
3241
|
+
async function jwkForLayer(layer) {
|
|
3242
|
+
const fromHeader = extractCnfJwk(layer.header);
|
|
3243
|
+
if (fromHeader) return fromHeader;
|
|
3244
|
+
const fromPayload = extractCnfJwk(layer.payload);
|
|
3245
|
+
return fromPayload;
|
|
3246
|
+
}
|
|
3247
|
+
async function thumbprintsMatch(a, b) {
|
|
3248
|
+
try {
|
|
3249
|
+
const ta = await jwkThumbprint(a);
|
|
3250
|
+
const tb = await jwkThumbprint(b);
|
|
3251
|
+
return ta === tb;
|
|
3252
|
+
} catch {
|
|
3253
|
+
return false;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
async function jwkThumbprint(jwk) {
|
|
3257
|
+
const canonical = canonicalJwk(jwk);
|
|
3258
|
+
const bytes = new TextEncoder().encode(JSON.stringify(canonical));
|
|
3259
|
+
const subtle = import_node_crypto4.webcrypto.subtle;
|
|
3260
|
+
const buffer = await new Promise((resolve, reject) => {
|
|
3261
|
+
const source = new ArrayBuffer(bytes.byteLength);
|
|
3262
|
+
new Uint8Array(source).set(bytes);
|
|
3263
|
+
subtle.digest("SHA-256", source).then(resolve).catch(reject);
|
|
3264
|
+
});
|
|
3265
|
+
return Buffer.from(new Uint8Array(buffer)).toString("base64url").replace(/=+$/, "");
|
|
3266
|
+
}
|
|
3267
|
+
function canonicalJwk(jwk) {
|
|
3268
|
+
if (jwk.kty === "EC") {
|
|
3269
|
+
return { crv: jwk.crv ?? "", kty: "EC", x: jwk.x ?? "", y: jwk.y ?? "" };
|
|
3270
|
+
}
|
|
3271
|
+
if (jwk.kty === "OKP") {
|
|
3272
|
+
return { crv: jwk.crv ?? "", kty: "OKP", x: jwk.x ?? "" };
|
|
3273
|
+
}
|
|
3274
|
+
if (jwk.kty === "RSA") {
|
|
3275
|
+
return { e: jwk.e ?? "", kty: "RSA", n: jwk.n ?? "" };
|
|
3276
|
+
}
|
|
3277
|
+
return { kty: jwk.kty ?? "" };
|
|
3278
|
+
}
|
|
3279
|
+
function computeCheckoutHashFromL2(l2) {
|
|
3280
|
+
const checkoutDisclosure = l2.payload.checkout ?? l2.payload.checkout_mandate;
|
|
3281
|
+
if (!checkoutDisclosure) return null;
|
|
3282
|
+
const canonical = canonicalStringify(checkoutDisclosure);
|
|
3283
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(canonical).digest("base64url").replace(/=+$/, "");
|
|
3284
|
+
return hash;
|
|
3285
|
+
}
|
|
3286
|
+
function canonicalStringify(value) {
|
|
3287
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3288
|
+
if (Array.isArray(value)) return "[" + value.map(canonicalStringify).join(",") + "]";
|
|
3289
|
+
const entries = Object.entries(value).sort(
|
|
3290
|
+
([a], [b]) => a < b ? -1 : a > b ? 1 : 0
|
|
3291
|
+
);
|
|
3292
|
+
return "{" + entries.map(([k, v]) => JSON.stringify(k) + ":" + canonicalStringify(v)).join(",") + "}";
|
|
3293
|
+
}
|
|
3294
|
+
function checkExpiryAcross(layers, toleranceSec, nowSec, errors) {
|
|
3295
|
+
let ok = true;
|
|
3296
|
+
const names = ["L1", "L2", "L3a", "L3b"];
|
|
3297
|
+
layers.forEach((layer, idx) => {
|
|
3298
|
+
if (!layer) return;
|
|
3299
|
+
const exp = toUnixSeconds2(layer.payload.exp ?? layer.payload.expires);
|
|
3300
|
+
if (exp === void 0) return;
|
|
3301
|
+
if (nowSec > exp + toleranceSec) {
|
|
3302
|
+
errors.push(`${names[idx]} mandate expired at ${exp}`);
|
|
3303
|
+
ok = false;
|
|
3304
|
+
}
|
|
3305
|
+
});
|
|
3306
|
+
return ok;
|
|
3307
|
+
}
|
|
3308
|
+
function toUnixSeconds2(v) {
|
|
3309
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
3310
|
+
if (typeof v === "string") {
|
|
3311
|
+
const asInt = Number(v);
|
|
3312
|
+
if (Number.isFinite(asInt) && asInt > 0) {
|
|
3313
|
+
return asInt >= 1e12 ? Math.floor(asInt / 1e3) : Math.floor(asInt);
|
|
3314
|
+
}
|
|
3315
|
+
const parsed = Date.parse(v);
|
|
3316
|
+
if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
|
|
3317
|
+
}
|
|
3318
|
+
return void 0;
|
|
3319
|
+
}
|
|
3320
|
+
function coerceString7(v) {
|
|
3321
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// src/transport/commerce-pipeline.ts
|
|
3325
|
+
async function runCommercePipeline(input) {
|
|
3326
|
+
const trustSignals = [];
|
|
3327
|
+
const signatures = {};
|
|
3328
|
+
const timings = { extractMs: 0, verifyMs: 0, evalMs: 0 };
|
|
3329
|
+
const extractStart = performance.now();
|
|
3330
|
+
const purpose = resolvePurpose(input);
|
|
3331
|
+
const transactionValue = resolveTransactionValue(input);
|
|
3332
|
+
const identityClaims = collectIdentityClaims(input);
|
|
3333
|
+
const paymentToken = resolvePaymentToken(input);
|
|
3334
|
+
timings.extractMs = Math.round(performance.now() - extractStart);
|
|
3335
|
+
const verifyStart = performance.now();
|
|
3336
|
+
let hardDeny = false;
|
|
3337
|
+
if (input.vi?.verifyInput) {
|
|
3338
|
+
signatures.vi = await verifyVIChain(input.vi.verifyInput);
|
|
3339
|
+
if (!signatures.vi.ok) hardDeny = true;
|
|
3340
|
+
}
|
|
3341
|
+
if (input.ap2) {
|
|
3342
|
+
signatures.ap2 = verifyAP2Chain({
|
|
3343
|
+
triple: input.ap2.triple,
|
|
3344
|
+
clockSkewSec: input.clockSkewSec,
|
|
3345
|
+
now: input.now
|
|
3346
|
+
});
|
|
3347
|
+
if (!signatures.ap2.ok) hardDeny = true;
|
|
3348
|
+
}
|
|
3349
|
+
if (input.acp?.verifyInput) {
|
|
3350
|
+
signatures.acp = await verifyACPSignature(input.acp.verifyInput);
|
|
3351
|
+
if (!signatures.acp.ok && signatures.acp.timestampStale) hardDeny = true;
|
|
3352
|
+
if (signatures.acp.algorithm === "unsupported") {
|
|
3353
|
+
trustSignals.push("acp-signature-algorithm-unsupported");
|
|
3354
|
+
} else if (!signatures.acp.ok) {
|
|
3355
|
+
hardDeny = true;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
if (input.rfc9421) {
|
|
3359
|
+
signatures.rfc9421 = await verifyRFC9421(input.rfc9421.request, input.rfc9421.verifyOptions);
|
|
3360
|
+
if (!signatures.rfc9421.ok) hardDeny = true;
|
|
3361
|
+
}
|
|
3362
|
+
if (input.mpp) {
|
|
3363
|
+
signatures.mpp = verifyMPP({
|
|
3364
|
+
context: input.mpp.context,
|
|
3365
|
+
rawBody: input.mpp.rawBody,
|
|
3366
|
+
clockSkewSec: input.clockSkewSec,
|
|
3367
|
+
now: input.now
|
|
3368
|
+
});
|
|
3369
|
+
if (!signatures.mpp.ok) hardDeny = true;
|
|
3370
|
+
if (input.mpp.context.credential?.source) {
|
|
3371
|
+
trustSignals.push(`mpp-source-${shortSource(input.mpp.context.credential.source)}`);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
if (input.stripeWebhook) {
|
|
3375
|
+
signatures.stripeWebhook = verifyStripeWebhook(
|
|
3376
|
+
input.stripeWebhook.payload,
|
|
3377
|
+
input.stripeWebhook.signatureHeader,
|
|
3378
|
+
input.stripeWebhook.secret,
|
|
3379
|
+
{ now: input.now ? () => input.now() : void 0 }
|
|
3380
|
+
);
|
|
3381
|
+
if (!signatures.stripeWebhook.ok) {
|
|
3382
|
+
trustSignals.push("stripe-webhook-hmac-failed");
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
timings.verifyMs = Math.round(performance.now() - verifyStart);
|
|
3386
|
+
let identity;
|
|
3387
|
+
if (input.identityResolver && identityClaims.length > 0) {
|
|
3388
|
+
const bound = await bindIdentity(identityClaims, input.identityResolver);
|
|
3389
|
+
identity = {
|
|
3390
|
+
claims: identityClaims,
|
|
3391
|
+
mappedAstraSyncAgentId: bound.mappedAstraSyncAgentId,
|
|
3392
|
+
mismatchAcrossLayers: bound.mismatchAcrossLayers
|
|
3393
|
+
};
|
|
3394
|
+
if (bound.mismatchAcrossLayers) trustSignals.push("identity-mismatch-across-layers");
|
|
3395
|
+
} else if (identityClaims.length > 0) {
|
|
3396
|
+
identity = {
|
|
3397
|
+
claims: identityClaims,
|
|
3398
|
+
mappedAstraSyncAgentId: void 0,
|
|
3399
|
+
mismatchAcrossLayers: false
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
const evalStart = performance.now();
|
|
3403
|
+
const constraints = runConstraintEval(input);
|
|
3404
|
+
if (constraints && !constraints.ok) hardDeny = true;
|
|
3405
|
+
timings.evalMs = Math.round(performance.now() - evalStart);
|
|
3406
|
+
if (paymentToken?.type === "stripe-spt") trustSignals.push("stripe-spt-present");
|
|
3407
|
+
if (paymentToken?.type === "acp-vt") trustSignals.push("acp-vault-token-present");
|
|
3408
|
+
if (paymentToken?.type === "tempo-tx") trustSignals.push("tempo-transaction-present");
|
|
3409
|
+
const mppReceipt = input.mpp?.context.receipt;
|
|
3410
|
+
return {
|
|
3411
|
+
protocol: input.protocol,
|
|
3412
|
+
purpose,
|
|
3413
|
+
transactionValue,
|
|
3414
|
+
signatures,
|
|
3415
|
+
identity,
|
|
3416
|
+
paymentToken,
|
|
3417
|
+
mppMethodsOffered: input.mpp?.context.offeredMethods,
|
|
3418
|
+
constraints,
|
|
3419
|
+
receipt: mppReceipt ? {
|
|
3420
|
+
method: mppReceipt.method,
|
|
3421
|
+
reference: mppReceipt.reference,
|
|
3422
|
+
status: mppReceipt.status,
|
|
3423
|
+
timestamp: mppReceipt.timestamp
|
|
3424
|
+
} : void 0,
|
|
3425
|
+
trustSignals,
|
|
3426
|
+
timings,
|
|
3427
|
+
ok: !hardDeny
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
function resolvePurpose(input) {
|
|
3431
|
+
if (input.vi?.claims.mandateType) {
|
|
3432
|
+
return mapVIMandateToPurpose(input.vi.claims.mandateType);
|
|
3433
|
+
}
|
|
3434
|
+
if (input.ap2?.triple.payment) return "commerce.payment.execute";
|
|
3435
|
+
if (input.ap2?.triple.cart) return "commerce.checkout.confirm";
|
|
3436
|
+
if (input.ap2?.triple.intent) return "commerce.delegation.intent";
|
|
3437
|
+
if (input.ucp?.endpoint) {
|
|
3438
|
+
const [method, path] = input.ucp.endpoint.split(" ");
|
|
3439
|
+
return mapUCPRequestToPurpose(method ?? "POST", path ?? "/");
|
|
3440
|
+
}
|
|
3441
|
+
if (input.acp?.context.endpoint) {
|
|
3442
|
+
switch (input.acp.context.endpoint) {
|
|
3443
|
+
case "checkout_sessions.create":
|
|
3444
|
+
return "commerce.checkout.create";
|
|
3445
|
+
case "checkout_sessions.update":
|
|
3446
|
+
return "commerce.checkout.update";
|
|
3447
|
+
case "checkout_sessions.complete":
|
|
3448
|
+
return "commerce.payment.execute";
|
|
3449
|
+
case "checkout_sessions.cancel":
|
|
3450
|
+
return "commerce.checkout.cancel";
|
|
3451
|
+
case "delegate_payment":
|
|
3452
|
+
return "commerce.delegation.payment";
|
|
3453
|
+
default:
|
|
3454
|
+
return mapACPRequestToPurpose("POST", "/checkout_sessions");
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
if (input.rfc9421?.tag) {
|
|
3458
|
+
return mapRFC9421TagToPurpose(
|
|
3459
|
+
input.rfc9421.tag === "browse" || input.rfc9421.tag === "purchase" ? input.rfc9421.tag : void 0
|
|
3460
|
+
);
|
|
3461
|
+
}
|
|
3462
|
+
if (input.mpp?.context.credential?.challenge || input.mpp?.context.challenges?.[0]) {
|
|
3463
|
+
const challenge = input.mpp.context.credential?.challenge ?? input.mpp.context.challenges?.[0];
|
|
3464
|
+
const amount = parseFloat(String(challenge?.request?.amount ?? "NaN"));
|
|
3465
|
+
return mapMPPRequestToPurpose(
|
|
3466
|
+
challenge?.intent === "session" ? "session" : "charge",
|
|
3467
|
+
Number.isFinite(amount) ? amount : void 0
|
|
3468
|
+
);
|
|
3469
|
+
}
|
|
3470
|
+
if (input.x402?.paymentRequired) {
|
|
3471
|
+
const amt = input.x402.paymentRequired.accepts[0]?.amount;
|
|
3472
|
+
return mapX402RequestToPurpose(Number(amt));
|
|
3473
|
+
}
|
|
3474
|
+
if (input.x402?.paymentPayload) return "commerce.payment.execute";
|
|
3475
|
+
return null;
|
|
3476
|
+
}
|
|
3477
|
+
function resolveTransactionValue(input) {
|
|
3478
|
+
if (input.vi?.claims) {
|
|
3479
|
+
const v = extractVITransactionValue({
|
|
3480
|
+
constraints: input.vi.claims.constraints,
|
|
3481
|
+
l3aPaymentAmount: input.vi.claims.constraints.paymentAmount && typeof input.vi.claims.constraints.paymentAmount.max === "number" ? {
|
|
3482
|
+
amount: input.vi.claims.constraints.paymentAmount.max,
|
|
3483
|
+
currency: input.vi.claims.constraints.paymentAmount.currency
|
|
3484
|
+
} : void 0
|
|
3485
|
+
});
|
|
3486
|
+
if (v) return v;
|
|
3487
|
+
}
|
|
3488
|
+
if (input.ucp?.totals) {
|
|
3489
|
+
const v = extractUCPTransactionValue({ totals: input.ucp.totals });
|
|
3490
|
+
if (v) return v;
|
|
3491
|
+
}
|
|
3492
|
+
if (input.acp?.context.totals) {
|
|
3493
|
+
const v = extractACPTransactionValue({ totals: input.acp.context.totals });
|
|
3494
|
+
if (v) return v;
|
|
3495
|
+
}
|
|
3496
|
+
if (input.mpp?.context.credential?.challenge) {
|
|
3497
|
+
const ch = input.mpp.context.credential.challenge;
|
|
3498
|
+
const v = extractMPPTransactionValue({ method: ch.method, request: ch.request });
|
|
3499
|
+
if (v) return v;
|
|
3500
|
+
}
|
|
3501
|
+
if (input.x402?.paymentRequired) {
|
|
3502
|
+
const first = input.x402.paymentRequired.accepts[0];
|
|
3503
|
+
if (first) {
|
|
3504
|
+
const v = extractX402TransactionValue({
|
|
3505
|
+
maxAmountRequired: Number(first.amount),
|
|
3506
|
+
asset: first.asset
|
|
3507
|
+
});
|
|
3508
|
+
if (v) return v;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
return void 0;
|
|
3512
|
+
}
|
|
3513
|
+
function collectIdentityClaims(input) {
|
|
3514
|
+
const claims = [];
|
|
3515
|
+
if (input.vi?.claims.kid)
|
|
3516
|
+
claims.push({ protocol: "vi", field: "kid", value: input.vi.claims.kid });
|
|
3517
|
+
if (input.ap2?.triple) {
|
|
3518
|
+
const agentId = input.ap2.triple.intent?.agent_id ?? input.ap2.triple.cart?.agent_id ?? input.ap2.triple.payment?.agent_id;
|
|
3519
|
+
if (agentId) claims.push({ protocol: "ap2", field: "agent_id", value: agentId });
|
|
3520
|
+
}
|
|
3521
|
+
if (input.acp?.context.bearer) {
|
|
3522
|
+
claims.push({ protocol: "acp", field: "bearer", value: input.acp.context.bearer });
|
|
3523
|
+
}
|
|
3524
|
+
if (input.mpp?.context.credential?.source) {
|
|
3525
|
+
claims.push({ protocol: "mpp", field: "source", value: input.mpp.context.credential.source });
|
|
3526
|
+
}
|
|
3527
|
+
if (input.rfc9421) {
|
|
3528
|
+
}
|
|
3529
|
+
return claims;
|
|
3530
|
+
}
|
|
3531
|
+
function resolvePaymentToken(input) {
|
|
3532
|
+
if (input.acp?.context.paymentToken?.type) {
|
|
3533
|
+
return { present: true, type: input.acp.context.paymentToken.type };
|
|
3534
|
+
}
|
|
3535
|
+
const mppMethod = input.mpp?.context.credential?.challenge?.method;
|
|
3536
|
+
if (mppMethod === "tempo") return { present: true, type: "tempo-tx" };
|
|
3537
|
+
if (mppMethod === "stripe") return { present: true, type: "stripe-spt" };
|
|
3538
|
+
return void 0;
|
|
3539
|
+
}
|
|
3540
|
+
function runConstraintEval(input) {
|
|
3541
|
+
const transaction = input.transaction ?? {};
|
|
3542
|
+
const results = {};
|
|
3543
|
+
const reasons = [];
|
|
3544
|
+
let hasAny = false;
|
|
3545
|
+
if (input.vi?.claims) {
|
|
3546
|
+
const viResult = evaluateVIConstraints({
|
|
3547
|
+
constraints: input.vi.claims.constraints,
|
|
3548
|
+
transaction
|
|
3549
|
+
});
|
|
3550
|
+
for (const [k, v] of Object.entries(viResult.results)) {
|
|
3551
|
+
results[k] = v;
|
|
3552
|
+
if (!v.ok && v.reason) reasons.push(v.reason);
|
|
3553
|
+
}
|
|
3554
|
+
if (Object.keys(viResult.results).length > 0) hasAny = true;
|
|
3555
|
+
}
|
|
3556
|
+
const registered = input.registeredConstraints;
|
|
3557
|
+
if (registered?.allowedPaymentMethods) {
|
|
3558
|
+
const pm = evaluatePaymentMethodAllowlist({
|
|
3559
|
+
allowedMethods: registered.allowedPaymentMethods,
|
|
3560
|
+
requestedMethod: transaction.paymentMethod
|
|
3561
|
+
});
|
|
3562
|
+
results.paymentMethod = pm;
|
|
3563
|
+
if (!pm.ok && pm.reason) reasons.push(pm.reason);
|
|
3564
|
+
hasAny = true;
|
|
3565
|
+
}
|
|
3566
|
+
if (registered?.spendingLimit) {
|
|
3567
|
+
const sp = evaluateSpendingLimit({
|
|
3568
|
+
limit: registered.spendingLimit,
|
|
3569
|
+
requested: { amount: transaction.amount, currency: transaction.currency }
|
|
3570
|
+
});
|
|
3571
|
+
results.spendingLimit = sp;
|
|
3572
|
+
if (!sp.ok && sp.reason) reasons.push(sp.reason);
|
|
3573
|
+
hasAny = true;
|
|
3574
|
+
}
|
|
3575
|
+
if (!hasAny) return void 0;
|
|
3576
|
+
return { ok: reasons.length === 0, results, reasons };
|
|
3577
|
+
}
|
|
3578
|
+
function shortSource(source) {
|
|
3579
|
+
return source.replace(/^did:[a-z0-9]+:/, "").slice(0, 16);
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
// src/transport/extractor-registry.ts
|
|
3583
|
+
var registry = /* @__PURE__ */ new Map();
|
|
3584
|
+
function registerTransportExtractor(extractor) {
|
|
3585
|
+
if (!extractor || typeof extractor.name !== "string" || extractor.name.length === 0) {
|
|
3586
|
+
throw new Error("registerTransportExtractor: extractor must have a non-empty name");
|
|
3587
|
+
}
|
|
3588
|
+
registry.set(extractor.name, extractor);
|
|
3589
|
+
}
|
|
3590
|
+
function getTransportExtractors() {
|
|
3591
|
+
return Array.from(registry.values());
|
|
3592
|
+
}
|
|
3593
|
+
function getTransportExtractor(name) {
|
|
3594
|
+
return registry.get(name);
|
|
3595
|
+
}
|
|
3596
|
+
function clearTransportExtractors() {
|
|
3597
|
+
registry.clear();
|
|
3598
|
+
}
|
|
3599
|
+
async function runMatchingExtractors(request) {
|
|
3600
|
+
const out = {};
|
|
3601
|
+
for (const extractor of registry.values()) {
|
|
3602
|
+
if (!extractor.match(request)) continue;
|
|
3603
|
+
const result = await extractor.extract(request);
|
|
3604
|
+
if (result !== null && result !== void 0) out[extractor.name] = result;
|
|
3605
|
+
}
|
|
3606
|
+
return out;
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
// src/transport/registry/visa.ts
|
|
3610
|
+
var import_jose = require("jose");
|
|
3611
|
+
var DEFAULT_VISA_JWKS_URL = "https://mcp.visa.com/.well-known/jwks";
|
|
3612
|
+
function createVisaRegistry(options = {}) {
|
|
3613
|
+
const url = new URL(options.jwksUrl ?? DEFAULT_VISA_JWKS_URL);
|
|
3614
|
+
const jwks = (0, import_jose.createRemoteJWKSet)(url, {
|
|
3615
|
+
cacheMaxAge: options.cacheMaxAge,
|
|
3616
|
+
cooldownDuration: options.cooldownDuration
|
|
3617
|
+
});
|
|
3618
|
+
return {
|
|
3619
|
+
name: "visa",
|
|
3620
|
+
async resolve(kid, context) {
|
|
3621
|
+
if (!kid) return null;
|
|
3622
|
+
try {
|
|
3623
|
+
const key = await jwks({
|
|
3624
|
+
kid,
|
|
3625
|
+
alg: context?.algorithm ?? "ES256",
|
|
3626
|
+
typ: "JWT"
|
|
3627
|
+
});
|
|
3628
|
+
return exportJwkFromKeyLike(key);
|
|
3629
|
+
} catch {
|
|
3630
|
+
return null;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
async function exportJwkFromKeyLike(keyLike) {
|
|
3636
|
+
if (!keyLike) return null;
|
|
3637
|
+
if (typeof keyLike === "object" && "kty" in keyLike) {
|
|
3638
|
+
return keyLike;
|
|
3639
|
+
}
|
|
3640
|
+
const { exportJWK } = await import("jose");
|
|
3641
|
+
try {
|
|
3642
|
+
return await exportJWK(keyLike);
|
|
3643
|
+
} catch {
|
|
3644
|
+
return null;
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
// src/transport/registry/mastercard.ts
|
|
3649
|
+
function createMastercardRegistry(options = {}) {
|
|
3650
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3651
|
+
const ttlSec = options.cacheTtlSec ?? 3600;
|
|
3652
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
3653
|
+
let warned = false;
|
|
3654
|
+
return {
|
|
3655
|
+
name: "mastercard",
|
|
3656
|
+
async resolve(kid) {
|
|
3657
|
+
if (!kid) return null;
|
|
3658
|
+
if (!options.registryUrl) {
|
|
3659
|
+
if (!warned && !options.silent) {
|
|
3660
|
+
warned = true;
|
|
3661
|
+
console.warn(
|
|
3662
|
+
"[mastercard-registry] registryUrl not configured \u2014 key resolution disabled. Kid lookups will return null until a partnership registry is supplied."
|
|
3663
|
+
);
|
|
3664
|
+
}
|
|
3665
|
+
return null;
|
|
3666
|
+
}
|
|
3667
|
+
const cached = cache.get(kid);
|
|
3668
|
+
if (cached && cached.expiresAt > Date.now()) return cached.jwk;
|
|
3669
|
+
try {
|
|
3670
|
+
const res = await fetchFn(options.registryUrl);
|
|
3671
|
+
if (!res.ok) return null;
|
|
3672
|
+
const body = await res.json();
|
|
3673
|
+
const keys = body.keys ?? [];
|
|
3674
|
+
for (const k of keys) {
|
|
3675
|
+
if (k.kid === kid) {
|
|
3676
|
+
cache.set(kid, { jwk: k, expiresAt: Date.now() + ttlSec * 1e3 });
|
|
3677
|
+
return k;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
return null;
|
|
3681
|
+
} catch {
|
|
3682
|
+
return null;
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// src/transport/registry/web-bot-auth.ts
|
|
3689
|
+
var DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
3690
|
+
function createWebBotAuthRegistry(options = {}) {
|
|
3691
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3692
|
+
const ttlSec = options.cacheTtlSec ?? 3600;
|
|
3693
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
3694
|
+
return {
|
|
3695
|
+
name: "web-bot-auth",
|
|
3696
|
+
async resolve(kid, context) {
|
|
3697
|
+
if (!kid) return null;
|
|
3698
|
+
const directoryUrl = resolveDirectoryUrl(options.directoryUrl, context?.origin);
|
|
3699
|
+
if (!directoryUrl) return null;
|
|
3700
|
+
const cached = cache.get(directoryUrl);
|
|
3701
|
+
const now = Date.now();
|
|
3702
|
+
if (cached && cached.expiresAt > now) {
|
|
3703
|
+
return findKeyByKid(cached.keys, kid);
|
|
3704
|
+
}
|
|
3705
|
+
try {
|
|
3706
|
+
const res = await fetchFn(directoryUrl);
|
|
3707
|
+
if (!res.ok) return null;
|
|
3708
|
+
const body = await res.json();
|
|
3709
|
+
const keys = body.keys ?? [];
|
|
3710
|
+
cache.set(directoryUrl, { keys, expiresAt: now + ttlSec * 1e3 });
|
|
3711
|
+
return findKeyByKid(keys, kid);
|
|
3712
|
+
} catch {
|
|
3713
|
+
return null;
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
function resolveDirectoryUrl(explicit, origin) {
|
|
3719
|
+
if (explicit) return explicit;
|
|
3720
|
+
if (!origin) return null;
|
|
3721
|
+
try {
|
|
3722
|
+
const url = new URL(origin);
|
|
3723
|
+
return `${url.origin}${DIRECTORY_PATH}`;
|
|
3724
|
+
} catch {
|
|
3725
|
+
return null;
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
function findKeyByKid(keys, kid) {
|
|
3729
|
+
for (const k of keys) {
|
|
3730
|
+
if (k.kid === kid) return k;
|
|
3731
|
+
}
|
|
3732
|
+
return null;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
1225
3735
|
// src/transport/index.ts
|
|
1226
3736
|
function detectProtocol(context) {
|
|
1227
3737
|
if (context.metadata && typeof context.metadata === "object") {
|