honeyweb-core 2.0.2 → 2.0.4
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/ai/gemini.js +54 -29
- package/ai/index.js +4 -27
- package/ai/prompt-templates.js +22 -42
- package/config/defaults.js +14 -37
- package/config/index.js +7 -35
- package/config/schema.js +1 -17
- package/detection/behavioral.js +26 -118
- package/detection/bot-detector.js +11 -62
- package/detection/index.js +19 -35
- package/detection/patterns.js +14 -51
- package/detection/rate-limiter.js +15 -86
- package/detection/traversal.js +42 -0
- package/index.js +1 -13
- package/middleware/blocklist-checker.js +1 -15
- package/middleware/dashboard.js +218 -214
- package/middleware/index.js +17 -39
- package/middleware/request-analyzer.js +29 -70
- package/middleware/trap-injector.js +3 -28
- package/package.json +1 -1
- package/storage/bot-tracker.js +9 -56
- package/storage/index.js +3 -12
- package/storage/json-store.js +15 -89
- package/storage/memory-store.js +9 -61
- package/utils/cache.js +5 -48
- package/utils/dns-verify.js +8 -45
- package/utils/event-emitter.js +7 -39
- package/utils/logger.js +9 -35
|
@@ -1,29 +1,12 @@
|
|
|
1
|
-
// honeyweb-core/middleware/request-analyzer.js
|
|
2
|
-
// Request analysis middleware
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Create request analyzer middleware
|
|
6
|
-
* @param {Object} config - Configuration
|
|
7
|
-
* @param {Object} storage - Storage instance
|
|
8
|
-
* @param {Object} botTracker - Bot tracker instance
|
|
9
|
-
* @param {Object} detector - Detection engine
|
|
10
|
-
* @param {Object} aiAnalyzer - AI analyzer
|
|
11
|
-
* @param {Object} logger - Logger instance
|
|
12
|
-
* @param {Object} events - Event emitter
|
|
13
|
-
* @returns {Function} - Middleware function
|
|
14
|
-
*/
|
|
15
1
|
function createRequestAnalyzer(config, storage, botTracker, detector, aiAnalyzer, logger, events) {
|
|
16
2
|
const trapPaths = config.traps.paths;
|
|
17
3
|
|
|
18
4
|
return async function analyzeRequest(req, res, next) {
|
|
19
5
|
const clientIP = req.ip || req.connection.remoteAddress;
|
|
20
|
-
|
|
21
|
-
// 1. THREAT DETECTION (whitelist check happens FIRST to protect legit bots)
|
|
22
6
|
const analysis = await detector.analyze(req, clientIP);
|
|
23
7
|
|
|
24
|
-
//
|
|
8
|
+
// Legitimate bots skip all checks
|
|
25
9
|
if (analysis.legitimateBot) {
|
|
26
|
-
// Record legitimate bot visit for dashboard
|
|
27
10
|
botTracker.recordVisit({
|
|
28
11
|
botName: analysis.whitelist.botName,
|
|
29
12
|
ip: clientIP,
|
|
@@ -32,22 +15,18 @@ function createRequestAnalyzer(config, storage, botTracker, detector, aiAnalyzer
|
|
|
32
15
|
verified: analysis.whitelist.verified,
|
|
33
16
|
timestamp: Date.now()
|
|
34
17
|
});
|
|
35
|
-
|
|
36
18
|
logger.info('Legitimate bot detected', {
|
|
37
|
-
ip: clientIP,
|
|
38
|
-
|
|
39
|
-
verified: analysis.whitelist.verified,
|
|
40
|
-
path: req.path
|
|
19
|
+
ip: clientIP, botName: analysis.whitelist.botName,
|
|
20
|
+
verified: analysis.whitelist.verified, path: req.path
|
|
41
21
|
});
|
|
42
22
|
return next();
|
|
43
23
|
}
|
|
44
24
|
|
|
45
|
-
//
|
|
25
|
+
// Trap access — immediate ban
|
|
46
26
|
if (trapPaths.includes(req.path)) {
|
|
47
27
|
logger.trapTriggered(clientIP, req.path);
|
|
48
28
|
events.emitTrapTriggered(clientIP, req.path);
|
|
49
29
|
|
|
50
|
-
// Ban the IP immediately
|
|
51
30
|
try {
|
|
52
31
|
await storage.ban(clientIP, `Trap triggered: ${req.path}`);
|
|
53
32
|
events.emitIpBanned(clientIP, `Trap triggered: ${req.path}`);
|
|
@@ -55,67 +34,47 @@ function createRequestAnalyzer(config, storage, botTracker, detector, aiAnalyzer
|
|
|
55
34
|
logger.error('Error banning IP', { error: err.message });
|
|
56
35
|
}
|
|
57
36
|
|
|
58
|
-
// AI analysis for trap trigger
|
|
59
37
|
if (aiAnalyzer.isEnabled()) {
|
|
60
38
|
aiAnalyzer.analyzeTrap({
|
|
61
|
-
ip: clientIP,
|
|
62
|
-
|
|
63
|
-
userAgent: req.headers['user-agent'],
|
|
64
|
-
timestamp: Date.now()
|
|
39
|
+
ip: clientIP, path: req.path,
|
|
40
|
+
userAgent: req.headers['user-agent'], timestamp: Date.now()
|
|
65
41
|
}).then(report => {
|
|
66
42
|
if (report) {
|
|
67
43
|
logger.info('AI Trap Analysis', { ip: clientIP, report });
|
|
68
44
|
events.emitAiAnalysis(clientIP, report);
|
|
69
45
|
}
|
|
70
|
-
}).catch(err => {
|
|
71
|
-
logger.error('AI analysis failed', { error: err.message });
|
|
72
|
-
});
|
|
46
|
+
}).catch(err => logger.error('AI trap analysis failed', { error: err.message }));
|
|
73
47
|
}
|
|
74
48
|
|
|
75
|
-
return res.status(403).send(
|
|
76
|
-
'<h1>403 Forbidden</h1><p>You have accessed a restricted area.</p>'
|
|
77
|
-
);
|
|
49
|
+
return res.status(403).send('<h1>403 Forbidden</h1><p>You have accessed a restricted area.</p>');
|
|
78
50
|
}
|
|
79
51
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
if (analysis.detected) {
|
|
52
|
+
// Pattern/rate/behavioral/bot threats
|
|
53
|
+
if (analysis.detected && analysis.threatLevel >= 50) {
|
|
83
54
|
logger.threatDetected(clientIP, analysis.threats, analysis.threatLevel);
|
|
84
55
|
events.emitThreatDetected(clientIP, analysis.threats, analysis.threatLevel);
|
|
85
56
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
logger.error('Error banning IP', { error: err.message });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// AI analysis for threat
|
|
96
|
-
if (aiAnalyzer.isEnabled()) {
|
|
97
|
-
aiAnalyzer.analyzeThreat({
|
|
98
|
-
ip: clientIP,
|
|
99
|
-
path: req.path,
|
|
100
|
-
method: req.method,
|
|
101
|
-
threats: analysis.threats,
|
|
102
|
-
userAgent: req.headers['user-agent'],
|
|
103
|
-
referer: req.headers['referer'],
|
|
104
|
-
timestamp: Date.now()
|
|
105
|
-
}).then(report => {
|
|
106
|
-
if (report) {
|
|
107
|
-
logger.info('AI Threat Analysis', { ip: clientIP, report });
|
|
108
|
-
events.emitAiAnalysis(clientIP, report);
|
|
109
|
-
}
|
|
110
|
-
}).catch(err => {
|
|
111
|
-
logger.error('AI analysis failed', { error: err.message });
|
|
112
|
-
});
|
|
113
|
-
}
|
|
57
|
+
try {
|
|
58
|
+
await storage.ban(clientIP, `Threat detected: ${analysis.threats[0]}`);
|
|
59
|
+
events.emitIpBanned(clientIP, `Threat detected: ${analysis.threats[0]}`);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
logger.error('Error banning IP', { error: err.message });
|
|
62
|
+
}
|
|
114
63
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
64
|
+
if (aiAnalyzer.isEnabled()) {
|
|
65
|
+
aiAnalyzer.analyzeThreat({
|
|
66
|
+
ip: clientIP, path: req.path, method: req.method,
|
|
67
|
+
threats: analysis.threats, userAgent: req.headers['user-agent'],
|
|
68
|
+
referer: req.headers['referer'], timestamp: Date.now()
|
|
69
|
+
}).then(report => {
|
|
70
|
+
if (report) {
|
|
71
|
+
logger.info('AI Threat Analysis', { ip: clientIP, report });
|
|
72
|
+
events.emitAiAnalysis(clientIP, report);
|
|
73
|
+
}
|
|
74
|
+
}).catch(err => logger.error('AI analysis failed', { error: err.message }));
|
|
118
75
|
}
|
|
76
|
+
|
|
77
|
+
return res.status(403).send('<h1>403 Forbidden</h1><p>Malicious activity detected.</p>');
|
|
119
78
|
}
|
|
120
79
|
|
|
121
80
|
next();
|
|
@@ -1,50 +1,25 @@
|
|
|
1
|
-
// honeyweb-core/middleware/trap-injector.js
|
|
2
|
-
// Trap injection middleware (extracted from main index.js)
|
|
3
|
-
|
|
4
1
|
const cheerio = require('cheerio');
|
|
5
2
|
|
|
6
|
-
/**
|
|
7
|
-
* Create trap injector middleware
|
|
8
|
-
* @param {Object} config - Trap configuration
|
|
9
|
-
* @returns {Function} - Middleware function
|
|
10
|
-
*/
|
|
11
3
|
function createTrapInjector(config) {
|
|
12
4
|
const trapPaths = config.traps.paths;
|
|
13
5
|
|
|
14
6
|
return function injectTraps(req, res, next) {
|
|
15
|
-
|
|
16
|
-
if (!config.traps.injection.enabled) {
|
|
17
|
-
return next();
|
|
18
|
-
}
|
|
7
|
+
if (!config.traps.injection.enabled) return next();
|
|
19
8
|
|
|
20
|
-
// Intercept res.send to inject traps
|
|
21
9
|
const originalSend = res.send;
|
|
22
|
-
|
|
23
10
|
res.send = function (body) {
|
|
24
|
-
// Only inject if the content is HTML
|
|
25
11
|
if (typeof body === 'string' && (body.includes('<html') || body.includes('<body'))) {
|
|
26
12
|
try {
|
|
27
13
|
const $ = cheerio.load(body);
|
|
28
|
-
|
|
29
|
-
// Pick a random trap
|
|
30
14
|
const trapUrl = trapPaths[Math.floor(Math.random() * trapPaths.length)];
|
|
31
|
-
|
|
32
|
-
// Create invisible link (Hidden by CSS)
|
|
33
|
-
const trapLink = `<a href="${trapUrl}" style="opacity:0; position:absolute; z-index:-999; left:-9999px;">Admin Panel</a>`;
|
|
34
|
-
|
|
35
|
-
// Inject into body
|
|
36
|
-
$('body').append(trapLink);
|
|
37
|
-
|
|
15
|
+
$('body').append(`<a href="${trapUrl}" style="opacity:0;position:absolute;z-index:-999;left:-9999px;">Admin Panel</a>`);
|
|
38
16
|
body = $.html();
|
|
39
17
|
} catch (err) {
|
|
40
|
-
// If
|
|
41
|
-
console.error('[TrapInjector] Failed to inject trap:', err.message);
|
|
18
|
+
// If injection fails, send original body
|
|
42
19
|
}
|
|
43
20
|
}
|
|
44
|
-
|
|
45
21
|
originalSend.call(this, body);
|
|
46
22
|
};
|
|
47
|
-
|
|
48
23
|
next();
|
|
49
24
|
};
|
|
50
25
|
}
|
package/package.json
CHANGED
package/storage/bot-tracker.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
// honeyweb-core/storage/bot-tracker.js
|
|
2
|
-
// Track legitimate bot visits for dashboard display
|
|
3
2
|
|
|
4
3
|
class BotTracker {
|
|
5
4
|
constructor() {
|
|
6
|
-
this.botVisits = new Map();
|
|
7
|
-
this.maxVisitsPerBot = 50;
|
|
5
|
+
this.botVisits = new Map();
|
|
6
|
+
this.maxVisitsPerBot = 50;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
/**
|
|
11
|
-
* Record a legitimate bot visit
|
|
12
|
-
* @param {Object} visit - Visit data
|
|
13
|
-
*/
|
|
14
9
|
recordVisit(visit) {
|
|
15
10
|
const { botName, ip, path, userAgent, verified, timestamp } = visit;
|
|
16
11
|
|
|
@@ -19,49 +14,23 @@ class BotTracker {
|
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
const visits = this.botVisits.get(botName);
|
|
17
|
+
visits.unshift({ ip, path, userAgent, verified, timestamp: timestamp || Date.now() });
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
visits.unshift({
|
|
25
|
-
ip,
|
|
26
|
-
path,
|
|
27
|
-
userAgent,
|
|
28
|
-
verified,
|
|
29
|
-
timestamp: timestamp || Date.now()
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// Keep only last N visits
|
|
33
|
-
if (visits.length > this.maxVisitsPerBot) {
|
|
34
|
-
visits.pop();
|
|
35
|
-
}
|
|
19
|
+
if (visits.length > this.maxVisitsPerBot) visits.pop();
|
|
36
20
|
}
|
|
37
21
|
|
|
38
|
-
/**
|
|
39
|
-
* Get all bot visits grouped by bot name
|
|
40
|
-
* @returns {Object} - Bot visits grouped by name
|
|
41
|
-
*/
|
|
42
22
|
getAllVisits() {
|
|
43
23
|
const result = {};
|
|
44
|
-
|
|
45
24
|
for (const [botName, visits] of this.botVisits.entries()) {
|
|
46
25
|
result[botName] = visits;
|
|
47
26
|
}
|
|
48
|
-
|
|
49
27
|
return result;
|
|
50
28
|
}
|
|
51
29
|
|
|
52
|
-
/**
|
|
53
|
-
* Get visits for a specific bot
|
|
54
|
-
* @param {string} botName - Bot name
|
|
55
|
-
* @returns {Array} - Array of visits
|
|
56
|
-
*/
|
|
57
30
|
getVisitsByBot(botName) {
|
|
58
31
|
return this.botVisits.get(botName) || [];
|
|
59
32
|
}
|
|
60
33
|
|
|
61
|
-
/**
|
|
62
|
-
* Get statistics
|
|
63
|
-
* @returns {Object} - Statistics
|
|
64
|
-
*/
|
|
65
34
|
getStats() {
|
|
66
35
|
let totalVisits = 0;
|
|
67
36
|
const botCounts = {};
|
|
@@ -71,37 +40,21 @@ class BotTracker {
|
|
|
71
40
|
botCounts[botName] = visits.length;
|
|
72
41
|
}
|
|
73
42
|
|
|
74
|
-
return {
|
|
75
|
-
totalBots: this.botVisits.size,
|
|
76
|
-
totalVisits,
|
|
77
|
-
botCounts
|
|
78
|
-
};
|
|
43
|
+
return { totalBots: this.botVisits.size, totalVisits, botCounts };
|
|
79
44
|
}
|
|
80
45
|
|
|
81
|
-
/**
|
|
82
|
-
* Clear all bot visits
|
|
83
|
-
*/
|
|
84
46
|
clear() {
|
|
85
47
|
this.botVisits.clear();
|
|
86
48
|
}
|
|
87
49
|
|
|
88
|
-
/**
|
|
89
|
-
* Cleanup old visits (older than 7 days)
|
|
90
|
-
*/
|
|
91
50
|
cleanup() {
|
|
92
|
-
const maxAge = 7 * 24 * 60 * 60 * 1000;
|
|
51
|
+
const maxAge = 7 * 24 * 60 * 60 * 1000;
|
|
93
52
|
const now = Date.now();
|
|
94
53
|
|
|
95
54
|
for (const [botName, visits] of this.botVisits.entries()) {
|
|
96
|
-
const filtered = visits.filter(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (filtered.length === 0) {
|
|
101
|
-
this.botVisits.delete(botName);
|
|
102
|
-
} else {
|
|
103
|
-
this.botVisits.set(botName, filtered);
|
|
104
|
-
}
|
|
55
|
+
const filtered = visits.filter(v => (now - v.timestamp) < maxAge);
|
|
56
|
+
if (filtered.length === 0) this.botVisits.delete(botName);
|
|
57
|
+
else this.botVisits.set(botName, filtered);
|
|
105
58
|
}
|
|
106
59
|
}
|
|
107
60
|
}
|
package/storage/index.js
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
// honeyweb-core/storage/index.js
|
|
2
|
-
// Storage factory
|
|
3
2
|
|
|
4
3
|
const JsonStore = require('./json-store');
|
|
5
4
|
const MemoryStore = require('./memory-store');
|
|
6
5
|
|
|
7
|
-
/**
|
|
8
|
-
* Create storage instance based on configuration
|
|
9
|
-
* @param {Object} config - Storage configuration
|
|
10
|
-
* @returns {JsonStore|MemoryStore}
|
|
11
|
-
*/
|
|
12
6
|
function createStorage(config) {
|
|
13
7
|
const type = config.type || 'json';
|
|
14
8
|
|
|
15
9
|
switch (type) {
|
|
16
|
-
case 'json':
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return new MemoryStore(config);
|
|
20
|
-
default:
|
|
21
|
-
throw new Error(`[HoneyWeb] Unknown storage type: ${type}`);
|
|
10
|
+
case 'json': return new JsonStore(config);
|
|
11
|
+
case 'memory': return new MemoryStore(config);
|
|
12
|
+
default: throw new Error(`[HoneyWeb] Unknown storage type: ${type}`);
|
|
22
13
|
}
|
|
23
14
|
}
|
|
24
15
|
|
package/storage/json-store.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// honeyweb-core/storage/json-store.js
|
|
2
|
-
// Async JSON storage with in-memory cache
|
|
3
2
|
|
|
4
3
|
const fs = require('fs-extra');
|
|
5
4
|
const path = require('path');
|
|
@@ -8,18 +7,14 @@ const LRUCache = require('../utils/cache');
|
|
|
8
7
|
class JsonStore {
|
|
9
8
|
constructor(config) {
|
|
10
9
|
this.filePath = path.resolve(config.path || './blocked-ips.json');
|
|
11
|
-
this.banDuration = config.banDuration || 86400000;
|
|
10
|
+
this.banDuration = config.banDuration || 86400000;
|
|
12
11
|
this.autoCleanup = config.autoCleanup !== false;
|
|
13
|
-
this.cache = config.cache ? new LRUCache(1000, 60000) : null;
|
|
12
|
+
this.cache = config.cache ? new LRUCache(1000, 60000) : null;
|
|
14
13
|
|
|
15
|
-
// Initialize file
|
|
16
14
|
this._initFile();
|
|
17
15
|
|
|
18
|
-
// Start auto-cleanup if enabled
|
|
19
16
|
if (this.autoCleanup) {
|
|
20
|
-
this.cleanupInterval = setInterval(() =>
|
|
21
|
-
this._cleanupExpired();
|
|
22
|
-
}, 300000); // Clean up every 5 minutes
|
|
17
|
+
this.cleanupInterval = setInterval(() => this._cleanupExpired(), 300000);
|
|
23
18
|
}
|
|
24
19
|
}
|
|
25
20
|
|
|
@@ -33,24 +28,12 @@ class JsonStore {
|
|
|
33
28
|
}
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
/**
|
|
37
|
-
* Get all banned IPs
|
|
38
|
-
* @returns {Promise<Array>} - Array of banned IP objects
|
|
39
|
-
*/
|
|
40
31
|
async getAll() {
|
|
41
|
-
|
|
42
|
-
if (this.cache && this.cache.has('all')) {
|
|
43
|
-
return this.cache.get('all');
|
|
44
|
-
}
|
|
32
|
+
if (this.cache && this.cache.has('all')) return this.cache.get('all');
|
|
45
33
|
|
|
46
34
|
try {
|
|
47
35
|
const data = await fs.readJson(this.filePath);
|
|
48
|
-
|
|
49
|
-
// Cache the result
|
|
50
|
-
if (this.cache) {
|
|
51
|
-
this.cache.set('all', data);
|
|
52
|
-
}
|
|
53
|
-
|
|
36
|
+
if (this.cache) this.cache.set('all', data);
|
|
54
37
|
return data;
|
|
55
38
|
} catch (err) {
|
|
56
39
|
console.error('[JsonStore] Error reading file:', err);
|
|
@@ -58,24 +41,16 @@ class JsonStore {
|
|
|
58
41
|
}
|
|
59
42
|
}
|
|
60
43
|
|
|
61
|
-
/**
|
|
62
|
-
* Check if IP is banned
|
|
63
|
-
* @param {string} ip
|
|
64
|
-
* @returns {Promise<boolean>}
|
|
65
|
-
*/
|
|
66
44
|
async isBanned(ip) {
|
|
67
45
|
const bannedIPs = await this.getAll();
|
|
68
46
|
|
|
69
|
-
// Support both simple array format and object format
|
|
70
47
|
if (bannedIPs.length > 0 && typeof bannedIPs[0] === 'string') {
|
|
71
48
|
return bannedIPs.includes(ip);
|
|
72
49
|
}
|
|
73
50
|
|
|
74
|
-
// Object format with expiry
|
|
75
51
|
const entry = bannedIPs.find(entry => entry.ip === ip);
|
|
76
52
|
if (!entry) return false;
|
|
77
53
|
|
|
78
|
-
// Check if expired
|
|
79
54
|
if (entry.expiry && Date.now() > entry.expiry) {
|
|
80
55
|
await this.unban(ip);
|
|
81
56
|
return false;
|
|
@@ -84,79 +59,47 @@ class JsonStore {
|
|
|
84
59
|
return true;
|
|
85
60
|
}
|
|
86
61
|
|
|
87
|
-
/**
|
|
88
|
-
* Ban an IP
|
|
89
|
-
* @param {string} ip
|
|
90
|
-
* @param {string} reason
|
|
91
|
-
* @returns {Promise<void>}
|
|
92
|
-
*/
|
|
93
62
|
async ban(ip, reason = 'Suspicious activity') {
|
|
94
63
|
const bannedIPs = await this.getAll();
|
|
95
64
|
|
|
96
|
-
|
|
97
|
-
const existingIndex = bannedIPs.findIndex(entry =>
|
|
65
|
+
const exists = bannedIPs.some(entry =>
|
|
98
66
|
typeof entry === 'string' ? entry === ip : entry.ip === ip
|
|
99
67
|
);
|
|
68
|
+
if (exists) return;
|
|
100
69
|
|
|
101
|
-
|
|
102
|
-
return; // Already banned
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Add new ban with expiry
|
|
106
|
-
const banEntry = {
|
|
70
|
+
bannedIPs.push({
|
|
107
71
|
ip,
|
|
108
72
|
reason,
|
|
109
73
|
timestamp: Date.now(),
|
|
110
74
|
expiry: Date.now() + this.banDuration
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
bannedIPs.push(banEntry);
|
|
75
|
+
});
|
|
114
76
|
|
|
115
77
|
await this._save(bannedIPs);
|
|
116
78
|
}
|
|
117
79
|
|
|
118
|
-
/**
|
|
119
|
-
* Unban an IP
|
|
120
|
-
* @param {string} ip
|
|
121
|
-
* @returns {Promise<void>}
|
|
122
|
-
*/
|
|
123
80
|
async unban(ip) {
|
|
124
81
|
const bannedIPs = await this.getAll();
|
|
125
|
-
|
|
126
82
|
const filtered = bannedIPs.filter(entry =>
|
|
127
83
|
typeof entry === 'string' ? entry !== ip : entry.ip !== ip
|
|
128
84
|
);
|
|
129
|
-
|
|
130
85
|
await this._save(filtered);
|
|
131
86
|
}
|
|
132
87
|
|
|
133
|
-
/**
|
|
134
|
-
* Save data to file and invalidate cache
|
|
135
|
-
* @private
|
|
136
|
-
*/
|
|
137
88
|
async _save(data) {
|
|
138
89
|
try {
|
|
139
90
|
await fs.writeJson(this.filePath, data, { spaces: 2 });
|
|
140
|
-
|
|
141
|
-
// Invalidate cache
|
|
142
|
-
if (this.cache) {
|
|
143
|
-
this.cache.delete('all');
|
|
144
|
-
}
|
|
91
|
+
if (this.cache) this.cache.delete('all');
|
|
145
92
|
} catch (err) {
|
|
146
93
|
console.error('[JsonStore] Error writing file:', err);
|
|
147
94
|
}
|
|
148
95
|
}
|
|
149
96
|
|
|
150
|
-
/**
|
|
151
|
-
* Clean up expired bans
|
|
152
|
-
* @private
|
|
153
|
-
*/
|
|
154
97
|
async _cleanupExpired() {
|
|
155
98
|
const bannedIPs = await this.getAll();
|
|
156
99
|
const now = Date.now();
|
|
157
100
|
|
|
158
101
|
const active = bannedIPs.filter(entry => {
|
|
159
|
-
if (typeof entry === 'string') return true;
|
|
102
|
+
if (typeof entry === 'string') return true;
|
|
160
103
|
return !entry.expiry || now <= entry.expiry;
|
|
161
104
|
});
|
|
162
105
|
|
|
@@ -165,41 +108,24 @@ class JsonStore {
|
|
|
165
108
|
}
|
|
166
109
|
}
|
|
167
110
|
|
|
168
|
-
/**
|
|
169
|
-
* Get statistics
|
|
170
|
-
* @returns {Promise<Object>}
|
|
171
|
-
*/
|
|
172
111
|
async getStats() {
|
|
173
112
|
const bannedIPs = await this.getAll();
|
|
174
113
|
const now = Date.now();
|
|
175
|
-
|
|
176
|
-
let active = 0;
|
|
177
|
-
let expired = 0;
|
|
114
|
+
let active = 0, expired = 0;
|
|
178
115
|
|
|
179
116
|
bannedIPs.forEach(entry => {
|
|
180
|
-
if (typeof entry === 'string') {
|
|
181
|
-
active++;
|
|
182
|
-
} else if (!entry.expiry || now <= entry.expiry) {
|
|
117
|
+
if (typeof entry === 'string' || !entry.expiry || now <= entry.expiry) {
|
|
183
118
|
active++;
|
|
184
119
|
} else {
|
|
185
120
|
expired++;
|
|
186
121
|
}
|
|
187
122
|
});
|
|
188
123
|
|
|
189
|
-
return {
|
|
190
|
-
total: bannedIPs.length,
|
|
191
|
-
active,
|
|
192
|
-
expired
|
|
193
|
-
};
|
|
124
|
+
return { total: bannedIPs.length, active, expired };
|
|
194
125
|
}
|
|
195
126
|
|
|
196
|
-
/**
|
|
197
|
-
* Cleanup and stop intervals
|
|
198
|
-
*/
|
|
199
127
|
destroy() {
|
|
200
|
-
if (this.cleanupInterval)
|
|
201
|
-
clearInterval(this.cleanupInterval);
|
|
202
|
-
}
|
|
128
|
+
if (this.cleanupInterval) clearInterval(this.cleanupInterval);
|
|
203
129
|
}
|
|
204
130
|
}
|
|
205
131
|
|