Repository navigation
Expand file tree
/
Copy pathcaptcha.js
More file actions
157 lines (130 loc) · 5.64 KB
/
Copy pathcaptcha.js
File metadata and controls
157 lines (130 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
async function checkCookieAndCaptcha(r) {
var cookie = r.headersIn.Cookie;
// Check if the secure cookie is present and valid
if (cookie && cookie.includes("MCLVALID")) {
// Implement your logic to validate the cookie value
let valid = await validateCookie(r);
if (valid) {
// Perform an internal subrequest to proxy the request
r.internalRedirect('/internal_proxy');
return
}
}
r.internalRedirect('/captcha_page.html');
}
function checkXForwardedFor(r, ip) {
let xffHeader = r.headersIn['X-Forwarded-For'];
if (xffHeader) {
// Split the header value by comma to handle multiple IPs
let ips = xffHeader.split(',');
// Trim whitespace and check if the target IP is in the list
for (let i = 0; i < ips.length; i++) {
if (ips[i].trim() === ip) {
return true; // IP found in the X-Forwarded-For header
}
}
}
return false; // IP not found in the X-Forwarded-For header
}
async function validateCaptcha(r) {
try {
let thirdPartyApiUrl = 'https://decrypt.mcl.spur.us/api/v1/assessment';
let data = JSON.parse(r.requestText);
let response = await ngx.fetch(thirdPartyApiUrl, {
method: 'POST',
body: data.captchaData,
headers: {
'Content-Type': 'text/plain',
'Token': r.variables.mclVerifyToken,
},
});
if (!response.ok) {
throw new Error(`API call failed: ${response.statusText}`);
}
let responseData = await response.json();
let clientIpAddress = r.remoteAddress;
// Parse the timestamp from the response
let responseTime = new Date(responseData.ts);
// Get the current time
let currentTime = new Date();
// Calculate the difference in seconds
let timeDifference = Math.abs(currentTime - responseTime) / 1000;
// Check if the time difference is within 5 seconds
if (timeDifference > 5) {
r.return(403, JSON.stringify(responseData));
return;
}
if (r.variables.blockVPNs !== "false" && responseData.anon && responseData.vpn) {
r.return(403, JSON.stringify(responseData));
return;
}
if (r.variables.blockProxies !== "false" && responseData.anon && responseData.proxied) {
r.return(403, JSON.stringify(responseData));
return;
}
if (r.variables.testLocalhost !== "false" || responseData.ip == clientIpAddress || (r.variables.checkXForwardedFor !== "false" && checkXForwardedFor(r, responseData.ip))) {
await setSecureCookie(responseData.ip, r);
r.return(200);
} else {
r.return(403, JSON.stringify(responseData));
}
} catch (error) {
r.return(500, "Internal Server Error");
}
}
function hexStringToByteArray(hexString) {
var result = [];
for (var i = 0; i < hexString.length; i += 2) {
result.push(parseInt(hexString.substr(i, 2), 16));
}
return result;
}
async function validateCookie(r) {
const cookies = r.headersIn.Cookie;
if (!cookies) {
return false;
}
const cookieName = "MCLVALID=";
let start = cookies.indexOf(cookieName);
if (start === -1) {
return false;
}
start += cookieName.length;
let end = cookies.indexOf(';', start);
if (end === -1) end = cookies.length;
const encryptedCookie = cookies.substring(start, end).split('.');
const encryptedData = encryptedCookie[0];
const ivHex = encryptedCookie[1];
const iv = new Uint8Array(hexStringToByteArray(ivHex));
const keyMaterial = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(r.variables.cookieSecret));
const key = await crypto.subtle.importKey("raw", keyMaterial, { name: "AES-CBC" }, false, ["decrypt"]);
const decryptedData = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, key, new Uint8Array(hexStringToByteArray(encryptedData)));
const decryptedString = new TextDecoder().decode(decryptedData);
const decryptedParts = decryptedString.split('|');
const decryptedIp = decryptedParts[0];
const expiryTime = decryptedParts[1];
if (Math.floor(Date.now() / 1000) >= parseInt(expiryTime, 10)) {
return false;
}
let isRemoteAddressMatch = r.remoteAddress === decryptedIp;
let isXForwardedForCheckEnabled = r.variables.checkXForwardedFor !== "false";
let isXForwardedForMatch = checkXForwardedFor(r, decryptedIp);
return isRemoteAddressMatch || (isXForwardedForCheckEnabled && isXForwardedForMatch);
}
function byteArrayToHex(buffer) {
var hex = [];
buffer.forEach(b => hex.push((b & 0xFF).toString(16).padStart(2, '0')));
return hex.join('');
}
async function setSecureCookie(ip, r) {
const data = `${ip}|${Math.floor(Date.now() / 1000) + 3600}`; // Data to encrypt
const keyMaterial = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(r.variables.cookieSecret));
const key = await crypto.subtle.importKey("raw", keyMaterial, { name: "AES-CBC" }, false, ["encrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(16)); // Generate a random IV
const encryptedData = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, key, new TextEncoder().encode(data));
const encryptedDataHex = byteArrayToHex(new Uint8Array(encryptedData));
const ivHex = byteArrayToHex(iv);
// Set the encrypted cookie
r.headersOut['Set-Cookie'] = `MCLVALID=${encryptedDataHex}.${ivHex}; Secure; HttpOnly; Path=/; SameSite=Strict`;
}
export default { checkCookieAndCaptcha, validateCaptcha };