api-response-manager 2.3.0 → 2.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.
- package/LICENSE +21 -0
- package/bin/arm.js +83 -0
- package/commands/health.js +114 -0
- package/commands/ipBlacklist.js +121 -0
- package/commands/ipWhitelist.js +121 -0
- package/commands/rateLimit.js +69 -0
- package/package.json +14 -3
- package/utils/api.js +17 -0
- package/CLI_UPDATES.md +0 -196
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vijay Singh Purohit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bin/arm.js
CHANGED
|
@@ -17,6 +17,10 @@ const webhookCommand = require('../commands/webhook');
|
|
|
17
17
|
const projectCommand = require('../commands/project');
|
|
18
18
|
const logsCommand = require('../commands/logs');
|
|
19
19
|
const configCommand = require('../commands/config');
|
|
20
|
+
const ipWhitelistCommand = require('../commands/ipWhitelist');
|
|
21
|
+
const ipBlacklistCommand = require('../commands/ipBlacklist');
|
|
22
|
+
const rateLimitCommand = require('../commands/rateLimit');
|
|
23
|
+
const healthCommand = require('../commands/health');
|
|
20
24
|
|
|
21
25
|
// CLI setup
|
|
22
26
|
program
|
|
@@ -127,6 +131,85 @@ program
|
|
|
127
131
|
.option('--tls', 'Enable TLS for ingress')
|
|
128
132
|
.action(tunnelCommand.configureIngress);
|
|
129
133
|
|
|
134
|
+
// IP Whitelist commands
|
|
135
|
+
program
|
|
136
|
+
.command('tunnel:ip-whitelist:add')
|
|
137
|
+
.description('Add IP to tunnel whitelist')
|
|
138
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
139
|
+
.argument('<ip>', 'IP address or CIDR range (e.g., 192.168.1.100 or 10.0.0.0/8)')
|
|
140
|
+
.action(ipWhitelistCommand.add);
|
|
141
|
+
|
|
142
|
+
program
|
|
143
|
+
.command('tunnel:ip-whitelist:remove')
|
|
144
|
+
.description('Remove IP from tunnel whitelist')
|
|
145
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
146
|
+
.argument('<ip>', 'IP address or CIDR range')
|
|
147
|
+
.action(ipWhitelistCommand.remove);
|
|
148
|
+
|
|
149
|
+
program
|
|
150
|
+
.command('tunnel:ip-whitelist:list')
|
|
151
|
+
.description('List tunnel IP whitelist')
|
|
152
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
153
|
+
.action(ipWhitelistCommand.list);
|
|
154
|
+
|
|
155
|
+
program
|
|
156
|
+
.command('tunnel:ip-whitelist:clear')
|
|
157
|
+
.description('Clear tunnel IP whitelist')
|
|
158
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
159
|
+
.action(ipWhitelistCommand.clear);
|
|
160
|
+
|
|
161
|
+
// IP Blacklist commands
|
|
162
|
+
program
|
|
163
|
+
.command('tunnel:ip-blacklist:add')
|
|
164
|
+
.description('Add IP to tunnel blacklist')
|
|
165
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
166
|
+
.argument('<ip>', 'IP address or CIDR range')
|
|
167
|
+
.action(ipBlacklistCommand.add);
|
|
168
|
+
|
|
169
|
+
program
|
|
170
|
+
.command('tunnel:ip-blacklist:remove')
|
|
171
|
+
.description('Remove IP from tunnel blacklist')
|
|
172
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
173
|
+
.argument('<ip>', 'IP address or CIDR range')
|
|
174
|
+
.action(ipBlacklistCommand.remove);
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.command('tunnel:ip-blacklist:list')
|
|
178
|
+
.description('List tunnel IP blacklist')
|
|
179
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
180
|
+
.action(ipBlacklistCommand.list);
|
|
181
|
+
|
|
182
|
+
program
|
|
183
|
+
.command('tunnel:ip-blacklist:clear')
|
|
184
|
+
.description('Clear tunnel IP blacklist')
|
|
185
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
186
|
+
.action(ipBlacklistCommand.clear);
|
|
187
|
+
|
|
188
|
+
// Rate Limit commands
|
|
189
|
+
program
|
|
190
|
+
.command('tunnel:rate-limit')
|
|
191
|
+
.description('Configure tunnel rate limits')
|
|
192
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
193
|
+
.option('--rpm <number>', 'Requests per minute')
|
|
194
|
+
.option('--rph <number>', 'Requests per hour')
|
|
195
|
+
.option('--rpd <number>', 'Requests per day')
|
|
196
|
+
.option('--enable', 'Enable rate limiting')
|
|
197
|
+
.option('--disable', 'Disable rate limiting')
|
|
198
|
+
.action(rateLimitCommand.configure);
|
|
199
|
+
|
|
200
|
+
program
|
|
201
|
+
.command('tunnel:rate-limit:show')
|
|
202
|
+
.description('Show tunnel rate limits')
|
|
203
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
204
|
+
.action(rateLimitCommand.show);
|
|
205
|
+
|
|
206
|
+
// Health check command
|
|
207
|
+
program
|
|
208
|
+
.command('health')
|
|
209
|
+
.description('Check server health')
|
|
210
|
+
.option('-d, --detailed', 'Show detailed health information')
|
|
211
|
+
.action(healthCommand.check);
|
|
212
|
+
|
|
130
213
|
// Webhook commands
|
|
131
214
|
program
|
|
132
215
|
.command('webhook')
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
const config = require('../utils/config');
|
|
5
|
+
|
|
6
|
+
// Check basic health
|
|
7
|
+
async function check(options) {
|
|
8
|
+
const apiUrl = config.get('apiUrl') || 'http://localhost:5000/api';
|
|
9
|
+
// Remove /api prefix since apiUrl already includes it
|
|
10
|
+
const endpoint = options.detailed ? '/health/detailed' : '/health';
|
|
11
|
+
|
|
12
|
+
const spinner = ora('Checking server health...').start();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const response = await axios.get(`${apiUrl}${endpoint}`, {
|
|
16
|
+
validateStatus: (status) => status < 500 || status === 503 // Accept 503 for degraded status
|
|
17
|
+
});
|
|
18
|
+
const health = response.data;
|
|
19
|
+
|
|
20
|
+
spinner.stop();
|
|
21
|
+
|
|
22
|
+
if (options.detailed) {
|
|
23
|
+
displayDetailedHealth(health);
|
|
24
|
+
} else {
|
|
25
|
+
displayBasicHealth(health);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Exit with 0 for ok/degraded, 1 for error
|
|
29
|
+
process.exit(health.status === 'ok' || health.status === 'degraded' ? 0 : 1);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
spinner.fail(chalk.red('Server is unreachable'));
|
|
32
|
+
console.error(chalk.red(`\n✗ ${error.message}\n`));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function displayBasicHealth(health) {
|
|
38
|
+
console.log(chalk.blue.bold('\n🏥 Server Health Check\n'));
|
|
39
|
+
|
|
40
|
+
const statusColor = health.status === 'ok' ? chalk.green : chalk.red;
|
|
41
|
+
const statusIcon = health.status === 'ok' ? '✓' : '✗';
|
|
42
|
+
|
|
43
|
+
console.log(chalk.gray('┌─────────────────────────────────────┐'));
|
|
44
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + statusColor(`${statusIcon} ${health.status.toUpperCase()}`).padEnd(28) + chalk.gray('│'));
|
|
45
|
+
console.log(chalk.gray('│') + chalk.white(' Uptime: ') + chalk.cyan(formatUptime(health.uptime)).padEnd(20) + chalk.gray('│'));
|
|
46
|
+
console.log(chalk.gray('│') + chalk.white(' Environment: ') + chalk.cyan(health.environment).padEnd(20) + chalk.gray('│'));
|
|
47
|
+
console.log(chalk.gray('│') + chalk.white(' Timestamp: ') + chalk.gray(new Date(health.timestamp).toLocaleString()).padEnd(20) + chalk.gray('│'));
|
|
48
|
+
console.log(chalk.gray('└─────────────────────────────────────┘\n'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function displayDetailedHealth(health) {
|
|
52
|
+
console.log(chalk.blue.bold('\n🏥 Detailed Server Health Check\n'));
|
|
53
|
+
|
|
54
|
+
const statusColor = health.status === 'ok' ? chalk.green : (health.status === 'degraded' ? chalk.yellow : chalk.red);
|
|
55
|
+
const statusIcon = health.status === 'ok' ? '✓' : (health.status === 'degraded' ? '⚠' : '✗');
|
|
56
|
+
|
|
57
|
+
console.log(chalk.gray('┌─────────────────────────────────────────────────────┐'));
|
|
58
|
+
console.log(chalk.gray('│') + chalk.white.bold(' Overall Status ') + chalk.gray('│'));
|
|
59
|
+
console.log(chalk.gray('├─────────────────────────────────────────────────────┤'));
|
|
60
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + statusColor(`${statusIcon} ${health.status.toUpperCase()}`).padEnd(42) + chalk.gray('│'));
|
|
61
|
+
console.log(chalk.gray('│') + chalk.white(' Uptime: ') + chalk.cyan(formatUptime(health.uptime)).padEnd(34) + chalk.gray('│'));
|
|
62
|
+
console.log(chalk.gray('│') + chalk.white(' Environment: ') + chalk.cyan(health.environment).padEnd(34) + chalk.gray('│'));
|
|
63
|
+
console.log(chalk.gray('│') + chalk.white(' Version: ') + chalk.cyan(health.version || 'N/A').padEnd(34) + chalk.gray('│'));
|
|
64
|
+
console.log(chalk.gray('└─────────────────────────────────────────────────────┘\n'));
|
|
65
|
+
|
|
66
|
+
if (health.checks) {
|
|
67
|
+
// Database
|
|
68
|
+
console.log(chalk.white.bold('Database:'));
|
|
69
|
+
displayCheckStatus(health.checks.database);
|
|
70
|
+
|
|
71
|
+
// Redis
|
|
72
|
+
console.log(chalk.white.bold('\nRedis:'));
|
|
73
|
+
displayCheckStatus(health.checks.redis);
|
|
74
|
+
|
|
75
|
+
// Memory
|
|
76
|
+
console.log(chalk.white.bold('\nMemory:'));
|
|
77
|
+
displayCheckStatus(health.checks.memory);
|
|
78
|
+
|
|
79
|
+
console.log();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function displayCheckStatus(check) {
|
|
84
|
+
const statusColor = check.status === 'ok' ? chalk.green :
|
|
85
|
+
check.status === 'warning' ? chalk.yellow :
|
|
86
|
+
check.status === 'not configured' ? chalk.gray : chalk.red;
|
|
87
|
+
const statusIcon = check.status === 'ok' ? '✓' :
|
|
88
|
+
check.status === 'warning' ? '⚠' :
|
|
89
|
+
check.status === 'not configured' ? '○' : '✗';
|
|
90
|
+
|
|
91
|
+
console.log(` ${statusColor(statusIcon)} ${statusColor(check.status.toUpperCase())}`);
|
|
92
|
+
|
|
93
|
+
Object.keys(check).forEach(key => {
|
|
94
|
+
if (key !== 'status') {
|
|
95
|
+
console.log(` ${chalk.gray(key + ':')} ${chalk.white(check[key])}`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatUptime(seconds) {
|
|
101
|
+
const days = Math.floor(seconds / 86400);
|
|
102
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
103
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
104
|
+
const secs = Math.floor(seconds % 60);
|
|
105
|
+
|
|
106
|
+
if (days > 0) return `${days}d ${hours}h ${minutes}m`;
|
|
107
|
+
if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
|
|
108
|
+
if (minutes > 0) return `${minutes}m ${secs}s`;
|
|
109
|
+
return `${secs}s`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
check
|
|
114
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const Table = require('cli-table3');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
|
|
6
|
+
// Add IP to blacklist
|
|
7
|
+
async function add(tunnelId, ip) {
|
|
8
|
+
const spinner = ora('Adding IP to blacklist...').start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Get current tunnel
|
|
12
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
13
|
+
const currentBlacklist = tunnel.tunnel.ipBlacklist || [];
|
|
14
|
+
|
|
15
|
+
// Check if IP already exists
|
|
16
|
+
if (currentBlacklist.includes(ip)) {
|
|
17
|
+
spinner.fail(chalk.yellow(`IP ${ip} is already in the blacklist`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Add new IP
|
|
22
|
+
const updatedBlacklist = [...currentBlacklist, ip];
|
|
23
|
+
await api.updateIPBlacklist(tunnelId, updatedBlacklist);
|
|
24
|
+
|
|
25
|
+
spinner.succeed(chalk.green(`IP ${ip} added to blacklist`));
|
|
26
|
+
console.log(chalk.gray(`\nTotal blacklisted IPs: ${updatedBlacklist.length}\n`));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
spinner.fail(chalk.red('Failed to add IP to blacklist'));
|
|
29
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Remove IP from blacklist
|
|
35
|
+
async function remove(tunnelId, ip) {
|
|
36
|
+
const spinner = ora('Removing IP from blacklist...').start();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Get current tunnel
|
|
40
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
41
|
+
const currentBlacklist = tunnel.tunnel.ipBlacklist || [];
|
|
42
|
+
|
|
43
|
+
// Check if IP exists
|
|
44
|
+
if (!currentBlacklist.includes(ip)) {
|
|
45
|
+
spinner.fail(chalk.yellow(`IP ${ip} is not in the blacklist`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Remove IP
|
|
50
|
+
const updatedBlacklist = currentBlacklist.filter(item => item !== ip);
|
|
51
|
+
await api.updateIPBlacklist(tunnelId, updatedBlacklist);
|
|
52
|
+
|
|
53
|
+
spinner.succeed(chalk.green(`IP ${ip} removed from blacklist`));
|
|
54
|
+
console.log(chalk.gray(`\nTotal blacklisted IPs: ${updatedBlacklist.length}\n`));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
spinner.fail(chalk.red('Failed to remove IP from blacklist'));
|
|
57
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// List blacklisted IPs
|
|
63
|
+
async function list(tunnelId) {
|
|
64
|
+
const spinner = ora('Fetching IP blacklist...').start();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
68
|
+
const blacklist = tunnel.tunnel.ipBlacklist || [];
|
|
69
|
+
|
|
70
|
+
spinner.stop();
|
|
71
|
+
|
|
72
|
+
if (blacklist.length === 0) {
|
|
73
|
+
console.log(chalk.yellow('\n⚠ No IP blacklist configured. No IPs are blocked.\n'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.blue.bold(`\n🚫 IP Blacklist (${blacklist.length} IPs)\n`));
|
|
78
|
+
|
|
79
|
+
const table = new Table({
|
|
80
|
+
head: [chalk.white('IP Address / CIDR')],
|
|
81
|
+
style: {
|
|
82
|
+
head: ['cyan'],
|
|
83
|
+
border: ['gray']
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
blacklist.forEach(ip => {
|
|
88
|
+
table.push([chalk.red(ip)]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log(table.toString());
|
|
92
|
+
console.log();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
spinner.fail(chalk.red('Failed to fetch IP blacklist'));
|
|
95
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clear all blacklisted IPs
|
|
101
|
+
async function clear(tunnelId) {
|
|
102
|
+
const spinner = ora('Clearing IP blacklist...').start();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await api.updateIPBlacklist(tunnelId, []);
|
|
106
|
+
|
|
107
|
+
spinner.succeed(chalk.green('IP blacklist cleared'));
|
|
108
|
+
console.log(chalk.gray('\nNo IPs are blocked.\n'));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
spinner.fail(chalk.red('Failed to clear IP blacklist'));
|
|
111
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
add,
|
|
118
|
+
remove,
|
|
119
|
+
list,
|
|
120
|
+
clear
|
|
121
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const Table = require('cli-table3');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
|
|
6
|
+
// Add IP to whitelist
|
|
7
|
+
async function add(tunnelId, ip) {
|
|
8
|
+
const spinner = ora('Adding IP to whitelist...').start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Get current tunnel
|
|
12
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
13
|
+
const currentWhitelist = tunnel.tunnel.ipWhitelist || [];
|
|
14
|
+
|
|
15
|
+
// Check if IP already exists
|
|
16
|
+
if (currentWhitelist.includes(ip)) {
|
|
17
|
+
spinner.fail(chalk.yellow(`IP ${ip} is already in the whitelist`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Add new IP
|
|
22
|
+
const updatedWhitelist = [...currentWhitelist, ip];
|
|
23
|
+
await api.updateIPWhitelist(tunnelId, updatedWhitelist);
|
|
24
|
+
|
|
25
|
+
spinner.succeed(chalk.green(`IP ${ip} added to whitelist`));
|
|
26
|
+
console.log(chalk.gray(`\nTotal whitelisted IPs: ${updatedWhitelist.length}\n`));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
spinner.fail(chalk.red('Failed to add IP to whitelist'));
|
|
29
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Remove IP from whitelist
|
|
35
|
+
async function remove(tunnelId, ip) {
|
|
36
|
+
const spinner = ora('Removing IP from whitelist...').start();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Get current tunnel
|
|
40
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
41
|
+
const currentWhitelist = tunnel.tunnel.ipWhitelist || [];
|
|
42
|
+
|
|
43
|
+
// Check if IP exists
|
|
44
|
+
if (!currentWhitelist.includes(ip)) {
|
|
45
|
+
spinner.fail(chalk.yellow(`IP ${ip} is not in the whitelist`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Remove IP
|
|
50
|
+
const updatedWhitelist = currentWhitelist.filter(item => item !== ip);
|
|
51
|
+
await api.updateIPWhitelist(tunnelId, updatedWhitelist);
|
|
52
|
+
|
|
53
|
+
spinner.succeed(chalk.green(`IP ${ip} removed from whitelist`));
|
|
54
|
+
console.log(chalk.gray(`\nTotal whitelisted IPs: ${updatedWhitelist.length}\n`));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
spinner.fail(chalk.red('Failed to remove IP from whitelist'));
|
|
57
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// List whitelisted IPs
|
|
63
|
+
async function list(tunnelId) {
|
|
64
|
+
const spinner = ora('Fetching IP whitelist...').start();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
68
|
+
const whitelist = tunnel.tunnel.ipWhitelist || [];
|
|
69
|
+
|
|
70
|
+
spinner.stop();
|
|
71
|
+
|
|
72
|
+
if (whitelist.length === 0) {
|
|
73
|
+
console.log(chalk.yellow('\n⚠ No IP whitelist configured. All IPs are allowed.\n'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.blue.bold(`\n🔒 IP Whitelist (${whitelist.length} IPs)\n`));
|
|
78
|
+
|
|
79
|
+
const table = new Table({
|
|
80
|
+
head: [chalk.white('IP Address / CIDR')],
|
|
81
|
+
style: {
|
|
82
|
+
head: ['cyan'],
|
|
83
|
+
border: ['gray']
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
whitelist.forEach(ip => {
|
|
88
|
+
table.push([chalk.green(ip)]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log(table.toString());
|
|
92
|
+
console.log();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
spinner.fail(chalk.red('Failed to fetch IP whitelist'));
|
|
95
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clear all whitelisted IPs
|
|
101
|
+
async function clear(tunnelId) {
|
|
102
|
+
const spinner = ora('Clearing IP whitelist...').start();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await api.updateIPWhitelist(tunnelId, []);
|
|
106
|
+
|
|
107
|
+
spinner.succeed(chalk.green('IP whitelist cleared'));
|
|
108
|
+
console.log(chalk.gray('\nAll IPs are now allowed.\n'));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
spinner.fail(chalk.red('Failed to clear IP whitelist'));
|
|
111
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
add,
|
|
118
|
+
remove,
|
|
119
|
+
list,
|
|
120
|
+
clear
|
|
121
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const api = require('../utils/api');
|
|
4
|
+
|
|
5
|
+
// Configure rate limits
|
|
6
|
+
async function configure(tunnelId, options) {
|
|
7
|
+
const spinner = ora('Updating rate limits...').start();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
// Get current tunnel
|
|
11
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
12
|
+
const currentRateLimit = tunnel.tunnel.rateLimit || {};
|
|
13
|
+
|
|
14
|
+
// Build update object
|
|
15
|
+
const updates = {
|
|
16
|
+
rateLimit: {
|
|
17
|
+
enabled: options.disable ? false : (options.enable ? true : currentRateLimit.enabled),
|
|
18
|
+
requestsPerMinute: options.rpm || currentRateLimit.requestsPerMinute,
|
|
19
|
+
requestsPerHour: options.rph || currentRateLimit.requestsPerHour,
|
|
20
|
+
requestsPerDay: options.rpd || currentRateLimit.requestsPerDay
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
await api.updateTunnel(tunnelId, updates);
|
|
25
|
+
|
|
26
|
+
spinner.succeed(chalk.green('Rate limits updated successfully'));
|
|
27
|
+
|
|
28
|
+
console.log(chalk.blue.bold('\n⚡ Rate Limit Configuration\n'));
|
|
29
|
+
console.log(chalk.gray('┌─────────────────────────────────────┐'));
|
|
30
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + (updates.rateLimit.enabled ? chalk.green('Enabled') : chalk.red('Disabled')).padEnd(28) + chalk.gray('│'));
|
|
31
|
+
console.log(chalk.gray('│') + chalk.white(' Per Minute: ') + chalk.cyan(String(updates.rateLimit.requestsPerMinute)).padEnd(20) + chalk.gray('│'));
|
|
32
|
+
console.log(chalk.gray('│') + chalk.white(' Per Hour: ') + chalk.cyan(String(updates.rateLimit.requestsPerHour)).padEnd(20) + chalk.gray('│'));
|
|
33
|
+
console.log(chalk.gray('│') + chalk.white(' Per Day: ') + chalk.cyan(String(updates.rateLimit.requestsPerDay)).padEnd(20) + chalk.gray('│'));
|
|
34
|
+
console.log(chalk.gray('└─────────────────────────────────────┘\n'));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
spinner.fail(chalk.red('Failed to update rate limits'));
|
|
37
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Show current rate limits
|
|
43
|
+
async function show(tunnelId) {
|
|
44
|
+
const spinner = ora('Fetching rate limits...').start();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
48
|
+
const rateLimit = tunnel.tunnel.rateLimit || {};
|
|
49
|
+
|
|
50
|
+
spinner.stop();
|
|
51
|
+
|
|
52
|
+
console.log(chalk.blue.bold('\n⚡ Rate Limit Configuration\n'));
|
|
53
|
+
console.log(chalk.gray('┌─────────────────────────────────────┐'));
|
|
54
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + (rateLimit.enabled ? chalk.green('Enabled') : chalk.red('Disabled')).padEnd(28) + chalk.gray('│'));
|
|
55
|
+
console.log(chalk.gray('│') + chalk.white(' Per Minute: ') + chalk.cyan(String(rateLimit.requestsPerMinute || 60)).padEnd(20) + chalk.gray('│'));
|
|
56
|
+
console.log(chalk.gray('│') + chalk.white(' Per Hour: ') + chalk.cyan(String(rateLimit.requestsPerHour || 1000)).padEnd(20) + chalk.gray('│'));
|
|
57
|
+
console.log(chalk.gray('│') + chalk.white(' Per Day: ') + chalk.cyan(String(rateLimit.requestsPerDay || 10000)).padEnd(20) + chalk.gray('│'));
|
|
58
|
+
console.log(chalk.gray('└─────────────────────────────────────┘\n'));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
spinner.fail(chalk.red('Failed to fetch rate limits'));
|
|
61
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
configure,
|
|
68
|
+
show
|
|
69
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-response-manager",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Command-line interface for API Response Manager",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,17 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "jest",
|
|
11
|
-
"link": "npm link"
|
|
11
|
+
"link": "npm link",
|
|
12
|
+
"prepublishOnly": "echo 'Publishing api-response-manager CLI...'"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"commands/",
|
|
17
|
+
"utils/",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
12
22
|
},
|
|
13
23
|
"keywords": [
|
|
14
24
|
"api",
|
|
@@ -47,5 +57,6 @@
|
|
|
47
57
|
"bugs": {
|
|
48
58
|
"url": "https://github.com/vijaypurohit322/api-response-manager/issues"
|
|
49
59
|
},
|
|
50
|
-
"homepage": "https://github.com/vijaypurohit322/api-response-manager#readme"
|
|
60
|
+
"homepage": "https://github.com/vijaypurohit322/api-response-manager#readme",
|
|
61
|
+
"preferGlobal": true
|
|
51
62
|
}
|
package/utils/api.js
CHANGED
|
@@ -185,6 +185,23 @@ class APIClient {
|
|
|
185
185
|
const response = await this.client.post(`/tunnels/${id}/ingress`, data);
|
|
186
186
|
return response.data;
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
// IP Management
|
|
190
|
+
async updateIPWhitelist(id, ipWhitelist) {
|
|
191
|
+
const response = await this.client.put(`/tunnels/${id}/ip-whitelist`, { ipWhitelist });
|
|
192
|
+
return response.data;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async updateIPBlacklist(id, ipBlacklist) {
|
|
196
|
+
const response = await this.client.put(`/tunnels/${id}/ip-blacklist`, { ipBlacklist });
|
|
197
|
+
return response.data;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Update Tunnel
|
|
201
|
+
async updateTunnel(id, updates) {
|
|
202
|
+
const response = await this.client.put(`/tunnels/${id}`, updates);
|
|
203
|
+
return response.data;
|
|
204
|
+
}
|
|
188
205
|
}
|
|
189
206
|
|
|
190
207
|
module.exports = new APIClient();
|
package/CLI_UPDATES.md
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# CLI Updates for Advanced Tunnel Features
|
|
2
|
-
|
|
3
|
-
## ✅ Changes Made
|
|
4
|
-
|
|
5
|
-
### 1. Enhanced `tunnel` Command
|
|
6
|
-
Added new options to the main tunnel command:
|
|
7
|
-
```bash
|
|
8
|
-
arm tunnel [port] [options]
|
|
9
|
-
|
|
10
|
-
Options:
|
|
11
|
-
-s, --subdomain <subdomain> Custom subdomain
|
|
12
|
-
-n, --name <name> Tunnel name
|
|
13
|
-
-a, --auth Enable basic authentication
|
|
14
|
-
-r, --rate-limit <limit> Rate limit (requests per minute)
|
|
15
|
-
-p, --protocol <protocol> Protocol (http, https, tcp, ws, wss)
|
|
16
|
-
--ssl Enable SSL/HTTPS
|
|
17
|
-
-d, --domain <domain> Custom domain
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### 2. New Commands Added
|
|
21
|
-
|
|
22
|
-
#### Custom Domain
|
|
23
|
-
```bash
|
|
24
|
-
arm tunnel:domain <tunnelId> <domain>
|
|
25
|
-
```
|
|
26
|
-
Example:
|
|
27
|
-
```bash
|
|
28
|
-
arm tunnel:domain abc123 api.yourdomain.com
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
#### SSL Certificate Upload
|
|
32
|
-
```bash
|
|
33
|
-
arm tunnel:ssl <tunnelId> --cert <path> --key <path> [--ca <path>]
|
|
34
|
-
```
|
|
35
|
-
Example:
|
|
36
|
-
```bash
|
|
37
|
-
arm tunnel:ssl abc123 --cert cert.pem --key key.pem --ca ca.pem
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
#### OAuth Configuration
|
|
41
|
-
```bash
|
|
42
|
-
arm tunnel:auth:oauth <tunnelId> [options]
|
|
43
|
-
|
|
44
|
-
Options:
|
|
45
|
-
--provider <provider> OAuth provider (google, github, microsoft, custom)
|
|
46
|
-
--client-id <id> OAuth client ID
|
|
47
|
-
--client-secret <secret> OAuth client secret
|
|
48
|
-
--callback-url <url> OAuth callback URL
|
|
49
|
-
--scope <scope> OAuth scope (comma-separated)
|
|
50
|
-
```
|
|
51
|
-
Example:
|
|
52
|
-
```bash
|
|
53
|
-
arm tunnel:auth:oauth abc123 \
|
|
54
|
-
--provider google \
|
|
55
|
-
--client-id YOUR_CLIENT_ID \
|
|
56
|
-
--client-secret YOUR_SECRET \
|
|
57
|
-
--callback-url https://yourtunnel.arm.dev/auth/callback \
|
|
58
|
-
--scope openid,email,profile
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
#### OIDC Configuration
|
|
62
|
-
```bash
|
|
63
|
-
arm tunnel:auth:oidc <tunnelId> [options]
|
|
64
|
-
|
|
65
|
-
Options:
|
|
66
|
-
--issuer <url> OIDC issuer URL
|
|
67
|
-
--client-id <id> OIDC client ID
|
|
68
|
-
--client-secret <secret> OIDC client secret
|
|
69
|
-
--callback-url <url> OIDC callback URL
|
|
70
|
-
```
|
|
71
|
-
Example:
|
|
72
|
-
```bash
|
|
73
|
-
arm tunnel:auth:oidc abc123 \
|
|
74
|
-
--issuer https://accounts.google.com \
|
|
75
|
-
--client-id YOUR_CLIENT_ID \
|
|
76
|
-
--client-secret YOUR_SECRET \
|
|
77
|
-
--callback-url https://yourtunnel.arm.dev/auth/callback
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
#### SAML Configuration
|
|
81
|
-
```bash
|
|
82
|
-
arm tunnel:auth:saml <tunnelId> [options]
|
|
83
|
-
|
|
84
|
-
Options:
|
|
85
|
-
--entry-point <url> SAML entry point URL
|
|
86
|
-
--issuer <issuer> SAML issuer
|
|
87
|
-
--cert <path> Path to IdP certificate file
|
|
88
|
-
--callback-url <url> SAML callback URL
|
|
89
|
-
```
|
|
90
|
-
Example:
|
|
91
|
-
```bash
|
|
92
|
-
arm tunnel:auth:saml abc123 \
|
|
93
|
-
--entry-point https://idp.example.com/saml/sso \
|
|
94
|
-
--issuer https://yourtunnel.arm.dev \
|
|
95
|
-
--cert idp-cert.pem \
|
|
96
|
-
--callback-url https://yourtunnel.arm.dev/auth/saml/callback
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
#### Ingress Configuration
|
|
100
|
-
```bash
|
|
101
|
-
arm tunnel:ingress <tunnelId> <rules> [--tls]
|
|
102
|
-
|
|
103
|
-
Rules format: "/path=host:port,/path2=host:port"
|
|
104
|
-
```
|
|
105
|
-
Example:
|
|
106
|
-
```bash
|
|
107
|
-
arm tunnel:ingress abc123 "/api=localhost:3000,/admin=localhost:4000" --tls
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## 📝 Usage Examples
|
|
111
|
-
|
|
112
|
-
### Example 1: Create HTTPS Tunnel
|
|
113
|
-
```bash
|
|
114
|
-
arm tunnel 3000 --protocol https --ssl --subdomain myapi
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Example 2: Create TCP Tunnel
|
|
118
|
-
```bash
|
|
119
|
-
arm tunnel 5432 --protocol tcp --subdomain postgres
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Example 3: Create Tunnel with Custom Domain
|
|
123
|
-
```bash
|
|
124
|
-
# Create tunnel
|
|
125
|
-
arm tunnel 3000 --protocol https --ssl
|
|
126
|
-
|
|
127
|
-
# Set custom domain
|
|
128
|
-
arm tunnel:domain <tunnel-id> api.mycompany.com
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Example 4: Create Tunnel with OAuth
|
|
132
|
-
```bash
|
|
133
|
-
# Create tunnel
|
|
134
|
-
arm tunnel 3000 --protocol https --ssl --subdomain myapi
|
|
135
|
-
|
|
136
|
-
# Configure OAuth
|
|
137
|
-
arm tunnel:auth:oauth <tunnel-id> \
|
|
138
|
-
--provider google \
|
|
139
|
-
--client-id YOUR_CLIENT_ID \
|
|
140
|
-
--client-secret YOUR_SECRET \
|
|
141
|
-
--callback-url https://myapi.tunnel.arm.dev/auth/callback
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Example 5: Create Tunnel with Ingress
|
|
145
|
-
```bash
|
|
146
|
-
# Create tunnel
|
|
147
|
-
arm tunnel 3000 --protocol https --ssl
|
|
148
|
-
|
|
149
|
-
# Configure ingress rules
|
|
150
|
-
arm tunnel:ingress <tunnel-id> \
|
|
151
|
-
"/v1=localhost:3000,/v2=localhost:4000" \
|
|
152
|
-
--tls
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## 🔧 Updated Files
|
|
156
|
-
|
|
157
|
-
1. **cli/bin/arm.js** - Added new commands
|
|
158
|
-
2. **cli/commands/tunnel.js** - Added new functions
|
|
159
|
-
3. **cli/utils/api.js** - Added new API methods
|
|
160
|
-
|
|
161
|
-
## 📦 No New Dependencies Required
|
|
162
|
-
|
|
163
|
-
All new features use existing dependencies.
|
|
164
|
-
|
|
165
|
-
## 🚀 Testing
|
|
166
|
-
|
|
167
|
-
Test the new commands:
|
|
168
|
-
```bash
|
|
169
|
-
# Test HTTPS tunnel
|
|
170
|
-
arm tunnel 3000 --protocol https --ssl
|
|
171
|
-
|
|
172
|
-
# Test custom domain
|
|
173
|
-
arm tunnel:domain <tunnel-id> test.example.com
|
|
174
|
-
|
|
175
|
-
# Test SSL upload
|
|
176
|
-
arm tunnel:ssl <tunnel-id> --cert cert.pem --key key.pem
|
|
177
|
-
|
|
178
|
-
# Test OAuth
|
|
179
|
-
arm tunnel:auth:oauth <tunnel-id> --provider google --client-id test --client-secret test --callback-url http://localhost/callback
|
|
180
|
-
|
|
181
|
-
# Test ingress
|
|
182
|
-
arm tunnel:ingress <tunnel-id> "/api=localhost:3000" --tls
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## 📖 Help Output
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
arm --help
|
|
189
|
-
arm tunnel --help
|
|
190
|
-
arm tunnel:domain --help
|
|
191
|
-
arm tunnel:ssl --help
|
|
192
|
-
arm tunnel:auth:oauth --help
|
|
193
|
-
arm tunnel:auth:oidc --help
|
|
194
|
-
arm tunnel:auth:saml --help
|
|
195
|
-
arm tunnel:ingress --help
|
|
196
|
-
```
|