@tanakayuto/intmax402-fetch 0.1.0 → 0.1.1
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/README.md +5 -0
- package/dist/handler.js +61 -64
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -119,3 +119,8 @@ interface Intmax402Context {
|
|
|
119
119
|
## ライセンス
|
|
120
120
|
|
|
121
121
|
MIT
|
|
122
|
+
|
|
123
|
+
## ⚠️ IP Binding Warning
|
|
124
|
+
When using `bindIp: true`, ensure your server is behind a trusted reverse proxy (Nginx, Cloudflare, etc.).
|
|
125
|
+
Direct exposure allows attackers to forge `X-Forwarded-For` headers, bypassing IP binding.
|
|
126
|
+
Default (`bindIp: false`) is recommended for AI agent use cases.
|
package/dist/handler.js
CHANGED
|
@@ -2,81 +2,78 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.handleIntmax402 = handleIntmax402;
|
|
4
4
|
const intmax402_core_1 = require("@tanakayuto/intmax402-core");
|
|
5
|
-
const crypto_1 = require("@tanakayuto/intmax402-express/
|
|
6
|
-
const verify_payment_1 = require("@tanakayuto/intmax402-express/
|
|
5
|
+
const crypto_1 = require("@tanakayuto/intmax402-express/crypto");
|
|
6
|
+
const verify_payment_1 = require("@tanakayuto/intmax402-express/verify-payment");
|
|
7
7
|
/**
|
|
8
8
|
* Handle INTMAX402 authentication for any Web Standard fetch-based framework.
|
|
9
9
|
* Returns null if auth passes (proceed to next handler).
|
|
10
10
|
* Returns Response if auth fails (return this response to client).
|
|
11
11
|
*/
|
|
12
12
|
async function handleIntmax402(request, config) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const credential = (0, intmax402_core_1.parseAuthorization)(authHeader);
|
|
37
|
-
if (!credential) {
|
|
38
|
-
return { response: errorResponse(401, "Invalid authorization header"), context: null };
|
|
39
|
-
}
|
|
40
|
-
if (!(0, intmax402_core_1.verifyNonce)(credential.nonce, config.secret, ip, url.pathname, config.bindIp ?? false)) {
|
|
41
|
-
return { response: errorResponse(401, "Invalid or expired nonce"), context: null };
|
|
42
|
-
}
|
|
43
|
-
if (config.allowList && config.allowList.length > 0) {
|
|
44
|
-
if (!config.allowList.includes(credential.address.toLowerCase())) {
|
|
45
|
-
return { response: errorResponse(403, "Address not in allow list"), context: null };
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
const authHeader = request.headers.get("authorization");
|
|
16
|
+
const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()
|
|
17
|
+
?? request.headers.get("x-real-ip")
|
|
18
|
+
?? "unknown";
|
|
19
|
+
if (!authHeader) {
|
|
20
|
+
const nonce = (0, intmax402_core_1.generateNonce)(config.secret, ip, url.pathname, config.bindIp ?? false);
|
|
21
|
+
const statusCode = config.mode === "payment" ? 402 : 401;
|
|
22
|
+
return {
|
|
23
|
+
response: new Response(JSON.stringify({
|
|
24
|
+
error: config.mode === "payment" ? "Payment Required" : "Unauthorized",
|
|
25
|
+
protocol: "INTMAX402",
|
|
26
|
+
mode: config.mode,
|
|
27
|
+
}), {
|
|
28
|
+
status: statusCode,
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
"WWW-Authenticate": (0, intmax402_core_1.buildWWWAuthenticate)(nonce, config),
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
context: null,
|
|
35
|
+
};
|
|
46
36
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!credential.txHash) {
|
|
54
|
-
return { response: errorResponse(402, "Payment transaction hash required"), context: null };
|
|
37
|
+
const credential = (0, intmax402_core_1.parseAuthorization)(authHeader);
|
|
38
|
+
if (!credential) {
|
|
39
|
+
return { response: errorResponse(401, "Invalid authorization header"), context: null };
|
|
40
|
+
}
|
|
41
|
+
if (!(0, intmax402_core_1.verifyNonce)(credential.nonce, config.secret, ip, url.pathname, config.bindIp ?? false)) {
|
|
42
|
+
return { response: errorResponse(401, "Invalid or expired nonce"), context: null };
|
|
55
43
|
}
|
|
56
|
-
if (
|
|
57
|
-
|
|
44
|
+
if (config.allowList && config.allowList.length > 0) {
|
|
45
|
+
if (!config.allowList.includes(credential.address.toLowerCase())) {
|
|
46
|
+
return { response: errorResponse(403, "Address not in allow list"), context: null };
|
|
47
|
+
}
|
|
58
48
|
}
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
61
|
-
return { response: errorResponse(
|
|
49
|
+
const isValidSig = (0, crypto_1.verifySignature)(credential.signature, credential.nonce, credential.address);
|
|
50
|
+
if (!isValidSig) {
|
|
51
|
+
return { response: errorResponse(401, "Invalid signature"), context: null };
|
|
62
52
|
}
|
|
53
|
+
if (config.mode === "payment") {
|
|
54
|
+
if (!credential.txHash) {
|
|
55
|
+
return { response: errorResponse(402, "Payment transaction hash required"), context: null };
|
|
56
|
+
}
|
|
57
|
+
if (!config.serverAddress || !config.amount) {
|
|
58
|
+
return { response: errorResponse(500, "Server misconfigured"), context: null };
|
|
59
|
+
}
|
|
60
|
+
const paymentResult = await (0, verify_payment_1.verifyPayment)(credential.txHash, config.amount, config.serverAddress);
|
|
61
|
+
if (!paymentResult.valid) {
|
|
62
|
+
return { response: errorResponse(402, paymentResult.error ?? "Payment verification failed"), context: null };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
response: null,
|
|
67
|
+
context: { address: credential.address, verified: true, txHash: credential.txHash },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
// URL parse error or unexpected error
|
|
72
|
+
return {
|
|
73
|
+
response: errorResponse(400, "Bad Request"),
|
|
74
|
+
context: null,
|
|
75
|
+
};
|
|
63
76
|
}
|
|
64
|
-
return {
|
|
65
|
-
response: null,
|
|
66
|
-
context: { address: credential.address, verified: true, txHash: credential.txHash },
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function buildWWWAuthenticate(nonce, config) {
|
|
70
|
-
let header = `INTMAX402 realm="intmax402", nonce="${nonce}", mode="${config.mode}"`;
|
|
71
|
-
if (config.serverAddress)
|
|
72
|
-
header += `, serverAddress="${config.serverAddress}"`;
|
|
73
|
-
if (config.amount)
|
|
74
|
-
header += `, amount="${config.amount}"`;
|
|
75
|
-
if (config.tokenAddress)
|
|
76
|
-
header += `, tokenAddress="${config.tokenAddress}"`;
|
|
77
|
-
if (config.chainId)
|
|
78
|
-
header += `, chainId="${config.chainId}"`;
|
|
79
|
-
return header;
|
|
80
77
|
}
|
|
81
78
|
function errorResponse(status, message) {
|
|
82
79
|
return new Response(JSON.stringify({ error: message }), {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanakayuto/intmax402-fetch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
],
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"@tanakayuto/intmax402-
|
|
30
|
-
"
|
|
28
|
+
"ethers": "^6.0.0",
|
|
29
|
+
"@tanakayuto/intmax402-core": "0.2.3",
|
|
30
|
+
"@tanakayuto/intmax402-express": "0.2.3"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"typescript": "^5.4.0",
|