EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
crowdsec.js
Go to the documentation of this file.
1module.exports.CreateCrowdSecBouncer = function (parent, config) {
2 const obj = {};
3
4 // Setup constants
5 const { getLogger } = require('@crowdsec/express-bouncer/src/nodejs-bouncer/lib/logger');
6 const { configure, renderBanWall, testConnectionToCrowdSec, getRemediationForIp } = require('@crowdsec/express-bouncer/src/nodejs-bouncer');
7 const applyCaptcha = require('@crowdsec/express-bouncer/src/express-crowdsec-middleware/lib/captcha');
8 const { BYPASS_REMEDIATION, CAPTCHA_REMEDIATION, BAN_REMEDIATION } = require('@crowdsec/express-bouncer/src/nodejs-bouncer/lib/constants'); // "bypass", "captcha", "ban";
9 const svgCaptcha = require('svg-captcha');
10 const { renderCaptchaWall } = require('@crowdsec/express-bouncer/src/nodejs-bouncer');
11
12 // Current captcha state
13 const currentCaptchaIpList = {};
14
15 // Set the default values. "config" will come in with lowercase names with everything, so we need to correct some value names.
16 if (typeof config.useragent != 'string') { config.useragent = 'CrowdSec Express-NodeJS bouncer/v0.0.1'; }
17 if (typeof config.timeout != 'number') { config.timeout = 2000; }
18 if ((typeof config.fallbackremediation != 'string') || (['bypass', 'captcha', 'ban'].indexOf(config.fallbackremediation) == -1)) { config.fallbackremediation = BAN_REMEDIATION; }
19 if (typeof config.maxremediation != 'number') { config.maxremediation = BAN_REMEDIATION; }
20 if (typeof config.captchagenerationcacheduration != 'number') { config.captchagenerationcacheduration = 60 * 1000; } // 60 seconds
21 if (typeof config.captcharesolutioncacheduration != 'number') { config.captcharesolutioncacheduration = 30 * 60 * 1000; } // 30 minutes
22 if (typeof config.captchatexts != 'object') { config.captchatexts = {}; } else {
23 if (typeof config.captchatexts.tabtitle == 'string') { config.captchatexts.tabTitle = config.captchatexts.tabtitle; delete config.captchatexts.tabtitle; } // Fix "tabTitle" capitalization
24 }
25 if (typeof config.bantexts != 'object') { config.bantexts = {}; } else {
26 if (typeof config.bantexts.tabtitle == 'string') { config.bantexts.tabTitle = config.bantexts.tabtitle; delete config.bantexts.tabtitle; } // Fix "tabTitle" capitalization
27 }
28 if (typeof config.colors != 'object') { config.colors = {}; } else {
29 var colors = {};
30 // All of the values in "text" and "background" sections happen to be lowercase, so, we can use the values as-is.
31 if (typeof config.colors.text == 'object') { colors.text = config.colors.text; }
32 if (typeof config.colors.background == 'object') { colors.background = config.colors.background; }
33 config.colors = colors;
34 }
35 if (typeof config.hidecrowdsecmentions != 'boolean') { config.hidecrowdsecmentions = false; }
36 if (typeof config.customcss != 'string') { delete config.customcss; }
37 if (typeof config.bypass != 'boolean') { config.bypass = false; }
38 if (typeof config.customlogger != 'object') { delete config.customlogger; }
39 if (typeof config.bypassconnectiontest != 'boolean') { config.bypassconnectiontest = false; }
40
41 // Setup the logger
42 var logger = config.customLogger ? config.customLogger : getLogger();
43
44 // Configure the bouncer
45 configure({
46 url: config.url,
47 apiKey: config.apikey,
48 userAgent: config.useragent,
49 timeout: config.timeout,
50 fallbackRemediation: config.fallbackremediation,
51 maxRemediation: config.maxremediation,
52 captchaTexts: config.captchatexts,
53 banTexts: config.bantexts,
54 colors: config.colors,
55 hideCrowdsecMentions: config.hidecrowdsecmentions,
56 customCss: config.customcss
57 });
58
59 // Test connectivity
60 obj.testConnectivity = async function() { return (await testConnectionToCrowdSec())['success']; }
61
62 // Process a web request
63 obj.process = async function (domain, req, res, next) {
64 try {
65 var remediation = config.fallbackremediation;
66 try { remediation = await getRemediationForIp(req.clientIp); } catch (ex) { }
67 //console.log('CrowdSec', req.clientIp, remediation, req.url);
68 switch (remediation) {
69 case BAN_REMEDIATION:
70 const banWallTemplate = await renderBanWall();
71 res.status(403);
72 res.send(banWallTemplate);
73 return true;
74 case CAPTCHA_REMEDIATION:
75 if ((currentCaptchaIpList[req.clientIp] == null) || (currentCaptchaIpList[req.clientIp].resolved !== true)) {
76 var domainCaptchaUrl = ((domain != null) && (domain.id != '') && (domain.dns == null)) ? ('/' + domain.id + '/captcha.ashx') : '/captcha.ashx';
77 if (req.url != domainCaptchaUrl) { res.redirect(domainCaptchaUrl); return true; }
78 }
79 break;
80 }
81 } catch (ex) { }
82 return false;
83 }
84
85 // Process a captcha request
86 obj.applyCaptcha = async function (req, res, next) {
87 await applyCaptchaEx(req.clientIp, req, res, next, config.captchagenerationcacheduration, config.captcharesolutioncacheduration, logger);
88 }
89
90 // Process a captcha request
91 async function applyCaptchaEx(ip, req, res, next, captchaGenerationCacheDuration, captchaResolutionCacheDuration, loggerInstance) {
92 logger = loggerInstance;
93 let error = false;
94
95 if (currentCaptchaIpList[ip] == null) {
96 generateCaptcha(ip, captchaGenerationCacheDuration);
97 } else {
98 if (currentCaptchaIpList[ip] && currentCaptchaIpList[ip].resolved) {
99 logger.debug({ type: 'CAPTCHA_ALREADY_SOLVED', ip });
100 next();
101 return;
102 } else {
103 if (req.body && req.body.crowdsec_captcha) {
104 if (req.body.refresh === '1') { generateCaptcha(ip, captchaGenerationCacheDuration); }
105 if (req.body.phrase !== '') {
106 if (currentCaptchaIpList[ip].text === req.body.phrase) {
107 currentCaptchaIpList[ip].resolved = true;
108 setTimeout(function() { if (currentCaptchaIpList[ip]) { delete currentCaptchaIpList[ip]; } }, captchaResolutionCacheDuration);
109 res.redirect(req.originalUrl);
110 logger.info({ type: 'CAPTCHA_RESOLUTION', ip, result: true });
111 return;
112 } else {
113 logger.info({ type: 'CAPTCHA_RESOLUTION', ip, result: false });
114 error = true;
115 }
116 }
117 }
118 }
119 }
120
121 const captchaWallTemplate = await renderCaptchaWall({ captchaImageTag: currentCaptchaIpList[ip].data, captchaResolutionFormUrl: '', error });
122 res.status(401);
123 res.send(captchaWallTemplate);
124 };
125
126 // Generate a CAPTCHA
127 function generateCaptcha(ip, captchaGenerationCacheDuration) {
128 const captcha = svgCaptcha.create();
129 currentCaptchaIpList[ip] = {
130 data: captcha.data,
131 text: captcha.text,
132 resolved: false,
133 };
134 setTimeout(() => {
135 if (currentCaptchaIpList[ip]) { delete currentCaptchaIpList[ip]; }
136 }, captchaGenerationCacheDuration);
137 logger.debug({ type: "GENERATE_CAPTCHA", ip });
138 };
139
140 return obj;
141}