api-response-manager 1.0.1 ā 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/README.md +244 -6
- package/bin/arm.js +142 -0
- package/commands/health.js +114 -0
- package/commands/ipBlacklist.js +121 -0
- package/commands/ipWhitelist.js +121 -0
- package/commands/login.js +122 -0
- package/commands/rateLimit.js +69 -0
- package/commands/tunnel.js +142 -1
- package/package.json +22 -10
- package/utils/api.js +62 -0
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/README.md
CHANGED
|
@@ -41,12 +41,36 @@ npx @vijaypurohit322-arm/cli webhook
|
|
|
41
41
|
## Quick Start
|
|
42
42
|
|
|
43
43
|
### 1. Login
|
|
44
|
+
|
|
45
|
+
**Interactive Login (Recommended)**
|
|
44
46
|
```bash
|
|
45
47
|
arm login
|
|
46
|
-
#
|
|
48
|
+
# Choose from: Email/Password, Google, GitHub, or Microsoft
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Email & Password**
|
|
52
|
+
```bash
|
|
47
53
|
arm login -e your@email.com -p yourpassword
|
|
48
54
|
```
|
|
49
55
|
|
|
56
|
+
**Social Login (OAuth)**
|
|
57
|
+
```bash
|
|
58
|
+
# Login with Google
|
|
59
|
+
arm login --provider google
|
|
60
|
+
|
|
61
|
+
# Login with GitHub
|
|
62
|
+
arm login --provider github
|
|
63
|
+
|
|
64
|
+
# Login with Microsoft
|
|
65
|
+
arm login --provider microsoft
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The CLI will:
|
|
69
|
+
1. Generate a unique device code
|
|
70
|
+
2. Open your browser automatically
|
|
71
|
+
3. Wait for you to authenticate
|
|
72
|
+
4. Store your credentials securely
|
|
73
|
+
|
|
50
74
|
### 2. Start a Tunnel
|
|
51
75
|
```bash
|
|
52
76
|
# Expose local port 3000
|
|
@@ -77,9 +101,59 @@ arm webhook --tunnel <tunnel-id>
|
|
|
77
101
|
|
|
78
102
|
#### `arm login`
|
|
79
103
|
Authenticate with API Response Manager
|
|
104
|
+
|
|
105
|
+
**Options:**
|
|
106
|
+
- `-e, --email <email>` - Email address (for email/password login)
|
|
107
|
+
- `-p, --password <password>` - Password (for email/password login)
|
|
108
|
+
- `--provider <provider>` - OAuth provider: google, github, or microsoft
|
|
109
|
+
|
|
110
|
+
**Examples:**
|
|
80
111
|
```bash
|
|
112
|
+
# Interactive login (choose method)
|
|
81
113
|
arm login
|
|
82
|
-
|
|
114
|
+
|
|
115
|
+
# Email & Password
|
|
116
|
+
arm login -e user@example.com -p mypassword
|
|
117
|
+
|
|
118
|
+
# Social Login (OAuth Device Flow)
|
|
119
|
+
arm login --provider github
|
|
120
|
+
arm login --provider google
|
|
121
|
+
arm login --provider microsoft
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**OAuth Device Flow:**
|
|
125
|
+
When using social login, the CLI will:
|
|
126
|
+
1. Generate a unique device code (e.g., `ABCD-EFGH`)
|
|
127
|
+
2. Display a verification URL
|
|
128
|
+
3. Automatically open your browser
|
|
129
|
+
4. Wait for you to complete authentication
|
|
130
|
+
5. Store your token securely
|
|
131
|
+
|
|
132
|
+
Example output:
|
|
133
|
+
```bash
|
|
134
|
+
$ arm login --provider github
|
|
135
|
+
|
|
136
|
+
š Logging in with github...
|
|
137
|
+
|
|
138
|
+
š Please complete authentication:
|
|
139
|
+
|
|
140
|
+
1. Visit: https://localhost:5173/device
|
|
141
|
+
2. Enter code: ABCD-EFGH
|
|
142
|
+
|
|
143
|
+
Code expires in 600 seconds
|
|
144
|
+
|
|
145
|
+
? Open browser automatically? (Y/n)
|
|
146
|
+
|
|
147
|
+
ā Browser opened
|
|
148
|
+
|
|
149
|
+
ā Waiting for authentication...
|
|
150
|
+
ā Authentication successful!
|
|
151
|
+
|
|
152
|
+
User: John Doe
|
|
153
|
+
Provider: github
|
|
154
|
+
Token saved to: ~/.config/arm-cli/config.json
|
|
155
|
+
|
|
156
|
+
ā You can now use all ARM CLI commands
|
|
83
157
|
```
|
|
84
158
|
|
|
85
159
|
#### `arm logout`
|
|
@@ -93,9 +167,23 @@ arm logout
|
|
|
93
167
|
#### `arm tunnel <port>`
|
|
94
168
|
Start a tunnel to expose local server
|
|
95
169
|
```bash
|
|
170
|
+
# Basic HTTP tunnel
|
|
96
171
|
arm tunnel 3000
|
|
97
|
-
|
|
98
|
-
|
|
172
|
+
|
|
173
|
+
# HTTPS tunnel with SSL
|
|
174
|
+
arm tunnel 3000 --protocol https --ssl
|
|
175
|
+
|
|
176
|
+
# TCP tunnel (for databases, etc.)
|
|
177
|
+
arm tunnel 5432 --protocol tcp --subdomain postgres
|
|
178
|
+
|
|
179
|
+
# WebSocket tunnel
|
|
180
|
+
arm tunnel 8080 --protocol ws
|
|
181
|
+
|
|
182
|
+
# With custom subdomain and authentication
|
|
183
|
+
arm tunnel 3000 --subdomain myapi --name "My API" --auth --rate-limit 100
|
|
184
|
+
|
|
185
|
+
# With custom domain
|
|
186
|
+
arm tunnel 3000 --protocol https --ssl --domain api.yourdomain.com
|
|
99
187
|
```
|
|
100
188
|
|
|
101
189
|
Options:
|
|
@@ -103,6 +191,9 @@ Options:
|
|
|
103
191
|
- `-n, --name <name>` - Tunnel name
|
|
104
192
|
- `-a, --auth` - Enable basic authentication
|
|
105
193
|
- `-r, --rate-limit <limit>` - Rate limit (requests per minute, default: 60)
|
|
194
|
+
- `-p, --protocol <protocol>` - Protocol: http, https, tcp, ws, wss (default: http)
|
|
195
|
+
- `--ssl` - Enable SSL/HTTPS
|
|
196
|
+
- `-d, --domain <domain>` - Custom domain
|
|
106
197
|
|
|
107
198
|
#### `arm tunnel:list`
|
|
108
199
|
List all active tunnels
|
|
@@ -128,6 +219,91 @@ Options:
|
|
|
128
219
|
- `-f, --follow` - Follow log output (real-time)
|
|
129
220
|
- `-n, --lines <number>` - Number of lines to show (default: 50)
|
|
130
221
|
|
|
222
|
+
#### `arm tunnel:domain <tunnelId> <domain>`
|
|
223
|
+
Set custom domain for tunnel
|
|
224
|
+
```bash
|
|
225
|
+
arm tunnel:domain 507f1f77bcf86cd799439011 api.yourdomain.com
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### `arm tunnel:ssl <tunnelId>`
|
|
229
|
+
Upload SSL certificate for tunnel
|
|
230
|
+
```bash
|
|
231
|
+
arm tunnel:ssl 507f1f77bcf86cd799439011 --cert cert.pem --key key.pem
|
|
232
|
+
arm tunnel:ssl 507f1f77bcf86cd799439011 --cert cert.pem --key key.pem --ca ca.pem
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Options:
|
|
236
|
+
- `--cert <path>` - Path to certificate file
|
|
237
|
+
- `--key <path>` - Path to private key file
|
|
238
|
+
- `--ca <path>` - Path to CA certificate file (optional)
|
|
239
|
+
|
|
240
|
+
#### `arm tunnel:auth:oauth <tunnelId>`
|
|
241
|
+
Configure OAuth authentication for tunnel
|
|
242
|
+
```bash
|
|
243
|
+
arm tunnel:auth:oauth 507f1f77bcf86cd799439011 \
|
|
244
|
+
--provider google \
|
|
245
|
+
--client-id YOUR_CLIENT_ID \
|
|
246
|
+
--client-secret YOUR_SECRET \
|
|
247
|
+
--callback-url https://yourtunnel.arm.dev/auth/callback \
|
|
248
|
+
--scope openid,email,profile
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Options:
|
|
252
|
+
- `--provider <provider>` - OAuth provider: google, github, microsoft, custom
|
|
253
|
+
- `--client-id <id>` - OAuth client ID
|
|
254
|
+
- `--client-secret <secret>` - OAuth client secret
|
|
255
|
+
- `--callback-url <url>` - OAuth callback URL
|
|
256
|
+
- `--scope <scope>` - OAuth scope (comma-separated)
|
|
257
|
+
|
|
258
|
+
#### `arm tunnel:auth:oidc <tunnelId>`
|
|
259
|
+
Configure OpenID Connect authentication for tunnel
|
|
260
|
+
```bash
|
|
261
|
+
arm tunnel:auth:oidc 507f1f77bcf86cd799439011 \
|
|
262
|
+
--issuer https://accounts.google.com \
|
|
263
|
+
--client-id YOUR_CLIENT_ID \
|
|
264
|
+
--client-secret YOUR_SECRET \
|
|
265
|
+
--callback-url https://yourtunnel.arm.dev/auth/callback
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Options:
|
|
269
|
+
- `--issuer <url>` - OIDC issuer URL
|
|
270
|
+
- `--client-id <id>` - OIDC client ID
|
|
271
|
+
- `--client-secret <secret>` - OIDC client secret
|
|
272
|
+
- `--callback-url <url>` - OIDC callback URL
|
|
273
|
+
|
|
274
|
+
#### `arm tunnel:auth:saml <tunnelId>`
|
|
275
|
+
Configure SAML authentication for tunnel
|
|
276
|
+
```bash
|
|
277
|
+
arm tunnel:auth:saml 507f1f77bcf86cd799439011 \
|
|
278
|
+
--entry-point https://idp.example.com/saml/sso \
|
|
279
|
+
--issuer https://yourtunnel.arm.dev \
|
|
280
|
+
--cert idp-cert.pem \
|
|
281
|
+
--callback-url https://yourtunnel.arm.dev/auth/saml/callback
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Options:
|
|
285
|
+
- `--entry-point <url>` - SAML entry point URL
|
|
286
|
+
- `--issuer <issuer>` - SAML issuer
|
|
287
|
+
- `--cert <path>` - Path to IdP certificate file
|
|
288
|
+
- `--callback-url <url>` - SAML callback URL
|
|
289
|
+
|
|
290
|
+
#### `arm tunnel:ingress <tunnelId> <rules>`
|
|
291
|
+
Configure ingress rules for tunnel (path-based routing)
|
|
292
|
+
```bash
|
|
293
|
+
# Route different paths to different backends
|
|
294
|
+
arm tunnel:ingress 507f1f77bcf86cd799439011 \
|
|
295
|
+
"/api=localhost:3000,/admin=localhost:4000" \
|
|
296
|
+
--tls
|
|
297
|
+
|
|
298
|
+
# Single rule
|
|
299
|
+
arm tunnel:ingress 507f1f77bcf86cd799439011 "/api=localhost:3000"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Options:
|
|
303
|
+
- `--tls` - Enable TLS for ingress
|
|
304
|
+
|
|
305
|
+
Rules format: `/path=host:port,/path2=host:port`
|
|
306
|
+
|
|
131
307
|
### Webhooks
|
|
132
308
|
|
|
133
309
|
#### `arm webhook`
|
|
@@ -268,13 +444,53 @@ Default configuration:
|
|
|
268
444
|
# Login
|
|
269
445
|
arm login
|
|
270
446
|
|
|
271
|
-
# Start tunnel on port 3000
|
|
272
|
-
arm tunnel 3000 --subdomain myapp
|
|
447
|
+
# Start HTTPS tunnel on port 3000
|
|
448
|
+
arm tunnel 3000 --protocol https --ssl --subdomain myapp
|
|
273
449
|
|
|
274
450
|
# Your local server is now accessible at:
|
|
275
451
|
# https://myapp.tunnel.arm.dev
|
|
276
452
|
```
|
|
277
453
|
|
|
454
|
+
### Secure Tunnel with OAuth Authentication
|
|
455
|
+
```bash
|
|
456
|
+
# Create HTTPS tunnel
|
|
457
|
+
arm tunnel 3000 --protocol https --ssl --subdomain myapi
|
|
458
|
+
|
|
459
|
+
# Configure Google OAuth
|
|
460
|
+
arm tunnel:auth:oauth <tunnel-id> \
|
|
461
|
+
--provider google \
|
|
462
|
+
--client-id YOUR_GOOGLE_CLIENT_ID \
|
|
463
|
+
--client-secret YOUR_GOOGLE_SECRET \
|
|
464
|
+
--callback-url https://myapi.tunnel.arm.dev/auth/callback
|
|
465
|
+
|
|
466
|
+
# Now your tunnel requires Google login to access
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### TCP Tunnel for Database
|
|
470
|
+
```bash
|
|
471
|
+
# Expose PostgreSQL database
|
|
472
|
+
arm tunnel 5432 --protocol tcp --subdomain mydb
|
|
473
|
+
|
|
474
|
+
# Connect from anywhere:
|
|
475
|
+
# psql -h mydb.tunnel.arm.dev -p 5432 -U username -d database
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Multi-Service Routing with Ingress
|
|
479
|
+
```bash
|
|
480
|
+
# Create tunnel
|
|
481
|
+
arm tunnel 3000 --protocol https --ssl
|
|
482
|
+
|
|
483
|
+
# Configure path-based routing
|
|
484
|
+
arm tunnel:ingress <tunnel-id> \
|
|
485
|
+
"/api/v1=localhost:3000,/api/v2=localhost:4000,/admin=localhost:5000" \
|
|
486
|
+
--tls
|
|
487
|
+
|
|
488
|
+
# Now:
|
|
489
|
+
# https://yourtunnel.arm.dev/api/v1 -> localhost:3000
|
|
490
|
+
# https://yourtunnel.arm.dev/api/v2 -> localhost:4000
|
|
491
|
+
# https://yourtunnel.arm.dev/admin -> localhost:5000
|
|
492
|
+
```
|
|
493
|
+
|
|
278
494
|
### Test Webhooks Locally
|
|
279
495
|
```bash
|
|
280
496
|
# Create webhook that forwards to local server
|
|
@@ -328,6 +544,28 @@ arm config:set apiUrl https://your-api-url.com/api
|
|
|
328
544
|
arm config:get
|
|
329
545
|
```
|
|
330
546
|
|
|
547
|
+
## Troubleshooting
|
|
548
|
+
|
|
549
|
+
### Command Not Found After Installation
|
|
550
|
+
|
|
551
|
+
If you get `arm: command not found` after installing:
|
|
552
|
+
|
|
553
|
+
**Windows:**
|
|
554
|
+
1. Check npm prefix: `npm config get prefix`
|
|
555
|
+
2. Add to PATH: `C:\Users\<YourUsername>\AppData\Roaming\npm`
|
|
556
|
+
3. Restart terminal
|
|
557
|
+
|
|
558
|
+
**macOS/Linux:**
|
|
559
|
+
```bash
|
|
560
|
+
echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> ~/.bashrc
|
|
561
|
+
source ~/.bashrc
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**Alternative - Use npx:**
|
|
565
|
+
```bash
|
|
566
|
+
npx @vijaypurohit322-arm/cli login
|
|
567
|
+
```
|
|
568
|
+
|
|
331
569
|
## Publishing to npm
|
|
332
570
|
|
|
333
571
|
See [PUBLISHING.md](./PUBLISHING.md) for detailed instructions on publishing this package to npm.
|
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
|
|
@@ -30,6 +34,7 @@ program
|
|
|
30
34
|
.description('Authenticate with API Response Manager')
|
|
31
35
|
.option('-e, --email <email>', 'Email address')
|
|
32
36
|
.option('-p, --password <password>', 'Password')
|
|
37
|
+
.option('--provider <provider>', 'OAuth provider (google, github, microsoft)')
|
|
33
38
|
.action(loginCommand);
|
|
34
39
|
|
|
35
40
|
// Logout command
|
|
@@ -47,6 +52,9 @@ program
|
|
|
47
52
|
.option('-n, --name <name>', 'Tunnel name')
|
|
48
53
|
.option('-a, --auth', 'Enable basic authentication')
|
|
49
54
|
.option('-r, --rate-limit <limit>', 'Rate limit (requests per minute)', '60')
|
|
55
|
+
.option('-p, --protocol <protocol>', 'Protocol (http, https, tcp, ws, wss)', 'http')
|
|
56
|
+
.option('--ssl', 'Enable SSL/HTTPS')
|
|
57
|
+
.option('-d, --domain <domain>', 'Custom domain')
|
|
50
58
|
.action(tunnelCommand.start);
|
|
51
59
|
|
|
52
60
|
program
|
|
@@ -68,6 +76,140 @@ program
|
|
|
68
76
|
.option('-n, --lines <number>', 'Number of lines to show', '50')
|
|
69
77
|
.action(tunnelCommand.logs);
|
|
70
78
|
|
|
79
|
+
program
|
|
80
|
+
.command('tunnel:domain')
|
|
81
|
+
.description('Set custom domain for tunnel')
|
|
82
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
83
|
+
.argument('<domain>', 'Custom domain (e.g., api.yourdomain.com)')
|
|
84
|
+
.action(tunnelCommand.setDomain);
|
|
85
|
+
|
|
86
|
+
program
|
|
87
|
+
.command('tunnel:ssl')
|
|
88
|
+
.description('Upload SSL certificate for tunnel')
|
|
89
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
90
|
+
.option('--cert <path>', 'Path to certificate file')
|
|
91
|
+
.option('--key <path>', 'Path to private key file')
|
|
92
|
+
.option('--ca <path>', 'Path to CA certificate file (optional)')
|
|
93
|
+
.action(tunnelCommand.uploadSSL);
|
|
94
|
+
|
|
95
|
+
program
|
|
96
|
+
.command('tunnel:auth:oauth')
|
|
97
|
+
.description('Configure OAuth authentication')
|
|
98
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
99
|
+
.option('--provider <provider>', 'OAuth provider (google, github, microsoft, custom)')
|
|
100
|
+
.option('--client-id <id>', 'OAuth client ID')
|
|
101
|
+
.option('--client-secret <secret>', 'OAuth client secret')
|
|
102
|
+
.option('--callback-url <url>', 'OAuth callback URL')
|
|
103
|
+
.option('--scope <scope>', 'OAuth scope (comma-separated)')
|
|
104
|
+
.action(tunnelCommand.configureOAuth);
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command('tunnel:auth:oidc')
|
|
108
|
+
.description('Configure OIDC authentication')
|
|
109
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
110
|
+
.option('--issuer <url>', 'OIDC issuer URL')
|
|
111
|
+
.option('--client-id <id>', 'OIDC client ID')
|
|
112
|
+
.option('--client-secret <secret>', 'OIDC client secret')
|
|
113
|
+
.option('--callback-url <url>', 'OIDC callback URL')
|
|
114
|
+
.action(tunnelCommand.configureOIDC);
|
|
115
|
+
|
|
116
|
+
program
|
|
117
|
+
.command('tunnel:auth:saml')
|
|
118
|
+
.description('Configure SAML authentication')
|
|
119
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
120
|
+
.option('--entry-point <url>', 'SAML entry point URL')
|
|
121
|
+
.option('--issuer <issuer>', 'SAML issuer')
|
|
122
|
+
.option('--cert <path>', 'Path to IdP certificate file')
|
|
123
|
+
.option('--callback-url <url>', 'SAML callback URL')
|
|
124
|
+
.action(tunnelCommand.configureSAML);
|
|
125
|
+
|
|
126
|
+
program
|
|
127
|
+
.command('tunnel:ingress')
|
|
128
|
+
.description('Configure ingress rules for tunnel')
|
|
129
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
130
|
+
.argument('<rules>', 'Ingress rules (e.g., "/api=localhost:3000,/admin=localhost:4000")')
|
|
131
|
+
.option('--tls', 'Enable TLS for ingress')
|
|
132
|
+
.action(tunnelCommand.configureIngress);
|
|
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
|
+
|
|
71
213
|
// Webhook commands
|
|
72
214
|
program
|
|
73
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
|
+
};
|
package/commands/login.js
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
const inquirer = require('inquirer');
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const ora = require('ora');
|
|
4
|
+
const open = require('open');
|
|
4
5
|
const api = require('../utils/api');
|
|
5
6
|
const config = require('../utils/config');
|
|
6
7
|
|
|
7
8
|
async function login(options) {
|
|
8
9
|
console.log(chalk.blue.bold('\nš API Response Manager - Login\n'));
|
|
9
10
|
|
|
11
|
+
// Check if social login is requested
|
|
12
|
+
if (options.provider) {
|
|
13
|
+
return await socialLogin(options.provider);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Ask for login method
|
|
17
|
+
const { method } = await inquirer.prompt([
|
|
18
|
+
{
|
|
19
|
+
type: 'list',
|
|
20
|
+
name: 'method',
|
|
21
|
+
message: 'Choose login method:',
|
|
22
|
+
choices: [
|
|
23
|
+
{ name: 'š§ Email & Password', value: 'email' },
|
|
24
|
+
{ name: 'š Google', value: 'google' },
|
|
25
|
+
{ name: 'š GitHub', value: 'github' },
|
|
26
|
+
{ name: 'šŖ Microsoft', value: 'microsoft' }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
if (method !== 'email') {
|
|
32
|
+
return await socialLogin(method);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Traditional email/password login
|
|
10
36
|
let email = options.email;
|
|
11
37
|
let password = options.password;
|
|
12
38
|
|
|
@@ -85,4 +111,100 @@ async function login(options) {
|
|
|
85
111
|
}
|
|
86
112
|
}
|
|
87
113
|
|
|
114
|
+
async function socialLogin(provider) {
|
|
115
|
+
console.log(chalk.blue(`\nš Logging in with ${provider}...\n`));
|
|
116
|
+
|
|
117
|
+
const spinner = ora('Initiating OAuth flow...').start();
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Request device code from backend
|
|
121
|
+
const deviceResponse = await api.requestDeviceCode(provider);
|
|
122
|
+
|
|
123
|
+
spinner.stop();
|
|
124
|
+
|
|
125
|
+
const { device_code, user_code, verification_uri, expires_in } = deviceResponse;
|
|
126
|
+
|
|
127
|
+
console.log(chalk.yellow('\nš Please complete authentication:\n'));
|
|
128
|
+
console.log(chalk.white(' 1. Visit:'), chalk.cyan.underline(verification_uri));
|
|
129
|
+
console.log(chalk.white(' 2. Enter code:'), chalk.green.bold(user_code));
|
|
130
|
+
console.log(chalk.gray(`\n Code expires in ${expires_in} seconds\n`));
|
|
131
|
+
|
|
132
|
+
// Open browser automatically
|
|
133
|
+
const { openBrowser } = await inquirer.prompt([
|
|
134
|
+
{
|
|
135
|
+
type: 'confirm',
|
|
136
|
+
name: 'openBrowser',
|
|
137
|
+
message: 'Open browser automatically?',
|
|
138
|
+
default: true
|
|
139
|
+
}
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
if (openBrowser) {
|
|
143
|
+
await open(verification_uri);
|
|
144
|
+
console.log(chalk.gray(' ā Browser opened\n'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const pollSpinner = ora('Waiting for authentication...').start();
|
|
148
|
+
|
|
149
|
+
// Poll for token
|
|
150
|
+
const pollInterval = 5000; // 5 seconds
|
|
151
|
+
const maxAttempts = Math.ceil(expires_in / (pollInterval / 1000));
|
|
152
|
+
let attempts = 0;
|
|
153
|
+
|
|
154
|
+
const pollForToken = async () => {
|
|
155
|
+
while (attempts < maxAttempts) {
|
|
156
|
+
attempts++;
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const tokenResponse = await api.pollDeviceToken(device_code, provider);
|
|
160
|
+
|
|
161
|
+
if (tokenResponse.token) {
|
|
162
|
+
pollSpinner.succeed(chalk.green('Authentication successful!'));
|
|
163
|
+
|
|
164
|
+
// Store credentials
|
|
165
|
+
api.setToken(tokenResponse.token);
|
|
166
|
+
|
|
167
|
+
const userId = tokenResponse.user?._id || tokenResponse.user?.id;
|
|
168
|
+
const userEmail = tokenResponse.user?.email;
|
|
169
|
+
const userName = tokenResponse.user?.name;
|
|
170
|
+
|
|
171
|
+
if (userId) config.set('userId', userId);
|
|
172
|
+
if (userEmail) config.set('email', userEmail);
|
|
173
|
+
|
|
174
|
+
console.log(chalk.gray('\nUser:'), chalk.white(userName || userEmail));
|
|
175
|
+
console.log(chalk.gray('Provider:'), chalk.white(provider));
|
|
176
|
+
console.log(chalk.gray('Token saved to:'), chalk.white(config.path));
|
|
177
|
+
console.log(chalk.green('\nā You can now use all ARM CLI commands\n'));
|
|
178
|
+
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error.response?.status === 428) {
|
|
183
|
+
// Still pending, continue polling
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
throw new Error('Authentication timeout - please try again');
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
await pollForToken();
|
|
195
|
+
|
|
196
|
+
} catch (error) {
|
|
197
|
+
spinner.stop();
|
|
198
|
+
console.error(chalk.red('\nā Social login failed'));
|
|
199
|
+
|
|
200
|
+
if (error.response?.data?.msg) {
|
|
201
|
+
console.error(chalk.red(`ā ${error.response.data.msg}\n`));
|
|
202
|
+
} else {
|
|
203
|
+
console.error(chalk.red(`ā ${error.message}\n`));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
88
210
|
module.exports = login;
|
|
@@ -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/commands/tunnel.js
CHANGED
|
@@ -21,6 +21,9 @@ async function start(port, options) {
|
|
|
21
21
|
name: options.name || `Tunnel-${port}`,
|
|
22
22
|
subdomain: options.subdomain,
|
|
23
23
|
localPort: parseInt(port),
|
|
24
|
+
protocol: options.protocol || 'http',
|
|
25
|
+
sslEnabled: options.ssl || options.protocol === 'https',
|
|
26
|
+
customDomain: options.domain,
|
|
24
27
|
security: {
|
|
25
28
|
requireAuth: options.auth || false
|
|
26
29
|
},
|
|
@@ -222,9 +225,147 @@ async function logs(tunnelId, options) {
|
|
|
222
225
|
}
|
|
223
226
|
}
|
|
224
227
|
|
|
228
|
+
// Set custom domain
|
|
229
|
+
async function setDomain(tunnelId, domain) {
|
|
230
|
+
const spinner = ora('Setting custom domain...').start();
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await api.setTunnelDomain(tunnelId, domain);
|
|
234
|
+
spinner.succeed(chalk.green('Custom domain set successfully'));
|
|
235
|
+
console.log(chalk.cyan(`\nā Tunnel now accessible at: https://${domain}\n`));
|
|
236
|
+
} catch (error) {
|
|
237
|
+
spinner.fail(chalk.red('Failed to set custom domain'));
|
|
238
|
+
console.error(chalk.red(`\nā ${error.response?.data?.msg || error.message}\n`));
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Upload SSL certificate
|
|
244
|
+
async function uploadSSL(tunnelId, options) {
|
|
245
|
+
const spinner = ora('Uploading SSL certificate...').start();
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const fs = require('fs');
|
|
249
|
+
const cert = fs.readFileSync(options.cert, 'utf8');
|
|
250
|
+
const key = fs.readFileSync(options.key, 'utf8');
|
|
251
|
+
const ca = options.ca ? fs.readFileSync(options.ca, 'utf8') : null;
|
|
252
|
+
|
|
253
|
+
await api.uploadTunnelSSL(tunnelId, { cert, key, ca });
|
|
254
|
+
spinner.succeed(chalk.green('SSL certificate uploaded successfully'));
|
|
255
|
+
console.log();
|
|
256
|
+
} catch (error) {
|
|
257
|
+
spinner.fail(chalk.red('Failed to upload SSL certificate'));
|
|
258
|
+
console.error(chalk.red(`\nā ${error.response?.data?.msg || error.message}\n`));
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Configure OAuth
|
|
264
|
+
async function configureOAuth(tunnelId, options) {
|
|
265
|
+
const spinner = ora('Configuring OAuth...').start();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await api.configureTunnelOAuth(tunnelId, {
|
|
269
|
+
provider: options.provider,
|
|
270
|
+
clientId: options.clientId,
|
|
271
|
+
clientSecret: options.clientSecret,
|
|
272
|
+
callbackUrl: options.callbackUrl,
|
|
273
|
+
scope: options.scope ? options.scope.split(',') : ['openid', 'email', 'profile']
|
|
274
|
+
});
|
|
275
|
+
spinner.succeed(chalk.green('OAuth configured successfully'));
|
|
276
|
+
console.log();
|
|
277
|
+
} catch (error) {
|
|
278
|
+
spinner.fail(chalk.red('Failed to configure OAuth'));
|
|
279
|
+
console.error(chalk.red(`\nā ${error.response?.data?.msg || error.message}\n`));
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Configure OIDC
|
|
285
|
+
async function configureOIDC(tunnelId, options) {
|
|
286
|
+
const spinner = ora('Configuring OIDC...').start();
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
await api.configureTunnelOIDC(tunnelId, {
|
|
290
|
+
issuer: options.issuer,
|
|
291
|
+
clientId: options.clientId,
|
|
292
|
+
clientSecret: options.clientSecret,
|
|
293
|
+
callbackUrl: options.callbackUrl
|
|
294
|
+
});
|
|
295
|
+
spinner.succeed(chalk.green('OIDC configured successfully'));
|
|
296
|
+
console.log();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
spinner.fail(chalk.red('Failed to configure OIDC'));
|
|
299
|
+
console.error(chalk.red(`\nā ${error.response?.data?.msg || error.message}\n`));
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Configure SAML
|
|
305
|
+
async function configureSAML(tunnelId, options) {
|
|
306
|
+
const spinner = ora('Configuring SAML...').start();
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const fs = require('fs');
|
|
310
|
+
const cert = fs.readFileSync(options.cert, 'utf8');
|
|
311
|
+
|
|
312
|
+
await api.configureTunnelSAML(tunnelId, {
|
|
313
|
+
entryPoint: options.entryPoint,
|
|
314
|
+
issuer: options.issuer,
|
|
315
|
+
cert,
|
|
316
|
+
callbackUrl: options.callbackUrl
|
|
317
|
+
});
|
|
318
|
+
spinner.succeed(chalk.green('SAML configured successfully'));
|
|
319
|
+
console.log();
|
|
320
|
+
} catch (error) {
|
|
321
|
+
spinner.fail(chalk.red('Failed to configure SAML'));
|
|
322
|
+
console.error(chalk.red(`\nā ${error.response?.data?.msg || error.message}\n`));
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Configure ingress
|
|
328
|
+
async function configureIngress(tunnelId, rules, options) {
|
|
329
|
+
const spinner = ora('Configuring ingress...').start();
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
// Parse rules: "/api=localhost:3000,/admin=localhost:4000"
|
|
333
|
+
const parsedRules = rules.split(',').map(rule => {
|
|
334
|
+
const [path, backend] = rule.split('=');
|
|
335
|
+
const [host, port] = backend.split(':');
|
|
336
|
+
return {
|
|
337
|
+
path: path.trim(),
|
|
338
|
+
pathType: 'Prefix',
|
|
339
|
+
backend: {
|
|
340
|
+
host: host.trim(),
|
|
341
|
+
port: parseInt(port)
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
await api.configureTunnelIngress(tunnelId, {
|
|
347
|
+
enabled: true,
|
|
348
|
+
rules: parsedRules,
|
|
349
|
+
tls: { enabled: options.tls || false }
|
|
350
|
+
});
|
|
351
|
+
spinner.succeed(chalk.green('Ingress configured successfully'));
|
|
352
|
+
console.log();
|
|
353
|
+
} catch (error) {
|
|
354
|
+
spinner.fail(chalk.red('Failed to configure ingress'));
|
|
355
|
+
console.error(chalk.red(`\nā ${error.response?.data?.msg || error.message}\n`));
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
225
360
|
module.exports = {
|
|
226
361
|
start,
|
|
227
362
|
list,
|
|
228
363
|
stop,
|
|
229
|
-
logs
|
|
364
|
+
logs,
|
|
365
|
+
setDomain,
|
|
366
|
+
uploadSSL,
|
|
367
|
+
configureOAuth,
|
|
368
|
+
configureOIDC,
|
|
369
|
+
configureSAML,
|
|
370
|
+
configureIngress
|
|
230
371
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-response-manager",
|
|
3
|
-
"version": "
|
|
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",
|
|
@@ -21,17 +31,18 @@
|
|
|
21
31
|
"author": "Vijay Singh Purohit",
|
|
22
32
|
"license": "MIT",
|
|
23
33
|
"dependencies": {
|
|
24
|
-
"commander": "^11.1.0",
|
|
25
34
|
"axios": "^1.6.2",
|
|
35
|
+
"boxen": "^5.1.2",
|
|
26
36
|
"chalk": "^4.1.2",
|
|
37
|
+
"cli-table3": "^0.6.3",
|
|
38
|
+
"commander": "^11.1.0",
|
|
39
|
+
"conf": "^10.2.0",
|
|
40
|
+
"dotenv": "^16.3.1",
|
|
27
41
|
"inquirer": "^8.2.5",
|
|
42
|
+
"open": "^8.4.0",
|
|
28
43
|
"ora": "^5.4.1",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"conf": "^10.2.0",
|
|
32
|
-
"boxen": "^5.1.2",
|
|
33
|
-
"cli-table3": "^0.6.3",
|
|
34
|
-
"update-notifier": "^6.0.2"
|
|
44
|
+
"update-notifier": "^6.0.2",
|
|
45
|
+
"ws": "^8.14.2"
|
|
35
46
|
},
|
|
36
47
|
"devDependencies": {
|
|
37
48
|
"jest": "^29.7.0"
|
|
@@ -46,5 +57,6 @@
|
|
|
46
57
|
"bugs": {
|
|
47
58
|
"url": "https://github.com/vijaypurohit322/api-response-manager/issues"
|
|
48
59
|
},
|
|
49
|
-
"homepage": "https://github.com/vijaypurohit322/api-response-manager#readme"
|
|
60
|
+
"homepage": "https://github.com/vijaypurohit322/api-response-manager#readme",
|
|
61
|
+
"preferGlobal": true
|
|
50
62
|
}
|
package/utils/api.js
CHANGED
|
@@ -58,6 +58,20 @@ class APIClient {
|
|
|
58
58
|
return response.data;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// OAuth Device Flow for CLI
|
|
62
|
+
async requestDeviceCode(provider) {
|
|
63
|
+
const response = await this.client.post('/auth/device/code', { provider });
|
|
64
|
+
return response.data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async pollDeviceToken(deviceCode, provider) {
|
|
68
|
+
const response = await this.client.post('/auth/device/token', {
|
|
69
|
+
device_code: deviceCode,
|
|
70
|
+
provider
|
|
71
|
+
});
|
|
72
|
+
return response.data;
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
// Tunnels
|
|
62
76
|
async createTunnel(data) {
|
|
63
77
|
const response = await this.client.post('/tunnels', data);
|
|
@@ -140,6 +154,54 @@ class APIClient {
|
|
|
140
154
|
const response = await this.client.get(`/projects/${id}/responses`, { params });
|
|
141
155
|
return response.data;
|
|
142
156
|
}
|
|
157
|
+
|
|
158
|
+
// Advanced Tunnel Features
|
|
159
|
+
async setTunnelDomain(id, domain) {
|
|
160
|
+
const response = await this.client.post(`/tunnels/${id}/custom-domain`, { domain });
|
|
161
|
+
return response.data;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async uploadTunnelSSL(id, data) {
|
|
165
|
+
const response = await this.client.post(`/tunnels/${id}/ssl`, data);
|
|
166
|
+
return response.data;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async configureTunnelOAuth(id, data) {
|
|
170
|
+
const response = await this.client.post(`/tunnels/${id}/auth/oauth`, data);
|
|
171
|
+
return response.data;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async configureTunnelOIDC(id, data) {
|
|
175
|
+
const response = await this.client.post(`/tunnels/${id}/auth/oidc`, data);
|
|
176
|
+
return response.data;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async configureTunnelSAML(id, data) {
|
|
180
|
+
const response = await this.client.post(`/tunnels/${id}/auth/saml`, data);
|
|
181
|
+
return response.data;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async configureTunnelIngress(id, data) {
|
|
185
|
+
const response = await this.client.post(`/tunnels/${id}/ingress`, data);
|
|
186
|
+
return response.data;
|
|
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
|
+
}
|
|
143
205
|
}
|
|
144
206
|
|
|
145
207
|
module.exports = new APIClient();
|