csrf-shield 1.0.1 → 1.0.3
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/app.js +69 -47
- package/old.js +74 -0
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -1,70 +1,92 @@
|
|
|
1
|
-
const
|
|
1
|
+
const crypto = require('crypto');
|
|
2
2
|
|
|
3
3
|
module.exports = function(options = {}) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
// Buffer işlemleri string işlemlerinden hızlıdır, secret'ı hazırda tutuyoruz.
|
|
5
|
+
const secretKey = options.secret ? Buffer.from(options.secret) : crypto.randomBytes(32);
|
|
6
|
+
const timeout = options.timeout || 1000 * 60 * 10;
|
|
7
|
+
const ALGORITHM = 'sha1'; // Hız için SHA1 (CSRF için yeterince güvenli). Daha yüksek güvenlik için 'sha256' yapın.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function parseToken(token) {
|
|
16
|
-
try {
|
|
17
|
-
const bytes = CryptoJS.AES.decrypt(token, secret);
|
|
18
|
-
const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
|
|
19
|
-
return JSON.parse(decryptedData);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
throw new Error('Invalid token');
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function verifyToken(token, ip, userAgent) {
|
|
26
|
-
try {
|
|
27
|
-
const { ip: tokenIp, userAgent: tokenUserAgent, timestamp } = parseToken(token);
|
|
28
|
-
|
|
29
|
-
if (tokenIp !== ip || tokenUserAgent !== userAgent) {
|
|
30
|
-
return { valid: false, reason: 'Token mismatch' };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (Date.now() - timestamp > timeout) {
|
|
34
|
-
return { valid: false, reason: 'Token expired' };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return { valid: true };
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return { valid: false, reason: error.message };
|
|
40
|
-
}
|
|
9
|
+
// IP'yi en hızlı şekilde alma fonksiyonu
|
|
10
|
+
// Proxy güvenliği için 'trust proxy' ayarının express'te yapılı olduğunu varsayıyoruz.
|
|
11
|
+
function getIP(req) {
|
|
12
|
+
return req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.socket.remoteAddress || '';
|
|
41
13
|
}
|
|
42
14
|
|
|
43
15
|
return {
|
|
44
16
|
middleware: function(req, res, next) {
|
|
17
|
+
// MEMOIZATION: Token sadece ilk çağrıldığında hesaplanır, sonra cache'den gelir.
|
|
18
|
+
let _cachedToken = null;
|
|
19
|
+
|
|
45
20
|
req.csrfToken = function() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
21
|
+
if (_cachedToken) return _cachedToken;
|
|
22
|
+
|
|
23
|
+
const timestamp = Date.now();
|
|
24
|
+
// User-Agent ve IP'yi al
|
|
25
|
+
const ua = req.headers['user-agent'] || '';
|
|
26
|
+
const ip = getIP(req);
|
|
27
|
+
|
|
28
|
+
// Tek seferlik string birleştirme (V8 motoru bunu çok iyi optimize eder)
|
|
29
|
+
// Format: timestamp|ip|ua
|
|
30
|
+
const payload = timestamp + '|' + ip + '|' + ua;
|
|
31
|
+
|
|
32
|
+
// C++ Binding'e tek çağrı (Overhead'i azaltır)
|
|
33
|
+
const signature = crypto.createHmac(ALGORITHM, secretKey)
|
|
34
|
+
.update(payload)
|
|
35
|
+
.digest('base64');
|
|
36
|
+
|
|
37
|
+
// Token: timestamp:signature
|
|
38
|
+
_cachedToken = timestamp + ':' + signature;
|
|
39
|
+
return _cachedToken;
|
|
49
40
|
};
|
|
50
41
|
next();
|
|
51
42
|
},
|
|
52
43
|
|
|
53
44
|
verifyToken: function() {
|
|
54
45
|
return function(req, res, next) {
|
|
55
|
-
|
|
46
|
+
// Token'ı en olası lokasyonlardan sırayla dene
|
|
47
|
+
const token = req.body?._csrf || req.headers['x-csrf-token'] || req.query?._csrf;
|
|
56
48
|
|
|
57
49
|
if (!token) {
|
|
58
|
-
return res.status(403).json({ status: false, message: '
|
|
50
|
+
return res.status(403).json({ status: false, message: 'MISSING_TOKEN' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// OPTIMIZASYON: Split yerine hızlı indeks bulma
|
|
54
|
+
const separatorIndex = token.indexOf(':');
|
|
55
|
+
if (separatorIndex === -1) {
|
|
56
|
+
return res.status(403).json({ status: false, message: 'INVALID_FORMAT' });
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
const
|
|
59
|
+
// Timestamp'i parse et
|
|
60
|
+
const timestampStr = token.substring(0, separatorIndex);
|
|
61
|
+
const receivedSignature = token.substring(separatorIndex + 1);
|
|
62
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
63
|
+
|
|
64
|
+
// 1. ADIM: Kriptografik işlemden ÖNCE zaman kontrolü (En ucuz kontrol)
|
|
65
|
+
if (!timestamp || (Date.now() - timestamp > timeout)) {
|
|
66
|
+
return res.status(403).json({ status: false, message: 'TOKEN_EXPIRED' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. ADIM: İmzayı yeniden oluştur
|
|
70
|
+
const ua = req.headers['user-agent'] || '';
|
|
71
|
+
const ip = getIP(req);
|
|
72
|
+
|
|
73
|
+
const expectedSignature = crypto.createHmac(ALGORITHM, secretKey)
|
|
74
|
+
.update(timestampStr + '|' + ip + '|' + ua)
|
|
75
|
+
.digest('base64');
|
|
63
76
|
|
|
64
|
-
|
|
77
|
+
// 3. ADIM: Timing-Safe karşılaştırma (Buffer seviyesinde)
|
|
78
|
+
const signatureBuf = Buffer.from(receivedSignature);
|
|
79
|
+
const expectedBuf = Buffer.from(expectedSignature);
|
|
65
80
|
|
|
66
|
-
if (!
|
|
67
|
-
|
|
81
|
+
if (signatureBuf.length !== expectedBuf.length || !crypto.timingSafeEqual(signatureBuf, expectedBuf)) {
|
|
82
|
+
console.log('CSRF token mismatch:', {
|
|
83
|
+
received: receivedSignature,
|
|
84
|
+
expected: expectedSignature,
|
|
85
|
+
ip: ip,
|
|
86
|
+
ua: ua
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return res.status(403).json({ status: false, message: 'CSRF_TOKEN_INVALID' });
|
|
68
90
|
}
|
|
69
91
|
|
|
70
92
|
next();
|
package/old.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const CryptoJS = require('crypto-js');
|
|
2
|
+
|
|
3
|
+
module.exports = function(options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
secret = CryptoJS.lib.WordArray.random(32).toString(),
|
|
6
|
+
timeout = 1000 * 60 * 10,
|
|
7
|
+
} = options;
|
|
8
|
+
|
|
9
|
+
function generateToken(ip, userAgent) {
|
|
10
|
+
const timestamp = Date.now();
|
|
11
|
+
const data = JSON.stringify({ ip, userAgent, timestamp });
|
|
12
|
+
return CryptoJS.AES.encrypt(data, secret).toString();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseToken(token) {
|
|
16
|
+
try {
|
|
17
|
+
const bytes = CryptoJS.AES.decrypt(token, secret);
|
|
18
|
+
const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
|
|
19
|
+
return JSON.parse(decryptedData);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw new Error('Invalid token');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function verifyToken(token, ip, userAgent) {
|
|
26
|
+
try {
|
|
27
|
+
const { ip: tokenIp, userAgent: tokenUserAgent, timestamp } = parseToken(token);
|
|
28
|
+
|
|
29
|
+
if (tokenIp !== ip || tokenUserAgent !== userAgent) {
|
|
30
|
+
return { valid: false, reason: 'Token mismatch' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (Date.now() - timestamp > timeout) {
|
|
34
|
+
return { valid: false, reason: 'Token expired' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { valid: true };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return { valid: false, reason: error.message };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
middleware: function(req, res, next) {
|
|
45
|
+
req.csrfToken = function() {
|
|
46
|
+
const ip = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.ip;
|
|
47
|
+
const userAgent = req.headers['user-agent'];
|
|
48
|
+
return generateToken(ip, userAgent);
|
|
49
|
+
};
|
|
50
|
+
next();
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
verifyToken: function() {
|
|
54
|
+
return function(req, res, next) {
|
|
55
|
+
const token = req.body._csrf || req.query._csrf || req.headers['x-csrf-token'];
|
|
56
|
+
|
|
57
|
+
if (!token) {
|
|
58
|
+
return res.status(403).json({ status: false, message: 'CSRF token is missing' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const ip = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.ip;
|
|
62
|
+
const userAgent = req.headers['user-agent'];
|
|
63
|
+
|
|
64
|
+
const { valid, reason } = verifyToken(token, ip, userAgent);
|
|
65
|
+
|
|
66
|
+
if (!valid) {
|
|
67
|
+
return res.status(403).json({ status: false, message: `CSRF_TOKEN_INVALID`, details: reason });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
next();
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
};
|