crowdsec.js 7.3 KB

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