securenow 7.7.16 → 8.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.
- package/NPM_README.md +44 -36
- package/README.md +56 -38
- package/SKILL-API.md +51 -27
- package/SKILL-CLI.md +67 -45
- package/app-config.js +90 -160
- package/cli/apiKey.js +21 -12
- package/cli/apps.js +3 -3
- package/cli/auth.js +114 -32
- package/cli/client.js +14 -13
- package/cli/config.js +219 -52
- package/cli/credentials.js +4 -4
- package/cli/diagnostics.js +5 -6
- package/cli/firewall.js +19 -7
- package/cli/human.js +13 -8
- package/cli/init.js +5 -5
- package/cli/run.js +1 -5
- package/cli/security.js +31 -11
- package/cli/utils.js +2 -3
- package/cli.js +68 -35
- package/console-instrumentation.js +1 -1
- package/firewall-only.js +7 -11
- package/firewall.js +110 -35
- package/mcp/catalog.js +582 -45
- package/mcp/server.js +73 -12
- package/nextjs-auto-capture.js +3 -6
- package/nextjs-middleware.js +2 -4
- package/nextjs-wrapper.js +3 -6
- package/nextjs.js +4 -11
- package/nuxt-server-plugin.mjs +7 -4
- package/otel-defaults.js +11 -0
- package/package.json +3 -3
- package/rate-limits.js +0 -2
- package/register-vite.js +5 -12
- package/register.js +5 -13
- package/resolve-ip.js +1 -1
- package/tracing.d.ts +1 -1
- package/tracing.js +6 -3
- package/web-vite.mjs +58 -62
package/cli/auth.js
CHANGED
|
@@ -50,9 +50,10 @@ function decodeJwtPayload(token) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
async function loginWithBrowser() {
|
|
53
|
+
async function loginWithBrowser(options = {}) {
|
|
54
54
|
const appUrl = config.getAppUrl();
|
|
55
55
|
const nonce = crypto.randomBytes(24).toString('base64url');
|
|
56
|
+
const mode = options.mode || null;
|
|
56
57
|
|
|
57
58
|
return new Promise((resolve, reject) => {
|
|
58
59
|
let pendingToken = null;
|
|
@@ -108,7 +109,7 @@ async function loginWithBrowser() {
|
|
|
108
109
|
const email = payload?.email || 'unknown account';
|
|
109
110
|
const safeEmail = email.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
110
111
|
const port = server.address().port;
|
|
111
|
-
const switchUrl = buildCliAuthUrl(appUrl, port, nonce, { force_login: 1 });
|
|
112
|
+
const switchUrl = buildCliAuthUrl(appUrl, port, nonce, { force_login: 1, ...(mode ? { mode } : {}) });
|
|
112
113
|
|
|
113
114
|
res.end([
|
|
114
115
|
'<!DOCTYPE html><html><head><meta charset="utf-8"><title>SecureNow CLI Login</title></head>',
|
|
@@ -181,7 +182,7 @@ async function loginWithBrowser() {
|
|
|
181
182
|
|
|
182
183
|
server.listen(0, '127.0.0.1', () => {
|
|
183
184
|
const port = server.address().port;
|
|
184
|
-
const authUrl = buildCliAuthUrl(appUrl, port, nonce);
|
|
185
|
+
const authUrl = buildCliAuthUrl(appUrl, port, nonce, mode ? { mode } : {});
|
|
185
186
|
|
|
186
187
|
console.log('');
|
|
187
188
|
ui.info('Opening browser for authentication...');
|
|
@@ -200,7 +201,7 @@ async function loginWithBrowser() {
|
|
|
200
201
|
|
|
201
202
|
const timeout = setTimeout(() => {
|
|
202
203
|
closeServer();
|
|
203
|
-
reject(new CLIError('Login timed out after 5 minutes. Try `securenow login --token <TOKEN>` instead.'));
|
|
204
|
+
reject(new CLIError('Login timed out after 5 minutes. Try `securenow admin login --token <TOKEN>` instead.'));
|
|
204
205
|
}, 5 * 60 * 1000);
|
|
205
206
|
|
|
206
207
|
server.on('close', () => clearTimeout(timeout));
|
|
@@ -235,7 +236,7 @@ async function login(args, flags) {
|
|
|
235
236
|
const email = payload?.email || 'unknown';
|
|
236
237
|
const exp = payload?.exp ? payload.exp * 1000 : null;
|
|
237
238
|
|
|
238
|
-
config.setAuth(token, email, exp, { local
|
|
239
|
+
config.setAuth(token, email, exp, { local });
|
|
239
240
|
if (local) config.ensureLocalGitignore();
|
|
240
241
|
console.log('');
|
|
241
242
|
ui.success(`Logged in as ${ui.c.bold(email)}`);
|
|
@@ -253,17 +254,28 @@ async function login(args, flags) {
|
|
|
253
254
|
const email = payload?.email || 'unknown';
|
|
254
255
|
const exp = payload?.exp ? payload.exp * 1000 : null;
|
|
255
256
|
|
|
256
|
-
config.setAuth(token, email, exp, { local
|
|
257
|
-
if (
|
|
257
|
+
config.setAuth(token, email, exp, { local });
|
|
258
|
+
if (app && (app.key || app.name)) {
|
|
259
|
+
config.setRuntime({
|
|
260
|
+
apiKey: apiKey || config.getApiKey() || null,
|
|
261
|
+
app: {
|
|
262
|
+
key: app.key || null,
|
|
263
|
+
name: app.name || null,
|
|
264
|
+
},
|
|
265
|
+
}, { local });
|
|
266
|
+
}
|
|
258
267
|
if (local) config.ensureLocalGitignore();
|
|
259
268
|
console.log('');
|
|
260
269
|
ui.success(`Logged in as ${ui.c.bold(email)}`);
|
|
261
|
-
ui.info(local ? '
|
|
270
|
+
ui.info(local ? 'Admin auth saved to project .securenow/admin.json' : 'Admin auth saved to ~/.securenow/admin.json');
|
|
262
271
|
if (app && (app.name || app.key)) {
|
|
263
272
|
ui.info(`Linked to app ${ui.c.bold(app.name || app.key)}${app.key ? ` (${ui.c.dim(app.key)})` : ''}`);
|
|
273
|
+
ui.info(local ? 'Runtime app config saved to project .securenow/runtime.json' : 'Runtime app config saved to ~/.securenow/runtime.json');
|
|
274
|
+
} else {
|
|
275
|
+
ui.info('Runtime app config was not changed.');
|
|
264
276
|
}
|
|
265
277
|
if (apiKey) {
|
|
266
|
-
ui.info(`
|
|
278
|
+
ui.info(`Runtime API key saved — telemetry ingestion and firewall sync will authenticate automatically on next start`);
|
|
267
279
|
}
|
|
268
280
|
if (exp) {
|
|
269
281
|
const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
|
|
@@ -276,7 +288,7 @@ async function login(args, flags) {
|
|
|
276
288
|
console.log('');
|
|
277
289
|
console.log(` 1. Go to ${ui.c.cyan(config.getAppUrl() + '/dashboard/settings')}`);
|
|
278
290
|
console.log(` 2. Copy your CLI token`);
|
|
279
|
-
console.log(` 3. Run: ${ui.c.bold('securenow login --token <YOUR_TOKEN>')}`);
|
|
291
|
+
console.log(` 3. Run: ${ui.c.bold('securenow admin login --token <YOUR_TOKEN>')}`);
|
|
280
292
|
console.log('');
|
|
281
293
|
} else {
|
|
282
294
|
throw err;
|
|
@@ -284,48 +296,118 @@ async function login(args, flags) {
|
|
|
284
296
|
}
|
|
285
297
|
}
|
|
286
298
|
|
|
299
|
+
async function adminLogin(args, flags) {
|
|
300
|
+
const local = flags.global ? false : true;
|
|
301
|
+
|
|
302
|
+
if (flags.token) {
|
|
303
|
+
const token = flags.token;
|
|
304
|
+
await loginWithToken(token);
|
|
305
|
+
const payload = decodeJwtPayload(token);
|
|
306
|
+
const email = payload?.email || 'unknown';
|
|
307
|
+
const exp = payload?.exp ? payload.exp * 1000 : null;
|
|
308
|
+
config.setAuth(token, email, exp, { local });
|
|
309
|
+
if (local) config.ensureLocalGitignore();
|
|
310
|
+
ui.success(`Admin auth connected as ${ui.c.bold(email)}`);
|
|
311
|
+
ui.info(local ? 'Saved to project .securenow/admin.json' : 'Saved to ~/.securenow/admin.json');
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { token } = await loginWithBrowser({ mode: 'admin' });
|
|
316
|
+
const payload = decodeJwtPayload(token);
|
|
317
|
+
const email = payload?.email || 'unknown';
|
|
318
|
+
const exp = payload?.exp ? payload.exp * 1000 : null;
|
|
319
|
+
config.setAuth(token, email, exp, { local });
|
|
320
|
+
if (local) config.ensureLocalGitignore();
|
|
321
|
+
ui.success(`Admin auth connected as ${ui.c.bold(email)}`);
|
|
322
|
+
ui.info(local ? 'Saved to project .securenow/admin.json' : 'Saved to ~/.securenow/admin.json');
|
|
323
|
+
ui.info('Runtime app config was not changed.');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function appConnect(args, flags) {
|
|
327
|
+
const local = flags.global ? false : true;
|
|
328
|
+
const { app, apiKey } = await loginWithBrowser({ mode: 'runtime' });
|
|
329
|
+
|
|
330
|
+
if (!app || !app.key) {
|
|
331
|
+
throw new CLIError('No app was selected. Runtime app config was not changed.');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
config.setRuntime({
|
|
335
|
+
apiKey: apiKey || config.getApiKey() || null,
|
|
336
|
+
app: {
|
|
337
|
+
key: app.key,
|
|
338
|
+
name: app.name || null,
|
|
339
|
+
},
|
|
340
|
+
}, { local });
|
|
341
|
+
if (local) config.ensureLocalGitignore();
|
|
342
|
+
|
|
343
|
+
ui.success(`Runtime app connected to ${ui.c.bold(app.name || app.key)}`);
|
|
344
|
+
ui.info(local ? 'Saved to project .securenow/runtime.json' : 'Saved to ~/.securenow/runtime.json');
|
|
345
|
+
if (apiKey) {
|
|
346
|
+
ui.info('Runtime API key saved; SDK telemetry and firewall sync can run without an admin token.');
|
|
347
|
+
} else {
|
|
348
|
+
ui.warn('No runtime API key was returned. Run `securenow api-key create` before sending telemetry.');
|
|
349
|
+
}
|
|
350
|
+
ui.info('Admin auth was not changed.');
|
|
351
|
+
}
|
|
352
|
+
|
|
287
353
|
async function logout(args, flags) {
|
|
288
354
|
// Default: clear project-local. --global clears ~/.securenow/.
|
|
289
355
|
const local = !(flags && flags.global);
|
|
290
|
-
const creds = config.
|
|
356
|
+
const creds = config.loadAdminCredentials();
|
|
291
357
|
config.clearCredentials({ local });
|
|
292
358
|
if (creds.email) {
|
|
293
|
-
ui.success(`
|
|
359
|
+
ui.success(`Admin auth logged out from ${ui.c.bold(creds.email)}`);
|
|
294
360
|
} else {
|
|
295
|
-
ui.success('
|
|
361
|
+
ui.success('Admin auth logged out');
|
|
296
362
|
}
|
|
297
|
-
ui.info(local ? 'Cleared project-local
|
|
363
|
+
ui.info(local ? 'Cleared project-local admin auth (.securenow/admin.json)' : 'Cleared global admin auth (~/.securenow/admin.json)');
|
|
364
|
+
ui.info('Runtime app config was not changed.');
|
|
298
365
|
}
|
|
299
366
|
|
|
300
367
|
async function whoami() {
|
|
301
|
-
const
|
|
368
|
+
const admin = config.loadAdminCredentials();
|
|
369
|
+
const runtime = config.loadRuntimeCredentials();
|
|
302
370
|
const token = config.getToken();
|
|
303
371
|
|
|
304
|
-
if (!token) {
|
|
305
|
-
ui.error('Not logged in. Run `securenow login` to authenticate.');
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
372
|
const payload = decodeJwtPayload(token);
|
|
310
373
|
|
|
311
|
-
ui.heading('
|
|
374
|
+
ui.heading('SecureNow Connection Status');
|
|
312
375
|
console.log('');
|
|
313
|
-
const
|
|
314
|
-
['
|
|
315
|
-
['
|
|
316
|
-
['
|
|
317
|
-
['Auth Source', config.getAuthSource()],
|
|
376
|
+
const adminPairs = [
|
|
377
|
+
['Status', token ? ui.c.green('connected') : ui.c.red('not connected')],
|
|
378
|
+
['Email', token ? (admin.email || payload?.email || 'unknown') : '—'],
|
|
379
|
+
['User ID', token ? (payload?.sub || 'unknown') : '—'],
|
|
380
|
+
['Auth Source', token ? config.getAuthSource() : '—'],
|
|
381
|
+
['System-rule admin', token ? 'server-enforced; use admin tools to verify' : 'no admin token'],
|
|
318
382
|
];
|
|
319
|
-
if (
|
|
320
|
-
const days = Math.ceil((
|
|
321
|
-
|
|
383
|
+
if (admin.expiresAt) {
|
|
384
|
+
const days = Math.ceil((admin.expiresAt - Date.now()) / (1000 * 60 * 60 * 24));
|
|
385
|
+
adminPairs.push(['Expires', days > 0 ? `in ${days} days` : ui.c.red('expired')]);
|
|
322
386
|
}
|
|
387
|
+
|
|
388
|
+
ui.heading('Admin CLI / MCP');
|
|
389
|
+
ui.keyValue(adminPairs);
|
|
390
|
+
console.log('');
|
|
391
|
+
|
|
392
|
+
const runtimePairs = [
|
|
393
|
+
['Status', runtime.app?.key ? ui.c.green('connected') : ui.c.red('no app selected')],
|
|
394
|
+
['App', runtime.app?.name || '—'],
|
|
395
|
+
['App Key', runtime.app?.key || '—'],
|
|
396
|
+
['Runtime API Key', runtime.apiKey ? ui.c.green('present') : ui.c.yellow('missing')],
|
|
397
|
+
['Environment', runtime.config?.runtime?.deploymentEnvironment || 'production'],
|
|
398
|
+
['Runtime Source', config.getRuntimeSource()],
|
|
399
|
+
['API', config.getApiUrl()],
|
|
400
|
+
];
|
|
323
401
|
const defaultApp = config.getDefaultApp();
|
|
324
402
|
if (defaultApp) {
|
|
325
|
-
|
|
403
|
+
runtimePairs.push(['CLI Default App', defaultApp]);
|
|
326
404
|
}
|
|
327
|
-
ui.
|
|
405
|
+
ui.heading('SDK Runtime');
|
|
406
|
+
ui.keyValue(runtimePairs);
|
|
328
407
|
console.log('');
|
|
408
|
+
|
|
409
|
+
if (!token) ui.info('Run `securenow admin login` for admin/control-plane CLI and MCP tools.');
|
|
410
|
+
if (!runtime.app?.key) ui.info('Run `securenow app connect` to select an app and write SDK runtime config.');
|
|
329
411
|
}
|
|
330
412
|
|
|
331
|
-
module.exports = { login, logout, whoami };
|
|
413
|
+
module.exports = { login, logout, whoami, adminLogin, appConnect, loginWithBrowser };
|
package/cli/client.js
CHANGED
|
@@ -50,14 +50,15 @@ function request(method, endpoint, { body, query, token, raw } = {}) {
|
|
|
50
50
|
parsed = data;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
if (res.statusCode === 401) {
|
|
54
|
-
reject(new CLIError('
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (res.statusCode === 403) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
if (res.statusCode === 401) {
|
|
54
|
+
reject(new CLIError('Admin auth is missing or expired. Run `securenow admin login` to re-authenticate. Runtime app credentials are unrelated.', 401));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (res.statusCode === 403) {
|
|
58
|
+
const msg = parsed?.error || parsed?.message || 'Access denied';
|
|
59
|
+
reject(new CLIError(`${msg}. Admin auth is connected, but this user/plan may lack permission for this control-plane operation. Runtime app connection is unrelated.`, 403));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
61
62
|
if (res.statusCode >= 400) {
|
|
62
63
|
const msg = parsed?.error || parsed?.message || `Request failed (HTTP ${res.statusCode})`;
|
|
63
64
|
const details = parsed?.details || parsed?.unauthorizedKeys;
|
|
@@ -96,11 +97,11 @@ class CLIError extends Error {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
function requireAuth() {
|
|
99
|
-
const token = config.getToken();
|
|
100
|
-
if (!token) {
|
|
101
|
-
ui.error('
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
100
|
+
const token = config.getToken();
|
|
101
|
+
if (!token) {
|
|
102
|
+
ui.error('Admin auth is not connected. Run `securenow admin login` first.');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
104
105
|
return token;
|
|
105
106
|
}
|
|
106
107
|
|
package/cli/config.js
CHANGED
|
@@ -8,10 +8,14 @@ const appConfig = require('../app-config');
|
|
|
8
8
|
const CONFIG_DIR = path.join(os.homedir(), '.securenow');
|
|
9
9
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
10
10
|
const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json');
|
|
11
|
+
const ADMIN_CREDENTIALS_FILE = path.join(CONFIG_DIR, 'admin.json');
|
|
12
|
+
const RUNTIME_CREDENTIALS_FILE = path.join(CONFIG_DIR, 'runtime.json');
|
|
11
13
|
|
|
12
14
|
const LOCAL_CONFIG_DIR = path.join(process.cwd(), '.securenow');
|
|
13
15
|
const LOCAL_CONFIG_FILE = path.join(LOCAL_CONFIG_DIR, 'config.json');
|
|
14
16
|
const LOCAL_CREDENTIALS_FILE = path.join(LOCAL_CONFIG_DIR, 'credentials.json');
|
|
17
|
+
const LOCAL_ADMIN_CREDENTIALS_FILE = path.join(LOCAL_CONFIG_DIR, 'admin.json');
|
|
18
|
+
const LOCAL_RUNTIME_CREDENTIALS_FILE = path.join(LOCAL_CONFIG_DIR, 'runtime.json');
|
|
15
19
|
|
|
16
20
|
const DEFAULTS = {
|
|
17
21
|
apiUrl: 'https://api.securenow.ai',
|
|
@@ -57,18 +61,89 @@ function credentialsFileForLocal(local) {
|
|
|
57
61
|
return local ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
function adminCredentialsFileForLocal(local) {
|
|
65
|
+
return local ? LOCAL_ADMIN_CREDENTIALS_FILE : ADMIN_CREDENTIALS_FILE;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function runtimeCredentialsFileForLocal(local) {
|
|
69
|
+
return local ? LOCAL_RUNTIME_CREDENTIALS_FILE : RUNTIME_CREDENTIALS_FILE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeRuntimeCredentials(creds) {
|
|
73
|
+
const payload = { ...(creds || {}) };
|
|
74
|
+
delete payload.token;
|
|
75
|
+
delete payload.email;
|
|
76
|
+
delete payload.expiresAt;
|
|
77
|
+
delete payload.admin;
|
|
78
|
+
return appConfig.withCredentialDefaults(payload) || {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeAdminCredentials(creds) {
|
|
82
|
+
const payload = { ...(creds || {}) };
|
|
83
|
+
delete payload.apiKey;
|
|
84
|
+
delete payload.app;
|
|
85
|
+
delete payload.config;
|
|
86
|
+
delete payload.runtime;
|
|
87
|
+
return payload;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function splitCredentialDocument(raw = {}) {
|
|
91
|
+
const doc = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
92
|
+
const runtime = doc.runtime && typeof doc.runtime === 'object' && !Array.isArray(doc.runtime)
|
|
93
|
+
? doc.runtime
|
|
94
|
+
: doc;
|
|
95
|
+
const admin = doc.admin && typeof doc.admin === 'object' && !Array.isArray(doc.admin)
|
|
96
|
+
? doc.admin
|
|
97
|
+
: doc;
|
|
98
|
+
return { runtime, admin };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function pickExistingFile(...files) {
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
try {
|
|
104
|
+
if (file && fs.existsSync(file)) return file;
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
60
110
|
function hasLocalCredentials() {
|
|
61
|
-
return !!
|
|
111
|
+
return !!(
|
|
112
|
+
appConfig.resolveLocalCredentialsFile() ||
|
|
113
|
+
appConfig.resolveLocalAdminCredentialsFile()
|
|
114
|
+
);
|
|
62
115
|
}
|
|
63
116
|
|
|
64
117
|
function resolveCredentialsFile() {
|
|
65
118
|
return appConfig.resolveLocalCredentialsFile() || appConfig.resolveGlobalCredentialsFile() || CREDENTIALS_FILE;
|
|
66
119
|
}
|
|
67
120
|
|
|
121
|
+
function resolveAdminCredentialsFile() {
|
|
122
|
+
return appConfig.resolveLocalAdminCredentialsFile() || appConfig.resolveGlobalAdminCredentialsFile() || ADMIN_CREDENTIALS_FILE;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveRuntimeCredentialsFile() {
|
|
126
|
+
return appConfig.resolveLocalCredentialsFile() || appConfig.resolveGlobalCredentialsFile() || RUNTIME_CREDENTIALS_FILE;
|
|
127
|
+
}
|
|
128
|
+
|
|
68
129
|
function getAuthSource() {
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
130
|
+
const file = appConfig.resolveLocalAdminCredentialsFile() || appConfig.resolveGlobalAdminCredentialsFile();
|
|
131
|
+
if (!file) return 'not configured';
|
|
132
|
+
if (file.includes(`${path.sep}.securenow${path.sep}`) && file.startsWith(process.cwd())) {
|
|
133
|
+
return file.endsWith('admin.json') ? 'project admin (.securenow/admin.json)' : 'project legacy (.securenow/credentials.json)';
|
|
134
|
+
}
|
|
135
|
+
if (file.endsWith('admin.json')) return 'global admin (~/.securenow/admin.json)';
|
|
136
|
+
return 'global legacy (~/.securenow/credentials.json)';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getRuntimeSource() {
|
|
140
|
+
const file = appConfig.resolveLocalCredentialsFile() || appConfig.resolveGlobalCredentialsFile();
|
|
141
|
+
if (!file) return 'not configured';
|
|
142
|
+
if (file.includes(`${path.sep}.securenow${path.sep}`) && file.startsWith(process.cwd())) {
|
|
143
|
+
return file.endsWith('runtime.json') ? 'project runtime (.securenow/runtime.json)' : 'project legacy (.securenow/credentials.json)';
|
|
144
|
+
}
|
|
145
|
+
if (file.endsWith('runtime.json')) return 'global runtime (~/.securenow/runtime.json)';
|
|
146
|
+
return 'global legacy (~/.securenow/credentials.json)';
|
|
72
147
|
}
|
|
73
148
|
|
|
74
149
|
function loadConfig() {
|
|
@@ -98,14 +173,109 @@ function setConfigValue(key, value) {
|
|
|
98
173
|
}
|
|
99
174
|
|
|
100
175
|
function loadCredentials() {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
176
|
+
const legacy = splitCredentialDocument(loadJSON(resolveCredentialsFile()));
|
|
177
|
+
const runtime = loadRuntimeCredentials();
|
|
178
|
+
const admin = loadAdminCredentials();
|
|
179
|
+
return appConfig.mergeCredentials(
|
|
180
|
+
normalizeRuntimeCredentials(runtime || legacy.runtime),
|
|
181
|
+
normalizeAdminCredentials(admin || legacy.admin)
|
|
182
|
+
) || {};
|
|
104
183
|
}
|
|
105
184
|
|
|
106
185
|
function saveCredentials(creds, { local = false } = {}) {
|
|
107
|
-
|
|
108
|
-
|
|
186
|
+
saveRuntimeCredentials(creds, { local });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function loadAdminCredentials() {
|
|
190
|
+
const adminFile = pickExistingFile(
|
|
191
|
+
appConfig.resolveLocalAdminCredentialsFile(),
|
|
192
|
+
appConfig.resolveGlobalAdminCredentialsFile()
|
|
193
|
+
);
|
|
194
|
+
if (!adminFile) return {};
|
|
195
|
+
const { admin } = splitCredentialDocument(loadJSON(adminFile));
|
|
196
|
+
return normalizeAdminCredentials(admin);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function saveAdminCredentials(creds, { local = false } = {}) {
|
|
200
|
+
const targetFile = adminCredentialsFileForLocal(local);
|
|
201
|
+
const existing = normalizeAdminCredentials(loadJSON(targetFile));
|
|
202
|
+
const payload = normalizeAdminCredentials({ ...existing, ...(creds || {}) });
|
|
203
|
+
payload._securenow = {
|
|
204
|
+
...(payload._securenow || {}),
|
|
205
|
+
schemaVersion: appConfig.CONFIG_SCHEMA_VERSION,
|
|
206
|
+
note: 'SecureNow admin/control-plane CLI and MCP auth. This file may contain a user session token; do not commit it.',
|
|
207
|
+
runtimeSeparate: 'SDK runtime app credentials live in runtime.json and are not changed by admin login/logout.',
|
|
208
|
+
};
|
|
209
|
+
saveJSON(targetFile, payload);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function stripAdminFromCredentialFile(filepath) {
|
|
213
|
+
try {
|
|
214
|
+
if (!filepath || !fs.existsSync(filepath)) return;
|
|
215
|
+
const doc = loadJSON(filepath);
|
|
216
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return;
|
|
217
|
+
|
|
218
|
+
let changed = false;
|
|
219
|
+
if (doc.admin) {
|
|
220
|
+
delete doc.admin;
|
|
221
|
+
changed = true;
|
|
222
|
+
}
|
|
223
|
+
for (const field of ['token', 'email', 'expiresAt', 'roles', 'plan', 'user', 'userId']) {
|
|
224
|
+
if (Object.prototype.hasOwnProperty.call(doc, field)) {
|
|
225
|
+
delete doc[field];
|
|
226
|
+
changed = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (changed) saveJSON(filepath, doc);
|
|
230
|
+
} catch {}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function clearAdminCredentials({ local } = {}) {
|
|
234
|
+
try {
|
|
235
|
+
const useLocal = local === true || (local == null && pickExistingFile(LOCAL_ADMIN_CREDENTIALS_FILE, LOCAL_CREDENTIALS_FILE));
|
|
236
|
+
if (useLocal) {
|
|
237
|
+
fs.unlinkSync(LOCAL_ADMIN_CREDENTIALS_FILE);
|
|
238
|
+
} else {
|
|
239
|
+
fs.unlinkSync(ADMIN_CREDENTIALS_FILE);
|
|
240
|
+
}
|
|
241
|
+
} catch {}
|
|
242
|
+
if (local === true) {
|
|
243
|
+
stripAdminFromCredentialFile(LOCAL_CREDENTIALS_FILE);
|
|
244
|
+
} else if (local === false) {
|
|
245
|
+
stripAdminFromCredentialFile(CREDENTIALS_FILE);
|
|
246
|
+
} else if (pickExistingFile(LOCAL_CREDENTIALS_FILE)) {
|
|
247
|
+
stripAdminFromCredentialFile(LOCAL_CREDENTIALS_FILE);
|
|
248
|
+
} else {
|
|
249
|
+
stripAdminFromCredentialFile(CREDENTIALS_FILE);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function loadRuntimeCredentials() {
|
|
254
|
+
const runtimeFile = pickExistingFile(
|
|
255
|
+
appConfig.resolveLocalCredentialsFile(),
|
|
256
|
+
appConfig.resolveGlobalCredentialsFile()
|
|
257
|
+
);
|
|
258
|
+
if (!runtimeFile) return {};
|
|
259
|
+
const { runtime } = splitCredentialDocument(loadJSON(runtimeFile));
|
|
260
|
+
return normalizeRuntimeCredentials(runtime);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function saveRuntimeCredentials(creds, { local = false } = {}) {
|
|
264
|
+
const targetFile = runtimeCredentialsFileForLocal(local);
|
|
265
|
+
const existing = normalizeRuntimeCredentials(loadJSON(targetFile));
|
|
266
|
+
saveJSON(targetFile, normalizeRuntimeCredentials(appConfig.mergeCredentials(existing, creds || {}) || {}));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function clearRuntimeCredentials({ local } = {}) {
|
|
270
|
+
try {
|
|
271
|
+
if (local === true) {
|
|
272
|
+
fs.unlinkSync(LOCAL_RUNTIME_CREDENTIALS_FILE);
|
|
273
|
+
} else if (local === false || !pickExistingFile(LOCAL_RUNTIME_CREDENTIALS_FILE)) {
|
|
274
|
+
fs.unlinkSync(RUNTIME_CREDENTIALS_FILE);
|
|
275
|
+
} else {
|
|
276
|
+
fs.unlinkSync(LOCAL_RUNTIME_CREDENTIALS_FILE);
|
|
277
|
+
}
|
|
278
|
+
} catch {}
|
|
109
279
|
}
|
|
110
280
|
|
|
111
281
|
function withOnboardingFirewallEnabled(creds) {
|
|
@@ -118,8 +288,8 @@ function withOnboardingFirewallEnabled(creds) {
|
|
|
118
288
|
|
|
119
289
|
function ensureCredentialDefaults({ local, enableFirewall = false } = {}) {
|
|
120
290
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
121
|
-
const targetFile =
|
|
122
|
-
const existing = useLocal ?
|
|
291
|
+
const targetFile = runtimeCredentialsFileForLocal(useLocal);
|
|
292
|
+
const existing = useLocal ? loadRuntimeCredentials() : loadJSON(targetFile);
|
|
123
293
|
const payload = enableFirewall
|
|
124
294
|
? withOnboardingFirewallEnabled(existing || {})
|
|
125
295
|
: appConfig.withCredentialDefaults(existing || {}) || {};
|
|
@@ -127,21 +297,11 @@ function ensureCredentialDefaults({ local, enableFirewall = false } = {}) {
|
|
|
127
297
|
}
|
|
128
298
|
|
|
129
299
|
function clearCredentials({ local } = {}) {
|
|
130
|
-
|
|
131
|
-
if (local === true) {
|
|
132
|
-
fs.unlinkSync(LOCAL_CREDENTIALS_FILE);
|
|
133
|
-
} else if (local === false || !hasLocalCredentials()) {
|
|
134
|
-
fs.unlinkSync(CREDENTIALS_FILE);
|
|
135
|
-
} else {
|
|
136
|
-
fs.unlinkSync(LOCAL_CREDENTIALS_FILE);
|
|
137
|
-
}
|
|
138
|
-
} catch {}
|
|
300
|
+
clearAdminCredentials({ local });
|
|
139
301
|
}
|
|
140
302
|
|
|
141
303
|
function getToken() {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const creds = loadCredentials();
|
|
304
|
+
const creds = loadAdminCredentials();
|
|
145
305
|
if (!creds.token) return null;
|
|
146
306
|
|
|
147
307
|
if (creds.expiresAt && Date.now() > creds.expiresAt) {
|
|
@@ -150,66 +310,62 @@ function getToken() {
|
|
|
150
310
|
return creds.token;
|
|
151
311
|
}
|
|
152
312
|
|
|
153
|
-
function setAuth(token, email, expiresAt, { local = false
|
|
154
|
-
|
|
155
|
-
const payload = { ...loadJSON(targetFile), token, email, expiresAt };
|
|
156
|
-
if (app && (app.key || app.name)) {
|
|
157
|
-
payload.app = {
|
|
158
|
-
key: app.key || null,
|
|
159
|
-
name: app.name || null,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
saveJSON(
|
|
163
|
-
targetFile,
|
|
164
|
-
enableFirewall ? withOnboardingFirewallEnabled(payload) : appConfig.withCredentialDefaults(payload) || {}
|
|
165
|
-
);
|
|
313
|
+
function setAuth(token, email, expiresAt, { local = false } = {}) {
|
|
314
|
+
saveAdminCredentials({ token, email, expiresAt }, { local });
|
|
166
315
|
}
|
|
167
316
|
|
|
168
317
|
function getApp() {
|
|
169
|
-
const creds =
|
|
318
|
+
const creds = loadRuntimeCredentials();
|
|
170
319
|
return creds && creds.app ? creds.app : null;
|
|
171
320
|
}
|
|
172
321
|
|
|
173
322
|
function setApiKey(apiKey, { local } = {}) {
|
|
174
323
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
saveJSON(targetFile, withOnboardingFirewallEnabled({ ...existing, apiKey }));
|
|
324
|
+
const existing = loadRuntimeCredentials();
|
|
325
|
+
saveRuntimeCredentials(withOnboardingFirewallEnabled({ ...existing, apiKey }), { local: useLocal });
|
|
178
326
|
}
|
|
179
327
|
|
|
180
328
|
function clearApiKey({ local } = {}) {
|
|
181
329
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
182
|
-
const
|
|
183
|
-
const existing = loadJSON(targetFile);
|
|
330
|
+
const existing = loadRuntimeCredentials();
|
|
184
331
|
if (!existing || !existing.apiKey) return;
|
|
185
332
|
delete existing.apiKey;
|
|
186
|
-
|
|
333
|
+
saveRuntimeCredentials(appConfig.withCredentialDefaults(existing) || existing, { local: useLocal });
|
|
187
334
|
}
|
|
188
335
|
|
|
189
336
|
function getApiKey() {
|
|
190
|
-
const creds =
|
|
337
|
+
const creds = loadRuntimeCredentials();
|
|
191
338
|
return creds && creds.apiKey ? creds.apiKey : null;
|
|
192
339
|
}
|
|
193
340
|
|
|
194
341
|
function setApp(app, { local } = {}) {
|
|
195
342
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
saveJSON(targetFile, appConfig.withCredentialDefaults({
|
|
343
|
+
const existing = loadRuntimeCredentials();
|
|
344
|
+
saveRuntimeCredentials(appConfig.withCredentialDefaults({
|
|
199
345
|
...existing,
|
|
200
346
|
app: {
|
|
201
347
|
key: app.key || null,
|
|
202
348
|
name: app.name || null,
|
|
203
349
|
},
|
|
204
|
-
}) || {});
|
|
350
|
+
}) || {}, { local: useLocal });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function setRuntime(runtime, { local } = {}) {
|
|
354
|
+
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
355
|
+
const existing = loadRuntimeCredentials();
|
|
356
|
+
saveRuntimeCredentials(withOnboardingFirewallEnabled(appConfig.mergeCredentials(existing, runtime || {}) || {}), { local: useLocal });
|
|
205
357
|
}
|
|
206
358
|
|
|
207
359
|
function ensureLocalGitignore() {
|
|
208
360
|
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
209
361
|
const legacyEntry = '.securenow/';
|
|
210
362
|
const entries = [
|
|
363
|
+
'.securenow/admin.json',
|
|
364
|
+
'.securenow/runtime.json',
|
|
211
365
|
'.securenow/credentials.json',
|
|
212
366
|
'.securenow/credentials.*.json',
|
|
367
|
+
'!.securenow/admin.example.json',
|
|
368
|
+
'!.securenow/runtime.example.json',
|
|
213
369
|
'!.securenow/credentials.example.json',
|
|
214
370
|
'!.securenow/credentials.*.example.json',
|
|
215
371
|
];
|
|
@@ -250,41 +406,52 @@ function ensureLocalGitignore() {
|
|
|
250
406
|
}
|
|
251
407
|
|
|
252
408
|
function getApiUrl() {
|
|
253
|
-
if (process.env.SECURENOW_API_URL) return process.env.SECURENOW_API_URL;
|
|
254
409
|
const cfg = loadConfig();
|
|
255
410
|
if (cfg.apiUrl && cfg.apiUrl !== DEFAULTS.apiUrl) return cfg.apiUrl;
|
|
256
|
-
return
|
|
411
|
+
return cfg.apiUrl;
|
|
257
412
|
}
|
|
258
413
|
|
|
259
414
|
function getAppUrl() {
|
|
260
|
-
return
|
|
415
|
+
return loadConfig().appUrl;
|
|
261
416
|
}
|
|
262
417
|
|
|
263
418
|
function getDefaultApp() {
|
|
264
|
-
return
|
|
419
|
+
return loadConfig().defaultApp;
|
|
265
420
|
}
|
|
266
421
|
|
|
267
422
|
module.exports = {
|
|
268
423
|
CONFIG_DIR,
|
|
269
424
|
CONFIG_FILE,
|
|
270
425
|
CREDENTIALS_FILE,
|
|
426
|
+
ADMIN_CREDENTIALS_FILE,
|
|
427
|
+
RUNTIME_CREDENTIALS_FILE,
|
|
271
428
|
LOCAL_CONFIG_DIR,
|
|
272
429
|
LOCAL_CREDENTIALS_FILE,
|
|
430
|
+
LOCAL_ADMIN_CREDENTIALS_FILE,
|
|
431
|
+
LOCAL_RUNTIME_CREDENTIALS_FILE,
|
|
273
432
|
loadConfig,
|
|
274
433
|
saveConfig,
|
|
275
434
|
getConfigValue,
|
|
276
435
|
setConfigValue,
|
|
277
436
|
loadCredentials,
|
|
278
437
|
saveCredentials,
|
|
438
|
+
loadAdminCredentials,
|
|
439
|
+
saveAdminCredentials,
|
|
440
|
+
clearAdminCredentials,
|
|
441
|
+
loadRuntimeCredentials,
|
|
442
|
+
saveRuntimeCredentials,
|
|
443
|
+
clearRuntimeCredentials,
|
|
279
444
|
clearCredentials,
|
|
280
445
|
getToken,
|
|
281
446
|
setAuth,
|
|
282
447
|
getApp,
|
|
283
448
|
setApp,
|
|
449
|
+
setRuntime,
|
|
284
450
|
setApiKey,
|
|
285
451
|
clearApiKey,
|
|
286
452
|
getApiKey,
|
|
287
453
|
getAuthSource,
|
|
454
|
+
getRuntimeSource,
|
|
288
455
|
hasLocalCredentials,
|
|
289
456
|
ensureCredentialDefaults,
|
|
290
457
|
withOnboardingFirewallEnabled,
|