validateBodyController.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. const { promisify } = require('util');
  2. const dns = require('dns');
  3. const axios = require('axios');
  4. const urlRegex = require('url-regex');
  5. const validator = require('express-validator/check');
  6. const { subHours } = require('date-fns/');
  7. const { validationResult } = require('express-validator/check');
  8. const { addCooldown, banUser } = require('../db/user');
  9. const { getBannedDomain, getBannedHost, urlCountFromDate } = require('../db/url');
  10. const config = require('../config');
  11. const subDay = require('date-fns/sub_days');
  12. const dnsLookup = promisify(dns.lookup);
  13. exports.validationCriterias = [
  14. validator
  15. .body('email')
  16. .exists()
  17. .withMessage('Email must be provided.')
  18. .isEmail()
  19. .withMessage('Email is not valid.')
  20. .trim()
  21. .normalizeEmail(),
  22. validator
  23. .body('password', 'Password must be at least 8 chars long.')
  24. .exists()
  25. .withMessage('Password must be provided.')
  26. .isLength({ min: 8 }),
  27. ];
  28. exports.validateBody = (req, res, next) => {
  29. const errors = validationResult(req);
  30. if (!errors.isEmpty()) {
  31. const errorsObj = errors.mapped();
  32. const emailError = errorsObj.email && errorsObj.email.msg;
  33. const passwordError = errorsObj.password && errorsObj.password.msg;
  34. return res.status(400).json({ error: emailError || passwordError });
  35. }
  36. return next();
  37. };
  38. const preservedUrls = [
  39. 'login',
  40. 'logout',
  41. 'signup',
  42. 'reset-password',
  43. 'resetpassword',
  44. 'url-password',
  45. 'url-info',
  46. 'settings',
  47. 'stats',
  48. 'verify',
  49. 'api',
  50. '404',
  51. 'static',
  52. 'images',
  53. 'banned',
  54. 'terms',
  55. 'privacy',
  56. 'report',
  57. ];
  58. exports.preservedUrls = preservedUrls;
  59. exports.validateUrl = async ({ body, user }, res, next) => {
  60. // Validate URL existence
  61. if (!body.target) return res.status(400).json({ error: 'No target has been provided.' });
  62. // validate URL length
  63. if (body.target.length > 3000) {
  64. return res.status(400).json({ error: 'Maximum URL length is 3000.' });
  65. }
  66. // Validate URL
  67. const isValidUrl = urlRegex({ exact: true, strict: false }).test(body.target);
  68. if (!isValidUrl) return res.status(400).json({ error: 'URL is not valid.' });
  69. // Validate password length
  70. if (body.password && body.password.length > 64) {
  71. return res.status(400).json({ error: 'Maximum password length is 64.' });
  72. }
  73. // Custom URL validations
  74. if (user && body.customurl) {
  75. // Validate custom URL
  76. if (!/^[a-zA-Z0-9-_]+$/g.test(body.customurl.trim())) {
  77. return res.status(400).json({ error: 'Custom URL is not valid.' });
  78. }
  79. // Prevent from using preserved URLs
  80. if (preservedUrls.some(url => url === body.customurl)) {
  81. return res.status(400).json({ error: "You can't use this custom URL name." });
  82. }
  83. // Validate custom URL length
  84. if (body.customurl.length > 64) {
  85. return res.status(400).json({ error: 'Maximum custom URL length is 64.' });
  86. }
  87. }
  88. return next();
  89. };
  90. exports.cooldownCheck = async user => {
  91. if (user && user.cooldowns) {
  92. if (user.cooldowns.length > 4) {
  93. await banUser(user);
  94. throw new Error('Too much malware requests. You are now banned.');
  95. }
  96. const hasCooldownNow = user.cooldowns.some(
  97. cooldown => cooldown > subHours(new Date(), 12).toJSON()
  98. );
  99. if (hasCooldownNow) {
  100. throw new Error('Cooldown because of a malware URL. Wait 12h');
  101. }
  102. }
  103. };
  104. exports.malwareCheck = async (user, target) => {
  105. const isMalware = await axios.post(
  106. `https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${
  107. config.GOOGLE_SAFE_BROWSING_KEY
  108. }`,
  109. {
  110. client: {
  111. clientId: config.DEFAULT_DOMAIN.toLowerCase().replace('.', ''),
  112. clientVersion: '1.0.0',
  113. },
  114. threatInfo: {
  115. threatTypes: [
  116. 'THREAT_TYPE_UNSPECIFIED',
  117. 'MALWARE',
  118. 'SOCIAL_ENGINEERING',
  119. 'UNWANTED_SOFTWARE',
  120. 'POTENTIALLY_HARMFUL_APPLICATION',
  121. ],
  122. platformTypes: ['ANY_PLATFORM', 'PLATFORM_TYPE_UNSPECIFIED'],
  123. threatEntryTypes: ['EXECUTABLE', 'URL', 'THREAT_ENTRY_TYPE_UNSPECIFIED'],
  124. threatEntries: [{ url: target }],
  125. },
  126. }
  127. );
  128. if (isMalware.data && isMalware.data.matches) {
  129. if (user) {
  130. await addCooldown(user);
  131. }
  132. throw new Error(user ? 'Malware detected! Cooldown for 12h.' : 'Malware detected!');
  133. }
  134. };
  135. exports.urlCountsCheck = async email => {
  136. const { count } = await urlCountFromDate({
  137. email,
  138. date: subDay(new Date(), 1).toJSON(),
  139. });
  140. if (count > config.USER_LIMIT_PER_DAY) {
  141. throw new Error(
  142. `You have reached your daily limit (${config.USER_LIMIT_PER_DAY}). Please wait 24h.`
  143. );
  144. }
  145. };
  146. exports.checkBannedDomain = async domain => {
  147. const isDomainBanned = await getBannedDomain(domain);
  148. if (isDomainBanned) {
  149. throw new Error('URL is containing malware/scam.');
  150. }
  151. };
  152. exports.checkBannedHost = async domain => {
  153. let isHostBanned;
  154. try {
  155. const dnsRes = await dnsLookup(domain);
  156. isHostBanned = await getBannedHost(dnsRes && dnsRes.address);
  157. } catch (error) {
  158. isHostBanned = null;
  159. }
  160. if (isHostBanned) {
  161. throw new Error('URL is containing malware/scam.');
  162. }
  163. };