correctover-scan 1.4.0 → 1.5.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/core/license.js +74 -53
- package/package.json +1 -1
package/core/license.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* License validator
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* License validator — freemium hook model.
|
|
3
|
+
*
|
|
4
|
+
* Free: unlimited scanning (see all risks)
|
|
5
|
+
* Pro: fix recommendations, auto-heal, reports, history
|
|
6
|
+
*
|
|
7
|
+
* The hook: users see ALL their problems for free.
|
|
8
|
+
* The paywall: solutions are locked.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
const fs = require('fs');
|
|
@@ -9,8 +13,8 @@ const path = require('path');
|
|
|
9
13
|
const crypto = require('crypto');
|
|
10
14
|
const os = require('os');
|
|
11
15
|
|
|
12
|
-
const FREE_LIMIT_PER_MONTH = 30;
|
|
13
16
|
const STATE_FILE = path.join(os.homedir(), '.correctover', 'license.json');
|
|
17
|
+
const FREE_FIX_PREVIEW = 2;
|
|
14
18
|
|
|
15
19
|
function loadState() {
|
|
16
20
|
try {
|
|
@@ -18,7 +22,7 @@ function loadState() {
|
|
|
18
22
|
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
|
19
23
|
}
|
|
20
24
|
} catch (e) {}
|
|
21
|
-
return { products: {}, license_key: null, installed_at: Date.now() / 1000 };
|
|
25
|
+
return { products: {}, license_key: null, installed_at: Date.now() / 1000, scan_history: [] };
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
function saveState(state) {
|
|
@@ -28,18 +32,9 @@ function saveState(state) {
|
|
|
28
32
|
} catch (e) {}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
function currentMonth() {
|
|
32
|
-
return new Date().toISOString().slice(0, 7); // YYYY-MM
|
|
33
|
-
}
|
|
34
|
-
|
|
35
35
|
function getProductState(state, product) {
|
|
36
|
-
const month = currentMonth();
|
|
37
36
|
if (!state.products[product]) {
|
|
38
|
-
state.products[product] = {
|
|
39
|
-
}
|
|
40
|
-
if (state.products[product].month !== month) {
|
|
41
|
-
state.products[product].checks_used = 0;
|
|
42
|
-
state.products[product].month = month;
|
|
37
|
+
state.products[product] = { total_scans: 0, total_risks_found: 0, first_scan: Date.now()/1000, last_scan: null };
|
|
43
38
|
}
|
|
44
39
|
return state.products[product];
|
|
45
40
|
}
|
|
@@ -47,7 +42,6 @@ function getProductState(state, product) {
|
|
|
47
42
|
function verifyLicenseKey(key) {
|
|
48
43
|
if (!key || key.length < 12) return false;
|
|
49
44
|
|
|
50
|
-
// COV-<product>-<hash> (Cloud HMAC)
|
|
51
45
|
if (key.startsWith('COV-')) {
|
|
52
46
|
const parts = key.split('-');
|
|
53
47
|
if (parts.length < 3) return false;
|
|
@@ -57,7 +51,6 @@ function verifyLicenseKey(key) {
|
|
|
57
51
|
return parts[parts.length - 1].startsWith(expected);
|
|
58
52
|
}
|
|
59
53
|
|
|
60
|
-
// CV-TRL-<base64> / CV-PRO-<base64> (FC XunhuPay)
|
|
61
54
|
if (key.startsWith('CV-')) {
|
|
62
55
|
const parts = key.split('-', 2);
|
|
63
56
|
if (parts.length < 3) return false;
|
|
@@ -77,66 +70,94 @@ function verifyLicenseKey(key) {
|
|
|
77
70
|
|
|
78
71
|
function checkLicense(product) {
|
|
79
72
|
const state = loadState();
|
|
80
|
-
const ps = getProductState(state, product);
|
|
81
73
|
const licenseKey = state.license_key || process.env.CORRECTOVER_LICENSE_KEY;
|
|
82
74
|
|
|
83
75
|
if (licenseKey && verifyLicenseKey(licenseKey)) {
|
|
84
76
|
return {
|
|
85
|
-
authorized: true,
|
|
86
77
|
tier: 'pro',
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
78
|
+
can_scan: true,
|
|
79
|
+
can_fix: true,
|
|
80
|
+
can_report: true,
|
|
81
|
+
can_heal: true,
|
|
82
|
+
can_history: true,
|
|
83
|
+
fix_preview: Infinity,
|
|
90
84
|
};
|
|
91
85
|
}
|
|
92
86
|
|
|
93
|
-
const remaining = Math.max(0, FREE_LIMIT_PER_MONTH - ps.checks_used);
|
|
94
87
|
return {
|
|
95
|
-
authorized: remaining > 0,
|
|
96
88
|
tier: 'free',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
can_scan: true,
|
|
90
|
+
can_fix: false,
|
|
91
|
+
can_report: false,
|
|
92
|
+
can_heal: false,
|
|
93
|
+
can_history: false,
|
|
94
|
+
fix_preview: FREE_FIX_PREVIEW,
|
|
100
95
|
};
|
|
101
96
|
}
|
|
102
97
|
|
|
103
|
-
function
|
|
98
|
+
function recordScan(product, risksFound) {
|
|
104
99
|
const state = loadState();
|
|
100
|
+
const ps = getProductState(state, product);
|
|
101
|
+
ps.total_scans += 1;
|
|
102
|
+
ps.total_risks_found += risksFound || 0;
|
|
103
|
+
ps.last_scan = Date.now() / 1000;
|
|
104
|
+
|
|
105
|
+
const history = state.scan_history || [];
|
|
106
|
+
history.push({ time: Date.now()/1000, product, risks: risksFound || 0 });
|
|
107
|
+
|
|
105
108
|
const status = checkLicense(product);
|
|
106
|
-
if (
|
|
109
|
+
if (status.tier === 'free' && history.length > 1) {
|
|
110
|
+
state.scan_history = history.slice(-1);
|
|
111
|
+
} else {
|
|
112
|
+
state.scan_history = history;
|
|
113
|
+
}
|
|
107
114
|
|
|
108
|
-
const ps = getProductState(state, product);
|
|
109
|
-
ps.checks_used += count;
|
|
110
|
-
ps.total_checks = (ps.total_checks || 0) + count;
|
|
111
115
|
saveState(state);
|
|
112
|
-
|
|
113
|
-
status.checks_remaining = Math.max(0, FREE_LIMIT_PER_MONTH - ps.checks_used);
|
|
114
|
-
status.checks_used = ps.checks_used;
|
|
115
|
-
return status;
|
|
116
|
+
return checkLicense(product);
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return
|
|
119
|
+
function getFixCTA(totalRisks, hiddenRisks) {
|
|
120
|
+
if (hiddenRisks <= 0) return '';
|
|
121
|
+
const shown = totalRisks - hiddenRisks;
|
|
122
|
+
return [
|
|
123
|
+
'',
|
|
124
|
+
'━'.repeat(55),
|
|
125
|
+
`🔒 ${hiddenRisks} fix recommendation(s) locked.`,
|
|
126
|
+
` You have ${totalRisks} risk(s) but can only see ${shown} fix(es).`,
|
|
127
|
+
'',
|
|
128
|
+
'🛡️ Upgrade to Pro to unlock:',
|
|
129
|
+
` ✓ Fix recommendations for all ${totalRisks} risk(s)`,
|
|
130
|
+
' ✓ Auto-heal (84.1% issues resolved automatically)',
|
|
131
|
+
' ✓ HTML/PDF audit reports',
|
|
132
|
+
' ✓ Scan history & tracking',
|
|
133
|
+
'━'.repeat(55),
|
|
134
|
+
' → https://correctover.com/checkout',
|
|
135
|
+
' → export CORRECTOVER_LICENSE_KEY=<your-key>',
|
|
136
|
+
'━'.repeat(55),
|
|
137
|
+
].join('\n');
|
|
122
138
|
}
|
|
123
139
|
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
function getNoRiskCTA() {
|
|
141
|
+
return [
|
|
142
|
+
'',
|
|
143
|
+
'━'.repeat(55),
|
|
144
|
+
'✅ No risks found in this scan.',
|
|
145
|
+
'',
|
|
146
|
+
'🛡️ Stay protected with Pro:',
|
|
147
|
+
' ✓ Continuous monitoring (auto-scan on changes)',
|
|
148
|
+
' ✓ Auto-heal when risks appear',
|
|
149
|
+
' ✓ Compliance reports (OAuth 2.1, CCS v1.0)',
|
|
150
|
+
'━'.repeat(55),
|
|
151
|
+
' → https://correctover.com/checkout',
|
|
152
|
+
'━'.repeat(55),
|
|
153
|
+
].join('\n');
|
|
133
154
|
}
|
|
134
155
|
|
|
135
156
|
module.exports = {
|
|
136
|
-
|
|
157
|
+
FREE_FIX_PREVIEW,
|
|
137
158
|
checkLicense,
|
|
138
|
-
|
|
139
|
-
canRun,
|
|
140
|
-
getUpgradeMessage,
|
|
159
|
+
recordScan,
|
|
141
160
|
verifyLicenseKey,
|
|
161
|
+
getFixCTA,
|
|
162
|
+
getNoRiskCTA,
|
|
142
163
|
};
|
package/package.json
CHANGED