urlController.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. const urlRegex = require('url-regex');
  2. const URL = require('url');
  3. const dns = require('dns');
  4. const { promisify } = require('util');
  5. const generate = require('nanoid/generate');
  6. const useragent = require('useragent');
  7. const geoip = require('geoip-lite');
  8. const bcrypt = require('bcryptjs');
  9. const subDay = require('date-fns/sub_days');
  10. const {
  11. createShortUrl,
  12. createVisit,
  13. deleteCustomDomain,
  14. deleteUrl,
  15. findUrl,
  16. getCountUrls,
  17. getCustomDomain,
  18. getStats,
  19. getUrls,
  20. setCustomDomain,
  21. urlCountFromDate,
  22. banUrl,
  23. getBannedDomain,
  24. getBannedHost,
  25. } = require('../db/url');
  26. const dnsLookup = promisify(dns.lookup);
  27. const { addProtocol, generateShortUrl } = require('../utils');
  28. const config = require('../config');
  29. const generateId = async () => {
  30. const id = generate('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', 6);
  31. const urls = await findUrl({ id });
  32. if (!urls.length) return id;
  33. return generateId();
  34. };
  35. exports.urlShortener = async ({ body, user }, res) => {
  36. // Check if user has passed daily limit
  37. if (user) {
  38. const { count } = await urlCountFromDate({
  39. email: user.email,
  40. date: subDay(new Date(), 1).toJSON(),
  41. });
  42. if (count > config.USER_LIMIT_PER_DAY) {
  43. return res.status(429).json({
  44. error: `You have reached your daily limit (${config.USER_LIMIT_PER_DAY}). Please wait 24h.`,
  45. });
  46. }
  47. }
  48. // if "reuse" is true, try to return
  49. // the existent URL without creating one
  50. if (user && body.reuse) {
  51. const urls = await findUrl({ target: addProtocol(body.target) });
  52. if (urls.length) {
  53. urls.sort((a, b) => a.createdAt > b.createdAt);
  54. const { domain: d, user: u, ...url } = urls[urls.length - 1];
  55. const data = {
  56. ...url,
  57. password: !!url.password,
  58. reuse: true,
  59. shortUrl: generateShortUrl(url.id, user.domain),
  60. };
  61. return res.json(data);
  62. }
  63. }
  64. // Check if custom URL already exists
  65. if (user && body.customurl) {
  66. const urls = await findUrl({ id: body.customurl || '' });
  67. if (urls.length) {
  68. const urlWithNoDomain = !user.domain && urls.some(url => !url.domain);
  69. const urlWithDmoain = user.domain && urls.some(url => url.domain === user.domain);
  70. if (urlWithNoDomain || urlWithDmoain) {
  71. return res.status(400).json({ error: 'Custom URL is already in use.' });
  72. }
  73. }
  74. }
  75. // If domain or host is banned
  76. const domain = URL.parse(body.target).hostname;
  77. const isDomainBanned = await getBannedDomain(domain);
  78. let isHostBanned;
  79. try {
  80. const dnsRes = await dnsLookup(domain);
  81. isHostBanned = await getBannedHost(dnsRes && dnsRes.address);
  82. } catch (error) {
  83. isHostBanned = null;
  84. }
  85. if (isDomainBanned || isHostBanned) {
  86. return res.status(400).json({ error: 'URL is containing malware/scam.' });
  87. }
  88. // Create new URL
  89. const id = (user && body.customurl) || (await generateId());
  90. const target = addProtocol(body.target);
  91. const url = await createShortUrl({ ...body, id, target, user });
  92. return res.json(url);
  93. };
  94. const browsersList = ['IE', 'Firefox', 'Chrome', 'Opera', 'Safari', 'Edge'];
  95. const osList = ['Windows', 'Mac Os X', 'Linux', 'Chrome OS', 'Android', 'iOS'];
  96. const botList = ['bot', 'dataminr', 'pinterest', 'yahoo', 'facebook', 'crawl'];
  97. const filterInBrowser = agent => item =>
  98. agent.family.toLowerCase().includes(item.toLocaleLowerCase());
  99. const filterInOs = agent => item =>
  100. agent.os.family.toLowerCase().includes(item.toLocaleLowerCase());
  101. exports.goToUrl = async (req, res, next) => {
  102. const { host } = req.headers;
  103. const reqestedId = req.params.id || req.body.id;
  104. const id = reqestedId.replace('+', '');
  105. const domain = host !== config.DEFAULT_DOMAIN && host;
  106. const agent = useragent.parse(req.headers['user-agent']);
  107. const [browser = 'Other'] = browsersList.filter(filterInBrowser(agent));
  108. const [os = 'Other'] = osList.filter(filterInOs(agent));
  109. const referrer = req.header('Referer') && URL.parse(req.header('Referer')).hostname;
  110. const location = geoip.lookup(req.realIp);
  111. const country = location && location.country;
  112. const urls = await findUrl({ id, domain });
  113. const isBot =
  114. botList.some(bot => agent.source.toLowerCase().includes(bot)) || agent.family === 'Other';
  115. if (!urls && !urls.length) return next();
  116. const url = urls.find(item => (domain ? item.domain === domain : !item.domain));
  117. if (!url) return next();
  118. if (url.banned) {
  119. return res.redirect('/banned');
  120. }
  121. const doesRequestInfo = /.*\+$/gi.test(reqestedId);
  122. if (doesRequestInfo && !url.password) {
  123. req.urlTarget = url.target;
  124. req.pageType = 'info';
  125. return next();
  126. }
  127. if (url.password && !req.body.password) {
  128. req.protectedUrl = id;
  129. req.pageType = 'password';
  130. return next();
  131. }
  132. if (url.password) {
  133. const isMatch = await bcrypt.compare(req.body.password, url.password);
  134. if (!isMatch) {
  135. return res.status(401).json({ error: 'Password is not correct' });
  136. }
  137. if (url.user && !isBot) {
  138. await createVisit({
  139. browser,
  140. country: country || 'Unknown',
  141. domain,
  142. id: url.id,
  143. os,
  144. referrer: referrer || 'Direct',
  145. });
  146. }
  147. return res.status(200).json({ target: url.target });
  148. }
  149. if (url.user && !isBot) {
  150. await createVisit({
  151. browser,
  152. country: country || 'Unknown',
  153. domain,
  154. id: url.id,
  155. os,
  156. referrer: referrer || 'Direct',
  157. });
  158. }
  159. return res.redirect(url.target);
  160. };
  161. exports.getUrls = async ({ query, user }, res) => {
  162. const { countAll } = await getCountUrls({ user });
  163. const urlsList = await getUrls({ options: query, user });
  164. return res.json({ ...urlsList, countAll });
  165. };
  166. exports.setCustomDomain = async ({ body: { customDomain }, user }, res) => {
  167. if (customDomain.length > 40) {
  168. return res.status(400).json({ error: 'Maximum custom domain length is 40.' });
  169. }
  170. if (customDomain === config.DEFAULT_DOMAIN) {
  171. return res.status(400).json({ error: "You can't use default domain." });
  172. }
  173. const isValidDomain = urlRegex({ exact: true, strict: false }).test(customDomain);
  174. if (!isValidDomain) return res.status(400).json({ error: 'Domain is not valid.' });
  175. const isOwned = await getCustomDomain({ customDomain });
  176. if (isOwned && isOwned.email !== user.email) {
  177. return res
  178. .status(400)
  179. .json({ error: 'Domain is already taken. Contact us for multiple users.' });
  180. }
  181. const userCustomDomain = await setCustomDomain({ user, customDomain });
  182. if (userCustomDomain) return res.status(201).json({ customDomain: userCustomDomain.name });
  183. return res.status(400).json({ error: "Couldn't set custom domain." });
  184. };
  185. exports.deleteCustomDomain = async ({ user }, res) => {
  186. const response = await deleteCustomDomain({ user });
  187. if (response) return res.status(200).json({ message: 'Domain deleted successfully' });
  188. return res.status(400).json({ error: "Couldn't delete custom domain." });
  189. };
  190. exports.deleteUrl = async ({ body: { id, domain }, user }, res) => {
  191. if (!id) return res.status(400).json({ error: 'No id has been provided.' });
  192. const customDomain = domain !== config.DEFAULT_DOMAIN && domain;
  193. const urls = await findUrl({ id, domain: customDomain });
  194. if (!urls && !urls.length) return res.status(400).json({ error: "Couldn't find the short URL." });
  195. const response = await deleteUrl({ id, domain: customDomain, user });
  196. if (response) return res.status(200).json({ message: 'Sort URL deleted successfully' });
  197. return res.status(400).json({ error: "Couldn't delete short URL." });
  198. };
  199. exports.getStats = async ({ query: { id, domain }, user }, res) => {
  200. if (!id) return res.status(400).json({ error: 'No id has been provided.' });
  201. const customDomain = domain !== config.DEFAULT_DOMAIN && domain;
  202. const stats = await getStats({ id, domain: customDomain, user });
  203. if (!stats) return res.status(400).json({ error: 'Could not get the short URL stats.' });
  204. return res.status(200).json(stats);
  205. };
  206. exports.ban = async ({ body }, res) => {
  207. if (!body.id) return res.status(400).json({ error: 'No id has been provided.' });
  208. const urls = await findUrl({ id: body.id });
  209. const [url] = urls.filter(item => !item.domain);
  210. if (!url) return res.status(400).json({ error: "Couldn't find the URL." });
  211. if (url.banned) return res.status(200).json({ message: 'URL was banned already' });
  212. const domain = URL.parse(url.target).hostname;
  213. let host;
  214. if (body.host) {
  215. try {
  216. const dnsRes = await dnsLookup(domain);
  217. host = dnsRes && dnsRes.address;
  218. } catch (error) {
  219. host = null;
  220. }
  221. }
  222. await banUrl({
  223. domain: body.domain && domain,
  224. host,
  225. id: body.id,
  226. user: body.user,
  227. });
  228. return res.status(200).json({ message: 'URL has been banned successfully' });
  229. };