@tanakayuto/intmax402-cli 0.2.0 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +121 -70
  2. package/package.json +8 -4
package/dist/index.js CHANGED
@@ -1,106 +1,157 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
- const crypto_1 = require("crypto");
7
+ const minimist_1 = __importDefault(require("minimist"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ethers_1 = require("ethers");
5
10
  const intmax402_core_1 = require("@tanakayuto/intmax402-core");
6
- const intmax402_client_1 = require("@tanakayuto/intmax402-client");
7
- const [, , command, ...args] = process.argv;
11
+ const argv = (0, minimist_1.default)(process.argv.slice(2), {
12
+ string: ["mode"],
13
+ boolean: ["help"],
14
+ alias: { h: "help" },
15
+ default: { mode: "identity" },
16
+ });
17
+ const command = argv._[0];
8
18
  async function main() {
19
+ if (argv.help || !command) {
20
+ printHelp();
21
+ return;
22
+ }
9
23
  switch (command) {
10
24
  case "test":
11
- await testCommand(args[0]);
25
+ await testCommand(argv._[1], argv.mode);
12
26
  break;
13
27
  case "keygen":
14
28
  keygenCommand();
15
29
  break;
16
- case "verify":
17
- verifyCommand(args[0]);
18
- break;
19
30
  default:
20
- printUsage();
31
+ console.error(chalk_1.default.red(`Unknown command: ${command}`));
32
+ printHelp();
33
+ process.exit(1);
21
34
  }
22
35
  }
23
- async function testCommand(url) {
36
+ async function testCommand(url, mode = "identity") {
24
37
  if (!url) {
25
- console.error("Usage: intmax402 test <url>");
38
+ console.error(chalk_1.default.red("Usage: intmax402 test <url> [--mode identity|payment]"));
39
+ process.exit(1);
40
+ }
41
+ // Validate URL: only http:// and https:// are allowed (SSRF prevention)
42
+ try {
43
+ const parsed = new URL(url);
44
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
45
+ console.error(chalk_1.default.red("Error: Only http:// and https:// URLs are supported"));
46
+ process.exit(1);
47
+ }
48
+ }
49
+ catch {
50
+ console.error(chalk_1.default.red(`Error: Invalid URL: ${url}`));
51
+ process.exit(1);
52
+ }
53
+ console.log(`Testing: ${chalk_1.default.cyan(url)}\n`);
54
+ const startTime = Date.now();
55
+ // Generate a random wallet for testing
56
+ const wallet = ethers_1.ethers.Wallet.createRandom();
57
+ const privateKey = wallet.privateKey;
58
+ const address = wallet.address;
59
+ // Step 1: Initial GET → expect 401
60
+ process.stdout.write(` ${chalk_1.default.yellow("①")} GET ${new URL(url).pathname} → `);
61
+ let res1;
62
+ try {
63
+ res1 = await fetch(url);
64
+ }
65
+ catch (err) {
66
+ console.log(chalk_1.default.red(`error: ${err.message}`));
26
67
  process.exit(1);
27
68
  }
28
- console.log(`Testing INTMAX402 flow against ${url}...\n`);
29
- // Step 1: Initial request
30
- console.log("Step 1: Sending initial request...");
31
- const response = await fetch(url);
32
- console.log(` Status: ${response.status}`);
33
- if (response.status !== 401 && response.status !== 402) {
34
- console.log(" No 402 challenge received. Endpoint may not be protected.");
69
+ const statusColor = res1.status === 401 || res1.status === 402 ? chalk_1.default.yellow : chalk_1.default.green;
70
+ console.log(statusColor(`${res1.status}`));
71
+ if (res1.status !== 401 && res1.status !== 402) {
72
+ console.log(chalk_1.default.yellow(" (No 402/401 challenge received. Endpoint may not be protected.)"));
35
73
  return;
36
74
  }
37
- const wwwAuth = response.headers.get("www-authenticate");
75
+ // Step 2: Parse nonce
76
+ const wwwAuth = res1.headers.get("www-authenticate");
38
77
  if (!wwwAuth) {
39
- console.error(" No WWW-Authenticate header found.");
40
- return;
78
+ console.error(chalk_1.default.red(" No WWW-Authenticate header found."));
79
+ process.exit(1);
41
80
  }
42
- console.log(` WWW-Authenticate: ${wwwAuth}`);
43
81
  const challenge = (0, intmax402_core_1.parseWWWAuthenticate)(wwwAuth);
44
82
  if (!challenge) {
45
- console.error(" Failed to parse challenge.");
46
- return;
83
+ console.error(chalk_1.default.red(" Failed to parse WWW-Authenticate challenge."));
84
+ process.exit(1);
47
85
  }
48
- console.log(` Mode: ${challenge.mode}`);
49
- console.log(` Nonce: ${challenge.nonce}`);
50
- // Step 2: Generate key and sign
51
- console.log("\nStep 2: Generating key and signing...");
52
- const privateKey = "0x" + (0, crypto_1.randomBytes)(32).toString("hex");
53
- const client = new intmax402_client_1.INTMAX402Client({ privateKey });
54
- await client.init();
55
- console.log(` Address: ${client.getAddress()}`);
56
- const signature = await client.sign(challenge.nonce);
57
- console.log(` Signature: ${signature.slice(0, 20)}...`);
58
- // Step 3: Retry with credentials
59
- console.log("\nStep 3: Retrying with credentials...");
60
- const retryResponse = await client.fetch(url);
61
- console.log(` Status: ${retryResponse.status}`);
62
- if (retryResponse.ok) {
63
- const body = await retryResponse.text();
64
- console.log(` Response: ${body.slice(0, 200)}`);
86
+ const nonceShort = challenge.nonce.slice(0, 16) + "...";
87
+ console.log(` ${chalk_1.default.yellow("②")} nonce: ${chalk_1.default.dim(nonceShort)} (${challenge.realm})`);
88
+ // Step 3: Sign
89
+ console.log(` ${chalk_1.default.yellow("③")} Signing with wallet: ${chalk_1.default.dim(address.slice(0, 8) + "...")}`);
90
+ let authHeader;
91
+ if (mode === "payment") {
92
+ // Payment mode: include a mock txHash
93
+ const mockTxHash = "0x" + Buffer.alloc(32).fill(0xab).toString("hex");
94
+ // Sign only the nonce (same format as server expects)
95
+ const signature = await wallet.signMessage(challenge.nonce);
96
+ authHeader = `INTMAX402 address="${address}",nonce="${challenge.nonce}",signature="${signature}",txHash="${mockTxHash}"`;
65
97
  }
66
98
  else {
67
- console.log(` Response: ${await retryResponse.text()}`);
99
+ // Identity mode: sign just the nonce
100
+ const signature = await wallet.signMessage(challenge.nonce);
101
+ authHeader = `INTMAX402 address="${address}",nonce="${challenge.nonce}",signature="${signature}"`;
68
102
  }
69
- }
70
- function keygenCommand() {
71
- const privateKey = "0x" + (0, crypto_1.randomBytes)(32).toString("hex");
72
- const client = new intmax402_client_1.INTMAX402Client({ privateKey });
73
- console.log("Generated test keypair:");
74
- console.log(` Private Key: ${privateKey}`);
75
- console.log(` Address: ${client.getAddress()}`);
76
- }
77
- function verifyCommand(header) {
78
- if (!header) {
79
- console.error("Usage: intmax402 verify <authorization-header>");
80
- process.exit(1);
103
+ // Step 4: Retry with Authorization
104
+ process.stdout.write(` ${chalk_1.default.yellow("④")} GET ${new URL(url).pathname} + Authorization → `);
105
+ let res2;
106
+ try {
107
+ res2 = await fetch(url, {
108
+ headers: { Authorization: authHeader },
109
+ });
81
110
  }
82
- const credential = (0, intmax402_core_1.parseAuthorization)(header);
83
- if (!credential) {
84
- console.error("Failed to parse Authorization header.");
111
+ catch (err) {
112
+ console.log(chalk_1.default.red(`error: ${err.message}`));
85
113
  process.exit(1);
86
114
  }
87
- console.log("Parsed credential:");
88
- console.log(` Address: ${credential.address}`);
89
- console.log(` Nonce: ${credential.nonce}`);
90
- console.log(` Signature: ${credential.signature.slice(0, 20)}...`);
91
- if (credential.txHash) {
92
- console.log(` TX Hash: ${credential.txHash}`);
115
+ const elapsed = Date.now() - startTime;
116
+ if (res2.ok) {
117
+ console.log(`${chalk_1.default.green(String(res2.status))} ${chalk_1.default.green("✅")}`);
93
118
  }
119
+ else {
120
+ console.log(`${chalk_1.default.red(String(res2.status))} ${chalk_1.default.red("❌")}`);
121
+ const body = await res2.text().catch(() => "");
122
+ if (body)
123
+ console.log(chalk_1.default.dim(` ${body.slice(0, 200)}`));
124
+ }
125
+ console.log();
126
+ console.log(`${chalk_1.default.bold("Address:")} ${address}`);
127
+ console.log(`${chalk_1.default.bold("Time:")} ${elapsed}ms`);
128
+ if (!res2.ok) {
129
+ process.exit(1);
130
+ }
131
+ }
132
+ function keygenCommand() {
133
+ const wallet = ethers_1.ethers.Wallet.createRandom();
134
+ console.log("\nGenerated wallet:");
135
+ console.log(` ${chalk_1.default.bold("Address:")} ${chalk_1.default.green(wallet.address)}`);
136
+ console.log(` ${chalk_1.default.bold("Private Key:")} ${chalk_1.default.yellow(wallet.privateKey)}`);
137
+ console.log();
138
+ console.log(chalk_1.default.red("⚠ Use for testing only. Never use in production."));
94
139
  }
95
- function printUsage() {
96
- console.log("intmax402 CLI - HTTP 402 Payment Gate Test Tool");
97
- console.log("");
98
- console.log("Usage:");
99
- console.log(" intmax402 test <url> Test 402 flow against a URL");
100
- console.log(" intmax402 keygen Generate a test ETH keypair");
101
- console.log(" intmax402 verify <header> Parse an Authorization header");
140
+ function printHelp() {
141
+ console.log(`${chalk_1.default.bold("intmax402")} - HTTP 402 Payment Gate CLI Tool`);
142
+ console.log();
143
+ console.log(chalk_1.default.bold("Usage:"));
144
+ console.log(` intmax402 ${chalk_1.default.cyan("test")} <url> [--mode identity|payment]`);
145
+ console.log(` Test 402 flow against a URL`);
146
+ console.log(` intmax402 ${chalk_1.default.cyan("keygen")} Generate a test Ethereum wallet`);
147
+ console.log(` intmax402 ${chalk_1.default.cyan("--help")} Show this help message`);
148
+ console.log();
149
+ console.log(chalk_1.default.bold("Examples:"));
150
+ console.log(` intmax402 test http://localhost:3760/identity`);
151
+ console.log(` intmax402 test http://localhost:3760/paid --mode payment`);
152
+ console.log(` intmax402 keygen`);
102
153
  }
103
154
  main().catch((err) => {
104
- console.error("Error:", err.message);
155
+ console.error(chalk_1.default.red("Error:"), err.message);
105
156
  process.exit(1);
106
157
  });
package/package.json CHANGED
@@ -1,17 +1,21 @@
1
1
  {
2
2
  "name": "@tanakayuto/intmax402-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "intmax402": "dist/index.js"
7
7
  },
8
8
  "dependencies": {
9
9
  "@tanakayuto/intmax402-core": "0.2.0",
10
- "@tanakayuto/intmax402-client": "0.2.0"
10
+ "chalk": "^4.1.2",
11
+ "ethers": "^6.16.0",
12
+ "minimist": "^1.2.8"
11
13
  },
12
14
  "devDependencies": {
13
- "typescript": "^5.4.0",
14
- "@types/node": "^20.0.0"
15
+ "@types/chalk": "^2.2.4",
16
+ "@types/minimist": "^1.2.5",
17
+ "@types/node": "^20.0.0",
18
+ "typescript": "^5.4.0"
15
19
  },
16
20
  "files": [
17
21
  "dist",