smoonb 0.0.100 → 0.0.101
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.
Potentially problematic release.
This version of smoonb might be problematic. Click here for more details.
package/package.json
CHANGED
|
@@ -10,9 +10,11 @@ const {
|
|
|
10
10
|
maskSecret,
|
|
11
11
|
safeStringifyHeaders,
|
|
12
12
|
truncateBody,
|
|
13
|
+
sanitizeResponseParsedForBundle,
|
|
13
14
|
classifyRequestError,
|
|
14
15
|
buildSupportBundle,
|
|
15
|
-
formatBundleForTerminal
|
|
16
|
+
formatBundleForTerminal,
|
|
17
|
+
BODY_TRUNCATE_BYTES_BUNDLE
|
|
16
18
|
} = require('../../../utils/supportDiagnostics');
|
|
17
19
|
|
|
18
20
|
const LICENSE_KEY_ENV = 'SMOONB_LICENSE_KEY';
|
|
@@ -119,7 +121,7 @@ function printLicenseValidationError(getT, appUrl, opts) {
|
|
|
119
121
|
bundleParams.httpStatus = httpStatus;
|
|
120
122
|
bundleParams.httpStatusText = httpStatusText || '';
|
|
121
123
|
bundleParams.responseHeaders = responseHeaders;
|
|
122
|
-
bundleParams.
|
|
124
|
+
bundleParams.responseBodyRaw = responseBodyTruncated || undefined;
|
|
123
125
|
}
|
|
124
126
|
if (err) {
|
|
125
127
|
bundleParams.errorName = err.name;
|
|
@@ -165,6 +167,63 @@ function printLicenseValidationError(getT, appUrl, opts) {
|
|
|
165
167
|
ui.hint(` ${getT('license.error.messageSuggestion')}\n`);
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Exibe falha quando servidor retorna allow === false (reason literal + CTA + bundle).
|
|
172
|
+
*/
|
|
173
|
+
function printLicenseDeniedByServer(getT, appUrl, opts) {
|
|
174
|
+
const {
|
|
175
|
+
correlationId,
|
|
176
|
+
correlationIdEchoed,
|
|
177
|
+
reason,
|
|
178
|
+
fullUrl,
|
|
179
|
+
rawBody,
|
|
180
|
+
resBody,
|
|
181
|
+
licenseMasked,
|
|
182
|
+
version,
|
|
183
|
+
command
|
|
184
|
+
} = opts;
|
|
185
|
+
const displayId = correlationIdEchoed || correlationId;
|
|
186
|
+
const responseBodyRaw = rawBody ? truncateBody(rawBody, BODY_TRUNCATE_BYTES_BUNDLE) : '';
|
|
187
|
+
const responseParsed = sanitizeResponseParsedForBundle(resBody);
|
|
188
|
+
|
|
189
|
+
ui.errorBold(`\n❌ ${getT('license.denied.title')}\n`);
|
|
190
|
+
ui.info(` ${getT('license.denied.reason', { reason: reason || 'No reason provided' })}`);
|
|
191
|
+
ui.info(` ${getT('license.denied.correlationId', { id: displayId })}\n`);
|
|
192
|
+
ui.link(` ${getT('license.error.howToHelp')}`);
|
|
193
|
+
ui.info(` ${getT('license.error.supportStep1', { url: appUrl })}`);
|
|
194
|
+
ui.info(` ${getT('license.error.supportStep2')}`);
|
|
195
|
+
ui.info(` ${getT('license.error.supportStep3')}`);
|
|
196
|
+
ui.info(` ${getT('license.error.supportStep4')}`);
|
|
197
|
+
ui.info(` ${getT('license.error.supportStep5')}\n`);
|
|
198
|
+
|
|
199
|
+
const bundleParams = {
|
|
200
|
+
correlationId: displayId,
|
|
201
|
+
product: 'smoonb CLI',
|
|
202
|
+
version,
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
os: process.platform,
|
|
205
|
+
arch: process.arch,
|
|
206
|
+
nodeVersion: process.version,
|
|
207
|
+
command: `smoonb ${command || 'backup'}`,
|
|
208
|
+
licenseMasked: licenseMasked || '***',
|
|
209
|
+
apiBaseUrl: fullUrl ? fullUrl.replace(/\/[^/]*$/, '') : APP_CONFIG.apiBaseUrl,
|
|
210
|
+
endpoint: ENDPOINT_PATH,
|
|
211
|
+
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
212
|
+
telemetryEnabled: (process.env.SMOONB_TELEMETRY_ENABLED !== 'false').toString(),
|
|
213
|
+
httpStatus: 200,
|
|
214
|
+
httpStatusText: 'OK',
|
|
215
|
+
responseBodyRaw: responseBodyRaw || undefined,
|
|
216
|
+
responseParsed: responseParsed !== '(none)' ? responseParsed : undefined,
|
|
217
|
+
correlationIdEchoed: correlationIdEchoed && correlationIdEchoed !== correlationId ? correlationIdEchoed : undefined
|
|
218
|
+
};
|
|
219
|
+
const bundleText = buildSupportBundle(bundleParams);
|
|
220
|
+
ui.block(formatBundleForTerminal(bundleText));
|
|
221
|
+
ui.info('');
|
|
222
|
+
ui.link(` ${getT('license.error.visit', { url: appUrl })}\n`);
|
|
223
|
+
ui.hint(` ${getT('license.error.subjectSuggestion', { id: displayId })}`);
|
|
224
|
+
ui.hint(` ${getT('license.error.messageSuggestion')}\n`);
|
|
225
|
+
}
|
|
226
|
+
|
|
168
227
|
/**
|
|
169
228
|
* Step 00: Validação de licença via backend.
|
|
170
229
|
* Roda antes de todos os outros steps.
|
|
@@ -276,7 +335,7 @@ module.exports = async (options) => {
|
|
|
276
335
|
if (statusCode !== 200) {
|
|
277
336
|
const displayCorrelationId = resBody.correlationId || correlationId;
|
|
278
337
|
const responseHeadersStr = safeStringifyHeaders(headers);
|
|
279
|
-
const
|
|
338
|
+
const responseBodyRaw = rawBody ? truncateBody(rawBody, BODY_TRUNCATE_BYTES_BUNDLE) : '';
|
|
280
339
|
printLicenseValidationError(getT, appUrl, {
|
|
281
340
|
correlationId: displayCorrelationId,
|
|
282
341
|
kind: 'http',
|
|
@@ -289,35 +348,47 @@ module.exports = async (options) => {
|
|
|
289
348
|
version: cliVersion,
|
|
290
349
|
command,
|
|
291
350
|
responseHeaders: responseHeadersStr,
|
|
292
|
-
responseBodyTruncated
|
|
351
|
+
responseBodyTruncated: responseBodyRaw
|
|
293
352
|
});
|
|
294
353
|
process.exit(1);
|
|
295
354
|
}
|
|
296
355
|
|
|
297
|
-
|
|
298
|
-
const
|
|
356
|
+
// allow como boolean robusto: true ou "true" -> true; resto -> false
|
|
357
|
+
const allowRaw = resBody.allow;
|
|
358
|
+
const allow = allowRaw === true || (typeof allowRaw === 'string' && allowRaw.toLowerCase() === 'true');
|
|
299
359
|
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
360
|
+
if (allow === true) {
|
|
361
|
+
// Nunca negar quando allow === true
|
|
362
|
+
const status = resBody.status || '';
|
|
363
|
+
if (status === 'trial') {
|
|
364
|
+
ui.warn(` ⚠️ ${getT('license.trialNotice')}`);
|
|
365
|
+
}
|
|
366
|
+
const license = {
|
|
367
|
+
status: resBody.status,
|
|
368
|
+
plan: resBody.plan,
|
|
369
|
+
expiresAt: resBody.expiresAt,
|
|
370
|
+
accountId: resBody.accountId,
|
|
371
|
+
workspaceId: resBody.workspaceId
|
|
372
|
+
};
|
|
373
|
+
const smoonbToken = resBody.token || '';
|
|
374
|
+
return { license, smoonbToken };
|
|
309
375
|
}
|
|
310
376
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
377
|
+
// allow === false: exibir reason do servidor literalmente + CTA + bundle
|
|
378
|
+
const serverReason = resBody.reason != null ? String(resBody.reason) : 'No reason provided';
|
|
379
|
+
const correlationIdEchoed = resBody.correlationId || null;
|
|
380
|
+
printLicenseDeniedByServer(getT, appUrl, {
|
|
381
|
+
correlationId,
|
|
382
|
+
correlationIdEchoed,
|
|
383
|
+
reason: serverReason,
|
|
384
|
+
fullUrl,
|
|
385
|
+
rawBody,
|
|
386
|
+
resBody,
|
|
387
|
+
licenseMasked: maskSecret(licenseKey),
|
|
388
|
+
version: cliVersion,
|
|
389
|
+
command
|
|
390
|
+
});
|
|
391
|
+
process.exit(1);
|
|
321
392
|
};
|
|
322
393
|
|
|
323
394
|
module.exports.maskLicense = maskLicense;
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -273,6 +273,9 @@
|
|
|
273
273
|
"license.checkConnection": "Check your connection and try again.",
|
|
274
274
|
"license.invalidOrInactive": "Your license is invalid or your subscription is not active.",
|
|
275
275
|
"license.openDesktopApp": "Open the desktop app, check your subscription and generate a new license.",
|
|
276
|
+
"license.denied.title": "License validation denied by server.",
|
|
277
|
+
"license.denied.reason": "Reason: {reason}",
|
|
278
|
+
"license.denied.correlationId": "Correlation ID: {id}",
|
|
276
279
|
"license.trialNotice": "You are using a trial license.",
|
|
277
280
|
"license.error.title": "License validation failed.",
|
|
278
281
|
"license.error.whatHappened": "What happened:",
|
|
@@ -273,6 +273,9 @@
|
|
|
273
273
|
"license.checkConnection": "Verifique sua conexão e tente novamente.",
|
|
274
274
|
"license.invalidOrInactive": "Sua licença é inválida ou sua assinatura não está ativa.",
|
|
275
275
|
"license.openDesktopApp": "Abra o app desktop, verifique sua assinatura e gere uma nova licença.",
|
|
276
|
+
"license.denied.title": "Validação de licença negada pelo servidor.",
|
|
277
|
+
"license.denied.reason": "Motivo: {reason}",
|
|
278
|
+
"license.denied.correlationId": "Correlation ID: {id}",
|
|
276
279
|
"license.trialNotice": "Você está usando uma licença trial.",
|
|
277
280
|
"license.error.title": "Falha na validação da licença.",
|
|
278
281
|
"license.error.whatHappened": "O que aconteceu:",
|
|
@@ -6,6 +6,7 @@ const crypto = require('crypto');
|
|
|
6
6
|
|
|
7
7
|
const ALLOWLIST_HEADERS = ['content-type', 'server', 'cf-ray', 'x-request-id'];
|
|
8
8
|
const BODY_TRUNCATE_BYTES = 2048;
|
|
9
|
+
const BODY_TRUNCATE_BYTES_BUNDLE = 4096;
|
|
9
10
|
|
|
10
11
|
function createCorrelationId() {
|
|
11
12
|
if (crypto.randomUUID) {
|
|
@@ -65,6 +66,30 @@ function truncateBody(input, maxBytes = BODY_TRUNCATE_BYTES) {
|
|
|
65
66
|
return buf.slice(0, maxBytes).toString('utf8') + '\n...[truncated]';
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/** Campos permitidos no ResponseParsed do bundle (sem tokens/sensíveis). */
|
|
70
|
+
const RESPONSE_PARSED_KEYS = ['allow', 'reason', 'periodEnd', 'trialEndsAt', 'remainingThisMonth', 'correlationId'];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Extrai objeto sanitizado da resposta para o bundle (allow, reason, periodEnd, trialEndsAt, remainingThisMonth, correlationId).
|
|
74
|
+
* @param {object} parsed - resBody
|
|
75
|
+
* @returns {string} JSON string ou "(none)"
|
|
76
|
+
*/
|
|
77
|
+
function sanitizeResponseParsedForBundle(parsed) {
|
|
78
|
+
if (!parsed || typeof parsed !== 'object') return '(none)';
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const key of RESPONSE_PARSED_KEYS) {
|
|
81
|
+
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
|
|
82
|
+
out[key] = parsed[key];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (Object.keys(out).length === 0) return '(none)';
|
|
86
|
+
try {
|
|
87
|
+
return JSON.stringify(out);
|
|
88
|
+
} catch {
|
|
89
|
+
return '(none)';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
68
93
|
/**
|
|
69
94
|
* Classifica erro de request (rede/TLS/timeout).
|
|
70
95
|
* @param {Error} err
|
|
@@ -169,14 +194,22 @@ function buildSupportBundle(params) {
|
|
|
169
194
|
];
|
|
170
195
|
|
|
171
196
|
if (p.httpStatus != null) {
|
|
172
|
-
lines.push(`
|
|
197
|
+
lines.push(`HttpStatus: ${p.httpStatus} ${(p.httpStatusText || '').trim()}`);
|
|
198
|
+
}
|
|
199
|
+
if (p.correlationIdEchoed != null && p.correlationIdEchoed !== p.correlationId) {
|
|
200
|
+
lines.push(`CorrelationIdEchoed: ${p.correlationIdEchoed}`);
|
|
173
201
|
}
|
|
174
202
|
if (p.responseHeaders) {
|
|
175
203
|
lines.push('ResponseHeaders:', p.responseHeaders);
|
|
176
204
|
}
|
|
177
|
-
if (p.
|
|
205
|
+
if (p.responseBodyRaw != null) {
|
|
206
|
+
lines.push('ResponseBodyRaw:', p.responseBodyRaw);
|
|
207
|
+
} else if (p.responseBodyTruncated) {
|
|
178
208
|
lines.push('ResponseBody(Truncated):', p.responseBodyTruncated);
|
|
179
209
|
}
|
|
210
|
+
if (p.responseParsed != null) {
|
|
211
|
+
lines.push('ResponseParsed:', p.responseParsed);
|
|
212
|
+
}
|
|
180
213
|
if (p.errorName || p.errorMessage) {
|
|
181
214
|
lines.push(`Error: name: ${p.errorName || ''} message: ${p.errorMessage || ''}`);
|
|
182
215
|
}
|
|
@@ -209,8 +242,10 @@ module.exports = {
|
|
|
209
242
|
maskSecret,
|
|
210
243
|
safeStringifyHeaders,
|
|
211
244
|
truncateBody,
|
|
245
|
+
sanitizeResponseParsedForBundle,
|
|
212
246
|
classifyRequestError,
|
|
213
247
|
buildSupportBundle,
|
|
214
248
|
formatBundleForTerminal,
|
|
215
|
-
BODY_TRUNCATE_BYTES
|
|
249
|
+
BODY_TRUNCATE_BYTES,
|
|
250
|
+
BODY_TRUNCATE_BYTES_BUNDLE
|
|
216
251
|
};
|