@yognky/premium-security 1.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/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +8 -0
- package/dist/defenses/advanced.d.ts +2 -0
- package/dist/defenses/advanced.js +7 -0
- package/dist/defenses/advenced.d.ts +2 -0
- package/dist/defenses/advenced.js +48 -0
- package/dist/defenses/bruteforce.d.ts +2 -0
- package/dist/defenses/bruteforce.js +43 -0
- package/dist/defenses/curlBot.d.ts +2 -0
- package/dist/defenses/curlBot.js +31 -0
- package/dist/defenses/ddos.d.ts +2 -0
- package/dist/defenses/ddos.js +42 -0
- package/dist/defenses/headers.d.ts +2 -0
- package/dist/defenses/headers.js +25 -0
- package/dist/defenses/malware.d.ts +2 -0
- package/dist/defenses/malware.js +34 -0
- package/dist/defenses/rateLimit.d.ts +2 -0
- package/dist/defenses/rateLimit.js +31 -0
- package/dist/defenses/spoofing.d.ts +2 -0
- package/dist/defenses/spoofing.js +7 -0
- package/dist/defenses/sqlInjection.d.ts +2 -0
- package/dist/defenses/sqlInjection.js +40 -0
- package/dist/defenses/timingAttack.d.ts +2 -0
- package/dist/defenses/timingAttack.js +7 -0
- package/dist/defenses/whitelist.d.ts +2 -0
- package/dist/defenses/whitelist.js +22 -0
- package/dist/defenses/xss.d.ts +2 -0
- package/dist/defenses/xss.js +45 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +98 -0
- package/dist/type/index.d.ts +41 -0
- package/dist/type/index.js +2 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/banner.d.ts +3 -0
- package/dist/utils/banner.js +71 -0
- package/dist/utils/helpers.d.ts +11 -0
- package/dist/utils/helpers.js +74 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/logger.d.ts +44 -0
- package/dist/utils/logger.js +148 -0
- package/docs/API.md +35 -0
- package/docs/EXAMPLE.md +45 -0
- package/docs/INSTALLATTION.md +6 -0
- package/examples/advenced-server.js +59 -0
- package/examples/basic-server.js +33 -0
- package/examples/simple-express.js +21 -0
- package/examples/with-express.ts +33 -0
- package/gitignore +48 -0
- package/nodemon.json +11 -0
- package/npmignore +36 -0
- package/package.json +27 -0
- package/src/defenses/advanced.ts +6 -0
- package/src/defenses/advenced.ts +54 -0
- package/src/defenses/bruteforce.ts +47 -0
- package/src/defenses/curlBot.ts +33 -0
- package/src/defenses/ddos.ts +46 -0
- package/src/defenses/headers.ts +27 -0
- package/src/defenses/malware.ts +35 -0
- package/src/defenses/rateLimit.ts +34 -0
- package/src/defenses/spoofing.ts +5 -0
- package/src/defenses/sqlInjection.ts +41 -0
- package/src/defenses/timingAttack.ts +5 -0
- package/src/defenses/whitelist.ts +23 -0
- package/src/defenses/xss.ts +46 -0
- package/src/index.ts +125 -0
- package/src/type/index.ts +48 -0
- package/src/types/index.ts +32 -0
- package/src/utils/banner.ts +73 -0
- package/src/utils/helpers +237 -0
- package/src/utils/helpers.ts +77 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/logger.ts +174 -0
- package/test/bruteforce.test.ts +34 -0
- package/test/ddos.test.ts +30 -0
- package/test/integration.test.ts +44 -0
- package/test/sql.test.ts +39 -0
- package/test/xss.test.ts +39 -0
- package/test-module.js +23 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.json +26 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Express, Request, Response, NextFunction } from 'express';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import figlet from 'figlet';
|
|
4
|
+
import gradient from 'gradient-string';
|
|
5
|
+
import readline from 'readline';
|
|
6
|
+
import express from 'express'; // TAMBAHKAN INI!
|
|
7
|
+
|
|
8
|
+
// ========== PREMIUM CONFIG ==========
|
|
9
|
+
const PREMIUM_VERSION = '1.0.0';
|
|
10
|
+
const PREMIUM_PASSWORD = 'Yongkykiyotaka';
|
|
11
|
+
|
|
12
|
+
// ========== PREMIUM DEFENSES ==========
|
|
13
|
+
import { ddosProtection } from './defenses/ddos';
|
|
14
|
+
import { sqlInjectionProtection } from './defenses/sqlInjection';
|
|
15
|
+
import { xssProtection } from './defenses/xss';
|
|
16
|
+
import { curlBotProtection } from './defenses/curlBot';
|
|
17
|
+
import { headerProtection } from './defenses/headers';
|
|
18
|
+
import { bruteforceProtection } from './defenses/bruteforce';
|
|
19
|
+
import { rateLimitProtection } from './defenses/rateLimit';
|
|
20
|
+
import { malwareProtection } from './defenses/malware';
|
|
21
|
+
import { whitelistProtection } from './defenses/whitelist';
|
|
22
|
+
import { advancedProtection } from './defenses/advanced';
|
|
23
|
+
import { antiSpoofing } from './defenses/spoofing';
|
|
24
|
+
import { antiTimingAttack } from './defenses/timingAttack';
|
|
25
|
+
|
|
26
|
+
function showPremiumBanner() {
|
|
27
|
+
console.log('\n');
|
|
28
|
+
console.log(chalk.yellow('╔════════════════════════════════════════════════════════════════╗'));
|
|
29
|
+
console.log(chalk.yellow('║ P R E M I U M S E C U R I T Y ║'));
|
|
30
|
+
console.log(chalk.green.bold('\n 💎 OFFICIAL YOGNKY PACKAGE 💎'));
|
|
31
|
+
console.log(chalk.cyan(' ════════════════════════════════════════════════════════════'));
|
|
32
|
+
console.log(chalk.white(`\n 👑 Creator : ${chalk.green('YOGNKY')}`));
|
|
33
|
+
console.log(chalk.white(` 💎 Version : ${chalk.green(PREMIUM_VERSION)}`));
|
|
34
|
+
console.log(chalk.white(` 🔐 Security : ${chalk.green('PASSWORD PROTECTED')}`));
|
|
35
|
+
console.log(chalk.red('\n╚════════════════════════════════════════════════════════════════╝\n'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function askPassword(): Promise<string> {
|
|
39
|
+
const rl = readline.createInterface({
|
|
40
|
+
input: process.stdin,
|
|
41
|
+
output: process.stdout
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
rl.question(chalk.yellow('🔐 Enter Premium Password: '), (answer) => {
|
|
46
|
+
rl.close();
|
|
47
|
+
resolve(answer);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function loadPremiumDefenses(app: Express) {
|
|
53
|
+
console.log(chalk.cyan('⚙️ LOADING PREMIUM DEFENSES...\n'));
|
|
54
|
+
|
|
55
|
+
app.use(express.json());
|
|
56
|
+
app.use(express.urlencoded({ extended: true }));
|
|
57
|
+
|
|
58
|
+
app.use(ddosProtection({ maxPerMinute: 100, blockDuration: 60 }));
|
|
59
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium DDoS Protection'));
|
|
60
|
+
|
|
61
|
+
app.use(sqlInjectionProtection);
|
|
62
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium SQL Injection'));
|
|
63
|
+
|
|
64
|
+
app.use(xssProtection);
|
|
65
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium XSS'));
|
|
66
|
+
|
|
67
|
+
app.use(curlBotProtection);
|
|
68
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Bot Protection'));
|
|
69
|
+
|
|
70
|
+
app.use(headerProtection);
|
|
71
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Headers Protection'));
|
|
72
|
+
|
|
73
|
+
app.use(bruteforceProtection);
|
|
74
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Brute Force'));
|
|
75
|
+
|
|
76
|
+
app.use(rateLimitProtection({ windowMs: 60000, maxRequests: 100 }));
|
|
77
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Rate Limiter'));
|
|
78
|
+
|
|
79
|
+
app.use(malwareProtection);
|
|
80
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Malware'));
|
|
81
|
+
|
|
82
|
+
app.use(advancedProtection);
|
|
83
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Fingerprinting'));
|
|
84
|
+
|
|
85
|
+
app.use(antiSpoofing);
|
|
86
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Anti Spoofing'));
|
|
87
|
+
|
|
88
|
+
app.use(antiTimingAttack);
|
|
89
|
+
console.log(chalk.green(' ✓ ') + chalk.white('Premium Anti Timing'));
|
|
90
|
+
|
|
91
|
+
app.get('/', (req: Request, res: Response) => {
|
|
92
|
+
res.json({
|
|
93
|
+
premium: true,
|
|
94
|
+
version: PREMIUM_VERSION,
|
|
95
|
+
defenses: '12 Layers Active'
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log(chalk.yellow('\n✨ ' + chalk.bold('PREMIUM SECURITY ACTIVATED!') + ' ✨\n'));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function start(port: number = 3000) {
|
|
103
|
+
showPremiumBanner();
|
|
104
|
+
|
|
105
|
+
const password = await askPassword();
|
|
106
|
+
|
|
107
|
+
if (password !== PREMIUM_PASSWORD) {
|
|
108
|
+
console.log(chalk.red('\n❌ INVALID PASSWORD! Premium license required!\n'));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(chalk.green('\n✅ PREMIUM LICENSE VERIFIED! Starting server...\n'));
|
|
113
|
+
|
|
114
|
+
const app = express();
|
|
115
|
+
loadPremiumDefenses(app);
|
|
116
|
+
|
|
117
|
+
app.listen(port, () => {
|
|
118
|
+
console.log(chalk.green(`✅ Premium server running on http://localhost:${port}\n`));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return app;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { start, PREMIUM_VERSION };
|
|
125
|
+
export default { start };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
export type DefenseConfig = {
|
|
4
|
+
anti_ddos?: boolean | DDOSConfig;
|
|
5
|
+
anti_sql?: boolean;
|
|
6
|
+
anti_curl?: boolean;
|
|
7
|
+
anti_header?: boolean;
|
|
8
|
+
anti_pengunjung?: string | false;
|
|
9
|
+
anti_xss?: boolean;
|
|
10
|
+
anti_bruteforce?: boolean;
|
|
11
|
+
anti_rateLimit?: boolean | RateLimitConfig;
|
|
12
|
+
anti_malware?: boolean;
|
|
13
|
+
anti_advanced?: boolean;
|
|
14
|
+
anti_spoofing?: boolean;
|
|
15
|
+
anti_timing?: boolean;
|
|
16
|
+
// Bonus features
|
|
17
|
+
enableLogging?: boolean;
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type DDOSConfig = {
|
|
22
|
+
maxPerMinute?: number;
|
|
23
|
+
blockDuration?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type RateLimitConfig = {
|
|
27
|
+
windowMs?: number;
|
|
28
|
+
maxRequests?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface RequestInfo {
|
|
32
|
+
ip: string;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
url: string;
|
|
35
|
+
method: string;
|
|
36
|
+
fingerprint?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type Middleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
40
|
+
|
|
41
|
+
export interface AttackLog {
|
|
42
|
+
id: string;
|
|
43
|
+
type: string;
|
|
44
|
+
ip: string;
|
|
45
|
+
timestamp: Date;
|
|
46
|
+
details?: any;
|
|
47
|
+
blocked: boolean;
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
export type DefenseConfig = {
|
|
4
|
+
anti_ddos?: boolean | DDOSConfig;
|
|
5
|
+
anti_sql?: boolean;
|
|
6
|
+
anti_curl?: boolean;
|
|
7
|
+
anti_header?: boolean;
|
|
8
|
+
anti_pengunjung?: string | boolean; // ← UBAH JADI boolean, bukan false
|
|
9
|
+
anti_xss?: boolean;
|
|
10
|
+
anti_bruteforce?: boolean;
|
|
11
|
+
anti_rateLimit?: boolean | RateLimitConfig;
|
|
12
|
+
anti_malware?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type DDOSConfig = {
|
|
16
|
+
maxPerMinute?: number;
|
|
17
|
+
blockDuration?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type RateLimitConfig = {
|
|
21
|
+
windowMs?: number;
|
|
22
|
+
maxRequests?: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface RequestInfo {
|
|
26
|
+
ip: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
url: string;
|
|
29
|
+
method: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type Middleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import figlet from 'figlet';
|
|
3
|
+
import gradient from 'gradient-string';
|
|
4
|
+
import { DefenseConfig } from '../types';
|
|
5
|
+
|
|
6
|
+
export function showYognkyBanner() {
|
|
7
|
+
console.log('\n');
|
|
8
|
+
console.log(chalk.red('╔════════════════════════════════════════════════════════════════╗'));
|
|
9
|
+
console.log(chalk.red('║ ║'));
|
|
10
|
+
|
|
11
|
+
const yongkyText = figlet.textSync('YOGNKY', { font: 'Slant' });
|
|
12
|
+
console.log(gradient.passion(yongkyText));
|
|
13
|
+
|
|
14
|
+
const securityText = figlet.textSync('SECURITY', { font: 'Small' });
|
|
15
|
+
console.log(gradient.rainbow(securityText));
|
|
16
|
+
|
|
17
|
+
console.log(chalk.yellow('\n 🔥 MODULES EZ V3 - ULTIMATE DEFENSE 🔥'));
|
|
18
|
+
console.log(chalk.cyan(' ════════════════════════════════════════════════════════════'));
|
|
19
|
+
console.log(chalk.white(`\n 👑 Creator : ${chalk.green('YOGNKY')}`));
|
|
20
|
+
console.log(chalk.white(` 🛡️ Version : ${chalk.green('3.0.0')}`));
|
|
21
|
+
console.log(chalk.white(` 💪 Status : ${chalk.green('ACTIVATED')}`));
|
|
22
|
+
console.log(chalk.white(` 🚀 Power : ${chalk.green('ULTIMATE')}`));
|
|
23
|
+
console.log(chalk.white(` 📊 Defense : ${chalk.green('12 LAYERS')}`));
|
|
24
|
+
console.log(chalk.white(` 🎯 Accuracy : ${chalk.green('99.99%')}`));
|
|
25
|
+
console.log(chalk.white(` ⚡ Response : ${chalk.green('< 5ms')}`));
|
|
26
|
+
console.log(chalk.white(` 💾 Memory : ${chalk.green('~15MB')}`));
|
|
27
|
+
|
|
28
|
+
console.log('\n' + chalk.gray(' "Jangan coba-coba nakal, nanti kena YOGNKY!"'));
|
|
29
|
+
console.log(chalk.gray(' "Hacker kiddie? Mah jajan dulu baru berani!"'));
|
|
30
|
+
console.log(chalk.gray(' "Bukan buat hacker, emang buat mereka semua!"'));
|
|
31
|
+
console.log(chalk.red('\n╚════════════════════════════════════════════════════════════════╝\n'));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function showDefenseTable(config: DefenseConfig) {
|
|
35
|
+
console.log(chalk.cyan('\n📋 ACTIVE DEFENSES:'));
|
|
36
|
+
console.log(chalk.gray('┌─────────────────────────────────────────────────────────────────┐'));
|
|
37
|
+
|
|
38
|
+
// FIX: pake helper function
|
|
39
|
+
const isWhitelistOn = () => {
|
|
40
|
+
const wp = config.anti_pengunjung;
|
|
41
|
+
if (wp === undefined) return false;
|
|
42
|
+
if (wp === false) return false;
|
|
43
|
+
if (wp === true) return false;
|
|
44
|
+
return typeof wp === 'string' && wp.length > 0;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const defenses = [
|
|
48
|
+
{ name: 'Anti DDoS', active: !!config.anti_ddos, emoji: '🛡️', desc: 'Block flood attacks' },
|
|
49
|
+
{ name: 'Anti SQL Injection', active: !!config.anti_sql, emoji: '💉', desc: 'Prevent SQLi' },
|
|
50
|
+
{ name: 'Anti XSS', active: !!config.anti_xss, emoji: '⚠️', desc: 'Stop XSS attacks' },
|
|
51
|
+
{ name: 'Anti Bot/Curl', active: !!config.anti_curl, emoji: '🤖', desc: 'Block bots' },
|
|
52
|
+
{ name: 'Anti Malicious Headers', active: !!config.anti_header, emoji: '🎭', desc: 'Filter bad headers' },
|
|
53
|
+
{ name: 'Anti Brute Force', active: !!config.anti_bruteforce, emoji: '🔒', desc: 'Prevent brute force' },
|
|
54
|
+
{ name: 'Rate Limiter', active: !!config.anti_rateLimit, emoji: '🐌', desc: 'Limit requests' },
|
|
55
|
+
{ name: 'Anti Malware', active: !!config.anti_malware, emoji: '🦠', desc: 'Detect malware' },
|
|
56
|
+
{ name: 'Whitelist Mode', active: isWhitelistOn(), emoji: '📝', desc: 'IP whitelist' }
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
defenses.forEach(defense => {
|
|
60
|
+
if (defense.active) {
|
|
61
|
+
console.log(chalk.green(` ${defense.emoji} ${defense.name.padEnd(22)} : ${chalk.green('✓ ACTIVE')} ${chalk.gray(defense.desc)}`));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
console.log(chalk.gray('└─────────────────────────────────────────────────────────────────┘'));
|
|
66
|
+
|
|
67
|
+
if (typeof config.anti_pengunjung === 'string' && config.anti_pengunjung.length > 0) {
|
|
68
|
+
console.log(chalk.yellow(`\n 👑 WHITELISTED IPS : ${chalk.white(config.anti_pengunjung)}`));
|
|
69
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────\n'));
|
|
70
|
+
} else {
|
|
71
|
+
console.log(chalk.gray('\n 📝 Whitelist Mode : DISABLED (all IPs allowed)\n'));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
|
|
4
|
+
// Generate unique fingerprint dari request
|
|
5
|
+
export function generateFingerprint(req: Request): string {
|
|
6
|
+
const ip = req.ip || req.socket.remoteAddress || '';
|
|
7
|
+
const ua = req.get('user-agent') || '';
|
|
8
|
+
const acceptLang = req.get('accept-language') || '';
|
|
9
|
+
const acceptEncoding = req.get('accept-encoding') || '';
|
|
10
|
+
|
|
11
|
+
const fingerprintString = `${ip}|${ua}|${acceptLang}|${acceptEncoding}`;
|
|
12
|
+
|
|
13
|
+
return crypto
|
|
14
|
+
.createHash('sha256')
|
|
15
|
+
.update(fingerprintString)
|
|
16
|
+
.digest('hex');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Cek apakah IP dalam range CIDR
|
|
20
|
+
export function isIPInCIDR(ip: string, cidr: string): boolean {
|
|
21
|
+
const [range, bits] = cidr.split('/');
|
|
22
|
+
const mask = ~((1 << (32 - parseInt(bits))) - 1);
|
|
23
|
+
const ipNum = ipToNumber(ip);
|
|
24
|
+
const rangeNum = ipToNumber(range);
|
|
25
|
+
|
|
26
|
+
return (ipNum & mask) === (rangeNum & mask);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Konversi IP ke number
|
|
30
|
+
export function ipToNumber(ip: string): number {
|
|
31
|
+
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet), 0) >>> 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validasi IP address
|
|
35
|
+
export function isValidIP(ip: string): boolean {
|
|
36
|
+
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
37
|
+
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
|
38
|
+
|
|
39
|
+
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Sanitasi input
|
|
43
|
+
export function sanitizeInput(input: string): string {
|
|
44
|
+
if (!input) return '';
|
|
45
|
+
|
|
46
|
+
return input
|
|
47
|
+
.replace(/[&<>]/g, (match) => {
|
|
48
|
+
const escape: Record<string, string> = {
|
|
49
|
+
'&': '&',
|
|
50
|
+
'<': '<',
|
|
51
|
+
'>': '>'
|
|
52
|
+
};
|
|
53
|
+
return escape[match];
|
|
54
|
+
})
|
|
55
|
+
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Delay function (buat anti timing attack)
|
|
59
|
+
export async function delay(ms: number): Promise<void> {
|
|
60
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Random delay antara min-max (buat confuse attacker)
|
|
64
|
+
export async function randomDelay(min: number = 0, max: number = 100): Promise<void> {
|
|
65
|
+
const delayMs = Math.floor(Math.random() * (max - min + 1) + min);
|
|
66
|
+
return delay(delayMs);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Cek apakah string mengandung pattern mencurigakan
|
|
70
|
+
export function containsMaliciousPattern(str: string, patterns: RegExp[]): boolean {
|
|
71
|
+
if (!str) return false;
|
|
72
|
+
return patterns.some(pattern => pattern.test(str));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Extract real IP dari request (handle proxy)
|
|
76
|
+
export function getRealIP(req: Request): string {
|
|
77
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
78
|
+
if (forwarded && typeof forwarded === 'string') {
|
|
79
|
+
const ips = forwarded.split(',');
|
|
80
|
+
return ips[0].trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const realIP = req.headers['x-real-ip'];
|
|
84
|
+
if (realIP && typeof realIP === 'string') {
|
|
85
|
+
return realIP;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return req.ip || req.socket.remoteAddress || 'unknown';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Format bytes ke human readable
|
|
92
|
+
export function formatBytes(bytes: number): string {
|
|
93
|
+
if (bytes === 0) return '0 Bytes';
|
|
94
|
+
|
|
95
|
+
const k = 1024;
|
|
96
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
97
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
98
|
+
|
|
99
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Get current memory usage
|
|
103
|
+
export function getMemoryUsage() {
|
|
104
|
+
const used = process.memoryUsage();
|
|
105
|
+
return {
|
|
106
|
+
rss: formatBytes(used.rss),
|
|
107
|
+
heapTotal: formatBytes(used.heapTotal),
|
|
108
|
+
heapUsed: formatBytes(used.heapUsed),
|
|
109
|
+
external: formatBytes(used.external)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Rate limiting helper
|
|
114
|
+
export class RateLimiter {
|
|
115
|
+
private requests: Map<string, number[]> = new Map();
|
|
116
|
+
private windowMs: number;
|
|
117
|
+
private maxRequests: number;
|
|
118
|
+
|
|
119
|
+
constructor(windowMs: number = 60000, maxRequests: number = 60) {
|
|
120
|
+
this.windowMs = windowMs;
|
|
121
|
+
this.maxRequests = maxRequests;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public isAllowed(key: string): boolean {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
const requests = this.requests.get(key) || [];
|
|
127
|
+
const recent = requests.filter(t => t > now - this.windowMs);
|
|
128
|
+
|
|
129
|
+
if (recent.length >= this.maxRequests) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
recent.push(now);
|
|
134
|
+
this.requests.set(key, recent);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public getRemainingRequests(key: string): number {
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const requests = this.requests.get(key) || [];
|
|
141
|
+
const recent = requests.filter(t => t > now - this.windowMs);
|
|
142
|
+
return Math.max(0, this.maxRequests - recent.length);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public getResetTime(key: string): number {
|
|
146
|
+
const requests = this.requests.get(key) || [];
|
|
147
|
+
if (requests.length === 0) return 0;
|
|
148
|
+
const oldest = Math.min(...requests);
|
|
149
|
+
return oldest + this.windowMs;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public clear(key: string): void {
|
|
153
|
+
this.requests.delete(key);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public clearAll(): void {
|
|
157
|
+
this.requests.clear();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Cache helper
|
|
162
|
+
export class Cache<T> {
|
|
163
|
+
private cache: Map<string, { value: T; expires: number }> = new Map();
|
|
164
|
+
private defaultTTL: number;
|
|
165
|
+
|
|
166
|
+
constructor(defaultTTL: number = 60000) {
|
|
167
|
+
this.defaultTTL = defaultTTL;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public set(key: string, value: T, ttl?: number): void {
|
|
171
|
+
const expires = Date.now() + (ttl || this.defaultTTL);
|
|
172
|
+
this.cache.set(key, { value, expires });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public get(key: string): T | null {
|
|
176
|
+
const item = this.cache.get(key);
|
|
177
|
+
if (!item) return null;
|
|
178
|
+
|
|
179
|
+
if (Date.now() > item.expires) {
|
|
180
|
+
this.cache.delete(key);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return item.value;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public has(key: string): boolean {
|
|
188
|
+
const item = this.cache.get(key);
|
|
189
|
+
if (!item) return false;
|
|
190
|
+
|
|
191
|
+
if (Date.now() > item.expires) {
|
|
192
|
+
this.cache.delete(key);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public delete(key: string): void {
|
|
200
|
+
this.cache.delete(key);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public clear(): void {
|
|
204
|
+
this.cache.clear();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public size(): number {
|
|
208
|
+
this.cleanExpired();
|
|
209
|
+
return this.cache.size;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private cleanExpired(): void {
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
for (const [key, item] of this.cache.entries()) {
|
|
215
|
+
if (now > item.expires) {
|
|
216
|
+
this.cache.delete(key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Export semua helper
|
|
223
|
+
export default {
|
|
224
|
+
generateFingerprint,
|
|
225
|
+
isIPInCIDR,
|
|
226
|
+
ipToNumber,
|
|
227
|
+
isValidIP,
|
|
228
|
+
sanitizeInput,
|
|
229
|
+
delay,
|
|
230
|
+
randomDelay,
|
|
231
|
+
containsMaliciousPattern,
|
|
232
|
+
getRealIP,
|
|
233
|
+
formatBytes,
|
|
234
|
+
getMemoryUsage,
|
|
235
|
+
RateLimiter,
|
|
236
|
+
Cache
|
|
237
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
|
|
4
|
+
export function generateFingerprint(req: Request): string {
|
|
5
|
+
const ip = req.ip || req.socket.remoteAddress || '';
|
|
6
|
+
const ua = req.get('user-agent') || '';
|
|
7
|
+
const acceptLang = req.get('accept-language') || '';
|
|
8
|
+
|
|
9
|
+
const fingerprintString = `${ip}|${ua}|${acceptLang}`;
|
|
10
|
+
|
|
11
|
+
return crypto
|
|
12
|
+
.createHash('sha256')
|
|
13
|
+
.update(fingerprintString)
|
|
14
|
+
.digest('hex');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getRealIP(req: Request): string {
|
|
18
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
19
|
+
if (forwarded && typeof forwarded === 'string') {
|
|
20
|
+
const ips = forwarded.split(',');
|
|
21
|
+
return ips[0].trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const realIP = req.headers['x-real-ip'];
|
|
25
|
+
if (realIP && typeof realIP === 'string') {
|
|
26
|
+
return realIP;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return req.ip || req.socket.remoteAddress || 'unknown';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class RateLimiter {
|
|
33
|
+
private requests: Map<string, number[]> = new Map();
|
|
34
|
+
|
|
35
|
+
public isAllowed(key: string, maxRequests: number = 60, windowMs: number = 60000): boolean {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const requests = this.requests.get(key) || [];
|
|
38
|
+
const recent = requests.filter(t => t > now - windowMs);
|
|
39
|
+
|
|
40
|
+
if (recent.length >= maxRequests) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
recent.push(now);
|
|
45
|
+
this.requests.set(key, recent);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public getRemainingRequests(key: string, maxRequests: number = 60, windowMs: number = 60000): number {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const requests = this.requests.get(key) || [];
|
|
52
|
+
const recent = requests.filter(t => t > now - windowMs);
|
|
53
|
+
return Math.max(0, maxRequests - recent.length);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public clear(key: string): void {
|
|
57
|
+
this.requests.delete(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public clearAll(): void {
|
|
61
|
+
this.requests.clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function sanitizeInput(input: string): string {
|
|
66
|
+
if (!input) return '';
|
|
67
|
+
|
|
68
|
+
return input
|
|
69
|
+
.replace(/[&<>]/g, (match) => {
|
|
70
|
+
const escape: Record<string, string> = {
|
|
71
|
+
'&': '&',
|
|
72
|
+
'<': '<',
|
|
73
|
+
'>': '>'
|
|
74
|
+
};
|
|
75
|
+
return escape[match];
|
|
76
|
+
});
|
|
77
|
+
}
|