@tanakayuto/intmax402-fetch 0.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/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @tanakayuto/intmax402-fetch
2
+
3
+ Web標準の `Request`/`Response` で動く INTMAX402 認証アダプター。
4
+
5
+ Hono、Next.js、Cloudflare Workers など、すべての fetch ベースフレームワークで使える共通基盤です。
6
+
7
+ ## インストール
8
+
9
+ ```bash
10
+ npm install @tanakayuto/intmax402-fetch
11
+ ```
12
+
13
+ ## 使い方
14
+
15
+ ### Cloudflare Workers
16
+
17
+ ```typescript
18
+ import { handleIntmax402 } from "@tanakayuto/intmax402-fetch"
19
+
20
+ const config = {
21
+ secret: process.env.INTMAX402_SECRET!,
22
+ mode: "signature" as const,
23
+ }
24
+
25
+ export default {
26
+ async fetch(request: Request): Promise<Response> {
27
+ const result = await handleIntmax402(request, config)
28
+ if (result.response) {
29
+ // 認証失敗 → エラーレスポンスを返す
30
+ return result.response
31
+ }
32
+ // 認証成功 → result.context にアドレス等が入る
33
+ const { address } = result.context
34
+ return new Response(`Hello, ${address}!`)
35
+ },
36
+ }
37
+ ```
38
+
39
+ ### Hono
40
+
41
+ ```typescript
42
+ import { Hono } from "hono"
43
+ import { handleIntmax402 } from "@tanakayuto/intmax402-fetch"
44
+
45
+ const app = new Hono()
46
+
47
+ const config = {
48
+ secret: process.env.INTMAX402_SECRET!,
49
+ mode: "signature" as const,
50
+ }
51
+
52
+ app.use("/protected/*", async (c, next) => {
53
+ const result = await handleIntmax402(c.req.raw, config)
54
+ if (result.response) {
55
+ return result.response
56
+ }
57
+ c.set("intmax402", result.context)
58
+ await next()
59
+ })
60
+
61
+ app.get("/protected/resource", (c) => {
62
+ const ctx = c.get("intmax402")
63
+ return c.json({ message: "Access granted", address: ctx.address })
64
+ })
65
+ ```
66
+
67
+ ### Next.js App Router (Middleware)
68
+
69
+ ```typescript
70
+ // middleware.ts
71
+ import { NextRequest, NextResponse } from "next/server"
72
+ import { handleIntmax402 } from "@tanakayuto/intmax402-fetch"
73
+
74
+ const config = {
75
+ secret: process.env.INTMAX402_SECRET!,
76
+ mode: "payment" as const,
77
+ serverAddress: process.env.SERVER_ADDRESS!,
78
+ amount: process.env.PAYMENT_AMOUNT!,
79
+ }
80
+
81
+ export async function middleware(request: NextRequest) {
82
+ const result = await handleIntmax402(request, config)
83
+ if (result.response) {
84
+ return result.response
85
+ }
86
+ // 認証成功 → ヘッダーにアドレスを追加して続行
87
+ const requestHeaders = new Headers(request.headers)
88
+ requestHeaders.set("x-intmax402-address", result.context.address)
89
+ return NextResponse.next({ request: { headers: requestHeaders } })
90
+ }
91
+
92
+ export const config = {
93
+ matcher: "/api/protected/:path*",
94
+ }
95
+ ```
96
+
97
+ ## API
98
+
99
+ ### `handleIntmax402(request, config)`
100
+
101
+ **パラメータ:**
102
+ - `request: Request` — Web標準の Request オブジェクト
103
+ - `config: INTMAX402Config` — 認証設定
104
+
105
+ **戻り値:**
106
+ - 認証失敗時: `{ response: Response, context: null }` — このレスポンスをクライアントに返す
107
+ - 認証成功時: `{ response: null, context: Intmax402Context }` — 次のハンドラに進む
108
+
109
+ ### `Intmax402Context`
110
+
111
+ ```typescript
112
+ interface Intmax402Context {
113
+ address: string // 認証済みウォレットアドレス
114
+ verified: boolean // 常に true
115
+ txHash?: string // payment モード時のトランザクションハッシュ
116
+ }
117
+ ```
118
+
119
+ ## ライセンス
120
+
121
+ MIT
@@ -0,0 +1,18 @@
1
+ import { INTMAX402Config } from "@tanakayuto/intmax402-core";
2
+ export interface Intmax402Context {
3
+ address: string;
4
+ verified: boolean;
5
+ txHash?: string;
6
+ }
7
+ /**
8
+ * Handle INTMAX402 authentication for any Web Standard fetch-based framework.
9
+ * Returns null if auth passes (proceed to next handler).
10
+ * Returns Response if auth fails (return this response to client).
11
+ */
12
+ export declare function handleIntmax402(request: Request, config: INTMAX402Config): Promise<{
13
+ response: Response;
14
+ context: null;
15
+ } | {
16
+ response: null;
17
+ context: Intmax402Context;
18
+ }>;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleIntmax402 = handleIntmax402;
4
+ const intmax402_core_1 = require("@tanakayuto/intmax402-core");
5
+ const crypto_1 = require("@tanakayuto/intmax402-express/dist/crypto");
6
+ const verify_payment_1 = require("@tanakayuto/intmax402-express/dist/verify-payment");
7
+ /**
8
+ * Handle INTMAX402 authentication for any Web Standard fetch-based framework.
9
+ * Returns null if auth passes (proceed to next handler).
10
+ * Returns Response if auth fails (return this response to client).
11
+ */
12
+ async function handleIntmax402(request, config) {
13
+ const url = new URL(request.url);
14
+ const authHeader = request.headers.get("authorization");
15
+ const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()
16
+ ?? request.headers.get("x-real-ip")
17
+ ?? "unknown";
18
+ if (!authHeader) {
19
+ const nonce = (0, intmax402_core_1.generateNonce)(config.secret, ip, url.pathname, config.bindIp ?? false);
20
+ const statusCode = config.mode === "payment" ? 402 : 401;
21
+ return {
22
+ response: new Response(JSON.stringify({
23
+ error: config.mode === "payment" ? "Payment Required" : "Unauthorized",
24
+ protocol: "INTMAX402",
25
+ mode: config.mode,
26
+ }), {
27
+ status: statusCode,
28
+ headers: {
29
+ "Content-Type": "application/json",
30
+ "WWW-Authenticate": buildWWWAuthenticate(nonce, config),
31
+ },
32
+ }),
33
+ context: null,
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 };
46
+ }
47
+ }
48
+ const isValidSig = (0, crypto_1.verifySignature)(credential.signature, credential.nonce, credential.address);
49
+ if (!isValidSig) {
50
+ return { response: errorResponse(401, "Invalid signature"), context: null };
51
+ }
52
+ if (config.mode === "payment") {
53
+ if (!credential.txHash) {
54
+ return { response: errorResponse(402, "Payment transaction hash required"), context: null };
55
+ }
56
+ if (!config.serverAddress || !config.amount) {
57
+ return { response: errorResponse(500, "Server misconfigured"), context: null };
58
+ }
59
+ const paymentResult = await (0, verify_payment_1.verifyPayment)(credential.txHash, config.amount, config.serverAddress);
60
+ if (!paymentResult.valid) {
61
+ return { response: errorResponse(402, paymentResult.error ?? "Payment verification failed"), context: null };
62
+ }
63
+ }
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
+ }
81
+ function errorResponse(status, message) {
82
+ return new Response(JSON.stringify({ error: message }), {
83
+ status,
84
+ headers: { "Content-Type": "application/json" },
85
+ });
86
+ }
@@ -0,0 +1,2 @@
1
+ export { handleIntmax402, type Intmax402Context } from "./handler";
2
+ export type { INTMAX402Config } from "@tanakayuto/intmax402-core";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleIntmax402 = void 0;
4
+ var handler_1 = require("./handler");
5
+ Object.defineProperty(exports, "handleIntmax402", { enumerable: true, get: function () { return handler_1.handleIntmax402; } });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@tanakayuto/intmax402-fetch",
3
+ "version": "0.1.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "files": [
7
+ "dist",
8
+ "README.md"
9
+ ],
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/zaq2989/intmax402"
16
+ },
17
+ "keywords": [
18
+ "intmax",
19
+ "http-402",
20
+ "payment",
21
+ "ai-agent",
22
+ "fetch",
23
+ "web-standard",
24
+ "cloudflare-workers"
25
+ ],
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@tanakayuto/intmax402-core": "0.2.2",
29
+ "@tanakayuto/intmax402-express": "0.2.2",
30
+ "ethers": "^6.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.4.0",
34
+ "@types/node": "^20.0.0"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "clean": "rm -rf dist"
39
+ }
40
+ }