securenow 5.18.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +40 -239
  3. package/cli.js +455 -415
  4. package/console-instrumentation.js +136 -147
  5. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
  6. package/docs/ARCHITECTURE.md +3 -3
  7. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  8. package/docs/AUTO-SETUP.md +4 -4
  9. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  10. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  11. package/docs/CHANGELOG-NEXTJS.md +1 -1
  12. package/docs/CUSTOMER-GUIDE.md +16 -16
  13. package/docs/EASIEST-SETUP.md +5 -5
  14. package/docs/ENVIRONMENT-VARIABLES.md +652 -880
  15. package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
  16. package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
  17. package/docs/INDEX.md +4 -22
  18. package/docs/LOGGING-GUIDE.md +708 -701
  19. package/docs/LOGGING-QUICKSTART.md +239 -234
  20. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  21. package/docs/NEXTJS-GUIDE.md +14 -14
  22. package/docs/NEXTJS-QUICKSTART.md +1 -1
  23. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  24. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  25. package/docs/REDACTION-EXAMPLES.md +1 -1
  26. package/docs/REQUEST-BODY-CAPTURE.md +10 -19
  27. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  28. package/examples/README.md +6 -6
  29. package/examples/instrumentation-with-auto-capture.ts +1 -1
  30. package/examples/nextjs-env-example.txt +2 -2
  31. package/examples/nextjs-instrumentation.js +1 -1
  32. package/examples/nextjs-instrumentation.ts +1 -1
  33. package/examples/nextjs-with-logging-example.md +6 -6
  34. package/examples/nextjs-with-options.ts +1 -1
  35. package/examples/test-nextjs-setup.js +1 -1
  36. package/nextjs-auto-capture.js +207 -199
  37. package/nextjs-middleware.js +181 -186
  38. package/nextjs-webpack-config.js +53 -88
  39. package/nextjs-wrapper.js +158 -158
  40. package/nextjs.d.ts +1 -1
  41. package/nextjs.js +135 -190
  42. package/package.json +45 -67
  43. package/postinstall.js +6 -6
  44. package/register.d.ts +1 -1
  45. package/register.js +4 -39
  46. package/tracing.d.ts +1 -2
  47. package/tracing.js +22 -287
  48. package/web-vite.mjs +156 -239
  49. package/CONSUMING-APPS-GUIDE.md +0 -455
  50. package/NPM_README.md +0 -1933
  51. package/SKILL-API.md +0 -600
  52. package/SKILL-CLI.md +0 -409
  53. package/cidr.js +0 -83
  54. package/cli/apps.js +0 -585
  55. package/cli/auth.js +0 -280
  56. package/cli/client.js +0 -115
  57. package/cli/config.js +0 -173
  58. package/cli/firewall.js +0 -100
  59. package/cli/fp.js +0 -638
  60. package/cli/init.js +0 -201
  61. package/cli/monitor.js +0 -440
  62. package/cli/run.js +0 -133
  63. package/cli/security.js +0 -1064
  64. package/cli/ui.js +0 -386
  65. package/docs/API-KEYS-GUIDE.md +0 -233
  66. package/docs/AUTO-SETUP-SUMMARY.md +0 -331
  67. package/docs/BODY-CAPTURE-FIX.md +0 -261
  68. package/docs/COMPLETION-REPORT.md +0 -408
  69. package/docs/FINAL-SOLUTION.md +0 -335
  70. package/docs/FIREWALL-GUIDE.md +0 -426
  71. package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
  72. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
  73. package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
  74. package/docs/NUXT-GUIDE.md +0 -166
  75. package/docs/SOLUTION-SUMMARY.md +0 -312
  76. package/firewall-cloud.js +0 -212
  77. package/firewall-iptables.js +0 -139
  78. package/firewall-only.js +0 -38
  79. package/firewall-tcp.js +0 -74
  80. package/firewall.js +0 -720
  81. package/free-trial-banner.js +0 -174
  82. package/nuxt-server-plugin.mjs +0 -423
  83. package/nuxt.d.ts +0 -60
  84. package/nuxt.mjs +0 -75
  85. package/resolve-ip.js +0 -77
package/cli/auth.js DELETED
@@ -1,280 +0,0 @@
1
- 'use strict';
2
-
3
- const http = require('http');
4
- const crypto = require('crypto');
5
- const { execFileSync } = require('child_process');
6
- const config = require('./config');
7
- const { api, CLIError } = require('./client');
8
- const ui = require('./ui');
9
-
10
- function openBrowser(url) {
11
- try {
12
- const platform = process.platform;
13
- if (platform === 'darwin') execFileSync('open', [url], { stdio: 'ignore' });
14
- else if (platform === 'win32') execFileSync('rundll32', ['url.dll,FileProtocolHandler', url], { stdio: 'ignore' });
15
- else execFileSync('xdg-open', [url], { stdio: 'ignore' });
16
- return true;
17
- } catch {
18
- return false;
19
- }
20
- }
21
-
22
- function decodeJwtPayload(token) {
23
- try {
24
- const parts = token.split('.');
25
- if (parts.length !== 3) return null;
26
- const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
27
- return JSON.parse(payload);
28
- } catch {
29
- try {
30
- const parts = token.split('.');
31
- const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
32
- const padded = base64 + '='.repeat((4 - base64.length % 4) % 4);
33
- const payload = Buffer.from(padded, 'base64').toString('utf8');
34
- return JSON.parse(payload);
35
- } catch {
36
- return null;
37
- }
38
- }
39
- }
40
-
41
- async function loginWithBrowser() {
42
- const appUrl = config.getAppUrl();
43
- const nonce = crypto.randomBytes(24).toString('base64url');
44
-
45
- return new Promise((resolve, reject) => {
46
- let pendingToken = null;
47
-
48
- const server = http.createServer((req, res) => {
49
- const url = new URL(req.url, `http://127.0.0.1`);
50
-
51
- if (url.pathname === '/callback') {
52
- const token = url.searchParams.get('token');
53
- const error = url.searchParams.get('error');
54
- const returnedState = url.searchParams.get('state');
55
-
56
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
57
-
58
- if (error) {
59
- res.end('<html><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Authentication Failed</h2><p>You can close this window.</p></body></html>');
60
- server.close();
61
- reject(new CLIError(`Authentication failed: ${error}`));
62
- return;
63
- }
64
-
65
- if (returnedState !== nonce) {
66
- res.end('<html><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Security Error</h2><p>State mismatch — this request may not have originated from your CLI. Please try again.</p></body></html>');
67
- server.close();
68
- reject(new CLIError('State mismatch on callback — possible CSRF. Please retry `securenow login`.'));
69
- return;
70
- }
71
-
72
- if (token) {
73
- pendingToken = token;
74
- const payload = decodeJwtPayload(token);
75
- const email = payload?.email || 'unknown account';
76
- const safeEmail = email.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
77
- const port = server.address().port;
78
- const switchUrl = `${appUrl}/cli/auth?callback=http://127.0.0.1:${port}/callback&state=${encodeURIComponent(nonce)}&force_login=1`;
79
-
80
- res.end([
81
- '<!DOCTYPE html><html><head><meta charset="utf-8"><title>SecureNow CLI Login</title></head>',
82
- '<body style="font-family:system-ui,sans-serif;text-align:center;padding:60px;margin:0;background:#fafafa">',
83
- '<div style="max-width:420px;margin:0 auto;background:#fff;border-radius:12px;padding:40px 32px;box-shadow:0 2px 12px rgba(0,0,0,.08)">',
84
- '<div style="width:56px;height:56px;margin:0 auto 20px;background:#f0fdf4;border-radius:50%;display:flex;align-items:center;justify-content:center">',
85
- '<svg width="28" height="28" fill="none" viewBox="0 0 24 24"><path d="M12 2a5 5 0 015 5v1a2 2 0 012 2v8a2 2 0 01-2 2H7a2 2 0 01-2-2v-8a2 2 0 012-2V7a5 5 0 015-5zm0 2a3 3 0 00-3 3v1h6V7a3 3 0 00-3-3z" fill="#22c55e"/></svg>',
86
- '</div>',
87
- '<h2 style="margin:0 0 8px;font-size:22px;color:#111">Connect to SecureNow CLI</h2>',
88
- `<p style="margin:0 0 24px;color:#666;font-size:15px">You are signing in as</p>`,
89
- `<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px 18px;margin:0 0 28px">`,
90
- `<span style="font-size:17px;font-weight:600;color:#0f172a">${safeEmail}</span>`,
91
- '</div>',
92
- '<button id="confirm-btn" style="width:100%;padding:13px 24px;font-size:16px;font-weight:600;color:#fff;background:#22c55e;border:none;border-radius:8px;cursor:pointer;transition:background .15s" ',
93
- 'onmouseover="this.style.background=\'#16a34a\'" onmouseout="this.style.background=\'#22c55e\'">',
94
- 'Confirm &amp; Continue</button>',
95
- `<p style="margin:20px 0 0"><a href="${switchUrl}" style="color:#6366f1;font-size:14px;text-decoration:none" `,
96
- 'onmouseover="this.style.textDecoration=\'underline\'" onmouseout="this.style.textDecoration=\'none\'">Use a different account</a></p>',
97
- '<div id="done-msg" style="display:none;margin-top:24px">',
98
- '<p style="color:#22c55e;font-weight:600;font-size:17px">\u2713 Connected! You can close this window.</p>',
99
- '</div>',
100
- '</div>',
101
- '<script>',
102
- 'document.getElementById("confirm-btn").addEventListener("click", function(){',
103
- ' this.disabled=true;this.textContent="Connecting\u2026";this.style.background="#86efac";this.style.cursor="default";',
104
- ` fetch("/confirm?nonce=${encodeURIComponent(nonce)}").then(function(){`,
105
- ' document.getElementById("confirm-btn").style.display="none";',
106
- ' document.getElementById("done-msg").style.display="block";',
107
- ' });',
108
- '});',
109
- '</script>',
110
- '</body></html>',
111
- ].join(''));
112
- return;
113
- }
114
-
115
- res.end('<html><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Something went wrong</h2><p>No token received. Please try again.</p></body></html>');
116
- server.close();
117
- reject(new CLIError('No token received in callback'));
118
- return;
119
- }
120
-
121
- if (url.pathname === '/confirm' && pendingToken) {
122
- if (url.searchParams.get('nonce') !== nonce) {
123
- res.writeHead(403, { 'Content-Type': 'application/json' });
124
- res.end('{"error":"invalid nonce"}');
125
- return;
126
- }
127
- res.writeHead(200, { 'Content-Type': 'application/json' });
128
- res.end('{"ok":true}');
129
- const token = pendingToken;
130
- pendingToken = null;
131
- server.close();
132
- resolve(token);
133
- return;
134
- }
135
-
136
- res.writeHead(404);
137
- res.end();
138
- });
139
-
140
- server.listen(0, '127.0.0.1', () => {
141
- const port = server.address().port;
142
- const authUrl = `${appUrl}/cli/auth?callback=http://127.0.0.1:${port}/callback&state=${encodeURIComponent(nonce)}`;
143
-
144
- console.log('');
145
- ui.info('Opening browser for authentication...');
146
- console.log('');
147
-
148
- const opened = openBrowser(authUrl);
149
- if (!opened) {
150
- console.log(' Open this URL in your browser to log in:\n');
151
- console.log(` ${ui.c.underline(ui.c.cyan(authUrl))}\n`);
152
- } else {
153
- console.log(` If the browser didn't open, visit:`);
154
- console.log(` ${ui.c.underline(ui.c.cyan(authUrl))}\n`);
155
- }
156
-
157
- console.log(ui.c.dim(' Waiting for authentication...'));
158
-
159
- const timeout = setTimeout(() => {
160
- server.close();
161
- reject(new CLIError('Login timed out after 5 minutes. Try `securenow login --token <TOKEN>` instead.'));
162
- }, 5 * 60 * 1000);
163
-
164
- server.on('close', () => clearTimeout(timeout));
165
- });
166
-
167
- server.on('error', (err) => {
168
- reject(new CLIError(`Failed to start local server: ${err.message}`));
169
- });
170
- });
171
- }
172
-
173
- async function loginWithToken(token) {
174
- const s = ui.spinner('Validating token');
175
- try {
176
- await api.get('/applications', { token });
177
- s.stop('Token is valid');
178
- return token;
179
- } catch (err) {
180
- s.fail('Token validation failed');
181
- throw new CLIError(`Invalid token: ${err.message}`);
182
- }
183
- }
184
-
185
- async function login(args, flags) {
186
- const local = !!flags.local;
187
-
188
- if (flags.token) {
189
- const token = flags.token;
190
- await loginWithToken(token);
191
- const payload = decodeJwtPayload(token);
192
- const email = payload?.email || 'unknown';
193
- const exp = payload?.exp ? payload.exp * 1000 : null;
194
-
195
- config.setAuth(token, email, exp, { local });
196
- if (local) config.ensureLocalGitignore();
197
- console.log('');
198
- ui.success(`Logged in as ${ui.c.bold(email)}`);
199
- if (local) ui.info('Credentials saved to project .securenow/ (local)');
200
- if (exp) {
201
- const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
202
- ui.info(`Session expires in ${days} days`);
203
- }
204
- return;
205
- }
206
-
207
- try {
208
- const token = await loginWithBrowser();
209
- const payload = decodeJwtPayload(token);
210
- const email = payload?.email || 'unknown';
211
- const exp = payload?.exp ? payload.exp * 1000 : null;
212
-
213
- config.setAuth(token, email, exp, { local });
214
- if (local) config.ensureLocalGitignore();
215
- console.log('');
216
- ui.success(`Logged in as ${ui.c.bold(email)}`);
217
- if (local) ui.info('Credentials saved to project .securenow/ (local)');
218
- if (exp) {
219
- const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
220
- ui.info(`Session expires in ${days} days`);
221
- }
222
- } catch (err) {
223
- if (err.message.includes('timed out')) {
224
- console.log('');
225
- ui.warn('Browser login timed out. You can also login with a token:');
226
- console.log('');
227
- console.log(` 1. Go to ${ui.c.cyan(config.getAppUrl() + '/dashboard/settings')}`);
228
- console.log(` 2. Copy your CLI token`);
229
- console.log(` 3. Run: ${ui.c.bold('securenow login --token <YOUR_TOKEN>')}`);
230
- console.log('');
231
- } else {
232
- throw err;
233
- }
234
- }
235
- }
236
-
237
- async function logout(args, flags) {
238
- const local = flags ? flags.local : undefined;
239
- const creds = config.loadCredentials();
240
- config.clearCredentials({ local });
241
- if (creds.email) {
242
- ui.success(`Logged out from ${ui.c.bold(creds.email)}`);
243
- } else {
244
- ui.success('Logged out');
245
- }
246
- if (local) ui.info('Cleared project-local credentials');
247
- }
248
-
249
- async function whoami() {
250
- const creds = config.loadCredentials();
251
- const token = config.getToken();
252
-
253
- if (!token) {
254
- ui.error('Not logged in. Run `securenow login` to authenticate.');
255
- process.exit(1);
256
- }
257
-
258
- const payload = decodeJwtPayload(token);
259
-
260
- ui.heading('Current Session');
261
- console.log('');
262
- const pairs = [
263
- ['Email', creds.email || payload?.email || 'unknown'],
264
- ['User ID', payload?.sub || 'unknown'],
265
- ['API', config.getApiUrl()],
266
- ['Auth Source', config.getAuthSource()],
267
- ];
268
- if (creds.expiresAt) {
269
- const days = Math.ceil((creds.expiresAt - Date.now()) / (1000 * 60 * 60 * 24));
270
- pairs.push(['Expires', days > 0 ? `in ${days} days` : ui.c.red('expired')]);
271
- }
272
- const defaultApp = config.getDefaultApp();
273
- if (defaultApp) {
274
- pairs.push(['Default App', defaultApp]);
275
- }
276
- ui.keyValue(pairs);
277
- console.log('');
278
- }
279
-
280
- module.exports = { login, logout, whoami };
package/cli/client.js DELETED
@@ -1,115 +0,0 @@
1
- 'use strict';
2
-
3
- const https = require('https');
4
- const http = require('http');
5
- const { URL } = require('url');
6
- const config = require('./config');
7
- const ui = require('./ui');
8
-
9
- function buildQueryString(params) {
10
- if (!params) return '';
11
- const entries = Object.entries(params).filter(([, v]) => v != null && v !== '');
12
- if (!entries.length) return '';
13
- return '?' + entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
14
- }
15
-
16
- function request(method, endpoint, { body, query, token, raw } = {}) {
17
- const baseUrl = config.getApiUrl();
18
- const qs = buildQueryString(query);
19
- const urlStr = `${baseUrl}/api${endpoint}${qs}`;
20
- const url = new URL(urlStr);
21
- const mod = url.protocol === 'https:' ? https : http;
22
-
23
- const authToken = token || config.getToken();
24
-
25
- return new Promise((resolve, reject) => {
26
- const options = {
27
- hostname: url.hostname,
28
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
29
- path: url.pathname + url.search,
30
- method,
31
- headers: { 'Content-Type': 'application/json', 'User-Agent': 'securenow-cli' },
32
- };
33
-
34
- if (authToken) {
35
- options.headers['Authorization'] = `Bearer ${authToken}`;
36
- }
37
-
38
- const req = mod.request(options, (res) => {
39
- let data = '';
40
- res.on('data', (chunk) => (data += chunk));
41
- res.on('end', () => {
42
- if (raw) {
43
- return resolve({ status: res.statusCode, headers: res.headers, body: data });
44
- }
45
-
46
- let parsed;
47
- try {
48
- parsed = JSON.parse(data);
49
- } catch {
50
- parsed = data;
51
- }
52
-
53
- if (res.statusCode === 401) {
54
- reject(new CLIError('Session expired. Run `securenow login` to re-authenticate.', 401));
55
- return;
56
- }
57
- if (res.statusCode === 403) {
58
- reject(new CLIError('Access denied. You may need to upgrade your plan.', 403));
59
- return;
60
- }
61
- if (res.statusCode >= 400) {
62
- const msg = parsed?.error || parsed?.message || `Request failed (HTTP ${res.statusCode})`;
63
- const details = parsed?.details || parsed?.unauthorizedKeys;
64
- const err = new CLIError(details ? `${msg} — ${details}` : msg, res.statusCode);
65
- reject(err);
66
- return;
67
- }
68
-
69
- resolve(parsed);
70
- });
71
- });
72
-
73
- req.on('error', (err) => {
74
- if (err.code === 'ECONNREFUSED') {
75
- reject(new CLIError(`Cannot connect to ${baseUrl}. Is the API server running?`));
76
- } else if (err.code === 'ENOTFOUND') {
77
- reject(new CLIError(`Cannot resolve ${url.hostname}. Check your internet connection.`));
78
- } else {
79
- reject(new CLIError(`Network error: ${err.message}`));
80
- }
81
- });
82
-
83
- if (body) {
84
- req.write(JSON.stringify(body));
85
- }
86
- req.end();
87
- });
88
- }
89
-
90
- class CLIError extends Error {
91
- constructor(message, statusCode) {
92
- super(message);
93
- this.name = 'CLIError';
94
- this.statusCode = statusCode;
95
- }
96
- }
97
-
98
- function requireAuth() {
99
- const token = config.getToken();
100
- if (!token) {
101
- ui.error('Not logged in. Run `securenow login` first.');
102
- process.exit(1);
103
- }
104
- return token;
105
- }
106
-
107
- const api = {
108
- get: (endpoint, opts) => request('GET', endpoint, opts),
109
- post: (endpoint, body, opts) => request('POST', endpoint, { body, ...opts }),
110
- put: (endpoint, body, opts) => request('PUT', endpoint, { body, ...opts }),
111
- patch: (endpoint, body, opts) => request('PATCH', endpoint, { body, ...opts }),
112
- delete: (endpoint, opts) => request('DELETE', endpoint, opts),
113
- };
114
-
115
- module.exports = { api, CLIError, requireAuth, buildQueryString };
package/cli/config.js DELETED
@@ -1,173 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
-
7
- const CONFIG_DIR = path.join(os.homedir(), '.securenow');
8
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
- const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json');
10
-
11
- const LOCAL_CONFIG_DIR = path.join(process.cwd(), '.securenow');
12
- const LOCAL_CONFIG_FILE = path.join(LOCAL_CONFIG_DIR, 'config.json');
13
- const LOCAL_CREDENTIALS_FILE = path.join(LOCAL_CONFIG_DIR, 'credentials.json');
14
-
15
- const DEFAULTS = {
16
- apiUrl: 'https://api.securenow.ai',
17
- appUrl: 'https://app.securenow.ai',
18
- defaultApp: null,
19
- output: 'table',
20
- };
21
-
22
- function ensureDir(dir) {
23
- if (!fs.existsSync(dir || CONFIG_DIR)) {
24
- fs.mkdirSync(dir || CONFIG_DIR, { recursive: true, mode: 0o700 });
25
- }
26
- }
27
-
28
- function loadJSON(filepath) {
29
- try {
30
- return JSON.parse(fs.readFileSync(filepath, 'utf8'));
31
- } catch {
32
- return {};
33
- }
34
- }
35
-
36
- function saveJSON(filepath, data) {
37
- ensureDir(path.dirname(filepath));
38
- fs.writeFileSync(filepath, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 });
39
- if (process.platform === 'win32') {
40
- try {
41
- const { execFileSync } = require('child_process');
42
- execFileSync('icacls', [filepath, '/inheritance:r', '/grant:r', `${process.env.USERNAME}:F`], { stdio: 'ignore' });
43
- } catch (_) {}
44
- }
45
- }
46
-
47
- function hasLocalCredentials() {
48
- return fs.existsSync(LOCAL_CREDENTIALS_FILE);
49
- }
50
-
51
- function resolveCredentialsFile() {
52
- if (fs.existsSync(LOCAL_CREDENTIALS_FILE)) return LOCAL_CREDENTIALS_FILE;
53
- return CREDENTIALS_FILE;
54
- }
55
-
56
- function getAuthSource() {
57
- if (process.env.SECURENOW_TOKEN) return 'env (SECURENOW_TOKEN)';
58
- if (fs.existsSync(LOCAL_CREDENTIALS_FILE)) return 'project (.securenow/)';
59
- return 'global (~/.securenow/)';
60
- }
61
-
62
- function loadConfig() {
63
- const global = loadJSON(CONFIG_FILE);
64
- const local = fs.existsSync(LOCAL_CONFIG_FILE) ? loadJSON(LOCAL_CONFIG_FILE) : {};
65
- if (hasLocalCredentials()) {
66
- const { defaultApp: _ignored, ...globalWithoutAccountScoped } = global;
67
- return { ...DEFAULTS, ...globalWithoutAccountScoped, ...local };
68
- }
69
- return { ...DEFAULTS, ...global, ...local };
70
- }
71
-
72
- function saveConfig(config, { local } = {}) {
73
- const useLocal = local === true || (local == null && hasLocalCredentials());
74
- const targetFile = useLocal ? LOCAL_CONFIG_FILE : CONFIG_FILE;
75
- const existing = loadJSON(targetFile);
76
- saveJSON(targetFile, { ...existing, ...config });
77
- }
78
-
79
- function getConfigValue(key) {
80
- const config = loadConfig();
81
- return config[key];
82
- }
83
-
84
- function setConfigValue(key, value) {
85
- saveConfig({ [key]: value });
86
- }
87
-
88
- function loadCredentials() {
89
- return loadJSON(resolveCredentialsFile());
90
- }
91
-
92
- function saveCredentials(creds, { local = false } = {}) {
93
- const targetFile = local ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
94
- saveJSON(targetFile, creds);
95
- }
96
-
97
- function clearCredentials({ local } = {}) {
98
- try {
99
- if (local === true) {
100
- fs.unlinkSync(LOCAL_CREDENTIALS_FILE);
101
- } else if (local === false || !hasLocalCredentials()) {
102
- fs.unlinkSync(CREDENTIALS_FILE);
103
- } else {
104
- fs.unlinkSync(LOCAL_CREDENTIALS_FILE);
105
- }
106
- } catch {}
107
- }
108
-
109
- function getToken() {
110
- if (process.env.SECURENOW_TOKEN) return process.env.SECURENOW_TOKEN;
111
-
112
- const creds = loadCredentials();
113
- if (!creds.token) return null;
114
-
115
- if (creds.expiresAt && Date.now() > creds.expiresAt) {
116
- return null;
117
- }
118
- return creds.token;
119
- }
120
-
121
- function setAuth(token, email, expiresAt, { local = false } = {}) {
122
- saveCredentials({ token, email, expiresAt }, { local });
123
- }
124
-
125
- function ensureLocalGitignore() {
126
- const gitignorePath = path.join(process.cwd(), '.gitignore');
127
- const entry = '.securenow/';
128
- try {
129
- if (fs.existsSync(gitignorePath)) {
130
- const content = fs.readFileSync(gitignorePath, 'utf8');
131
- if (!content.split('\n').some(line => line.trim() === entry)) {
132
- fs.appendFileSync(gitignorePath, `\n# SecureNow local credentials\n${entry}\n`);
133
- }
134
- } else {
135
- fs.writeFileSync(gitignorePath, `# SecureNow local credentials\n${entry}\n`);
136
- }
137
- } catch {}
138
- }
139
-
140
- function getApiUrl() {
141
- return process.env.SECURENOW_API_URL || loadConfig().apiUrl;
142
- }
143
-
144
- function getAppUrl() {
145
- return process.env.SECURENOW_APP_URL || loadConfig().appUrl;
146
- }
147
-
148
- function getDefaultApp() {
149
- return process.env.SECURENOW_APP || loadConfig().defaultApp;
150
- }
151
-
152
- module.exports = {
153
- CONFIG_DIR,
154
- CONFIG_FILE,
155
- CREDENTIALS_FILE,
156
- LOCAL_CONFIG_DIR,
157
- LOCAL_CREDENTIALS_FILE,
158
- loadConfig,
159
- saveConfig,
160
- getConfigValue,
161
- setConfigValue,
162
- loadCredentials,
163
- saveCredentials,
164
- clearCredentials,
165
- getToken,
166
- setAuth,
167
- getAuthSource,
168
- hasLocalCredentials,
169
- ensureLocalGitignore,
170
- getApiUrl,
171
- getAppUrl,
172
- getDefaultApp,
173
- };
package/cli/firewall.js DELETED
@@ -1,100 +0,0 @@
1
- 'use strict';
2
-
3
- const { api, requireAuth } = require('./client');
4
- const ui = require('./ui');
5
-
6
- async function status(args, flags) {
7
- requireAuth();
8
- const s = ui.spinner('Checking firewall status');
9
-
10
- try {
11
- const data = await api.get('/firewall/status');
12
-
13
- s.stop('Firewall status retrieved');
14
-
15
- if (flags.json) {
16
- ui.json(data);
17
- return;
18
- }
19
-
20
- console.log('');
21
- console.log(` ${ui.c.bold(ui.c.green('Firewall: ENABLED'))}`);
22
- console.log('');
23
- ui.keyValue([
24
- ['Blocked IPs', `${data.totalIps} total (${data.exactCount} exact + ${data.cidrCount} CIDR ranges)`],
25
- ['Last updated', data.updatedAt || 'unknown'],
26
- ['Allowed IPs', data.allowlistCount != null ? `${data.allowlistCount} total (${data.allowlistExactCount} exact + ${data.allowlistCidrCount} CIDR ranges)` : '0'],
27
- ['Allowlist updated', data.allowlistUpdatedAt || 'never'],
28
- ['Sync TTL', `${data.ttl || 60}s`],
29
- ]);
30
- if (data.allowlistCount > 0) {
31
- console.log('');
32
- console.log(` ${ui.c.yellow('!')} Allowlist is active — only ${data.allowlistCount} IP(s) can reach your app`);
33
- }
34
- console.log('');
35
- console.log(` ${ui.c.dim('Manage your blocklist:')} securenow blocklist`);
36
- console.log(` ${ui.c.dim('Manage your allowlist:')} securenow allowlist`);
37
- console.log(` ${ui.c.dim('Test an IP:')} securenow firewall test-ip <ip>`);
38
- console.log('');
39
- } catch (err) {
40
- if (err.statusCode === 401 || err.status === 401) {
41
- s.fail('Authentication failed');
42
- ui.error('API key is invalid or missing the firewall:read scope.');
43
- ui.info('Create an API key with firewall:read scope in the dashboard.');
44
- } else {
45
- s.fail('Failed to check firewall status');
46
- throw err;
47
- }
48
- }
49
- }
50
-
51
- async function testIp(args, flags) {
52
- requireAuth();
53
- const ip = args[0];
54
- if (!ip) {
55
- ui.error('Usage: securenow firewall test-ip <ip-address>');
56
- process.exit(1);
57
- }
58
-
59
- const s = ui.spinner(`Testing IP ${ip}`);
60
-
61
- try {
62
- const data = await api.get(`/firewall/check/${encodeURIComponent(ip)}`);
63
-
64
- s.stop(`IP ${ip} checked`);
65
-
66
- if (flags.json) {
67
- ui.json(data);
68
- return;
69
- }
70
-
71
- console.log('');
72
- if (data.blocked) {
73
- if (data.allowlistActive && !data.allowlisted) {
74
- console.log(` ${ui.c.bold(ui.c.red('BLOCKED'))} — ${ip} is not on the allowlist`);
75
- console.log(` ${ui.c.dim('Allowlist is active — only listed IPs are permitted')}`);
76
- } else {
77
- console.log(` ${ui.c.bold(ui.c.red('BLOCKED'))} — ${ip} is in the blocklist`);
78
- if (data.matchedEntry && data.matchedEntry !== ip) {
79
- console.log(` ${ui.c.dim(`Matched by: ${data.matchedEntry}`)}`);
80
- }
81
- }
82
- } else {
83
- if (data.allowlisted) {
84
- console.log(` ${ui.c.bold(ui.c.green('ALLOWED'))} — ${ip} is on the allowlist`);
85
- } else {
86
- console.log(` ${ui.c.bold(ui.c.green('ALLOWED'))} — ${ip} is not in the blocklist`);
87
- }
88
- }
89
- console.log(` ${ui.c.dim(`Blocklist contains ${data.totalBlockedIps} entries`)}`);
90
- if (data.allowlistActive) {
91
- console.log(` ${ui.c.dim('Allowlist is active')}`);
92
- }
93
- console.log('');
94
- } catch (err) {
95
- s.fail('Failed to test IP');
96
- throw err;
97
- }
98
- }
99
-
100
- module.exports = { status, testIp };