authController.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. const fs = require('fs');
  2. const path = require('path');
  3. const passport = require('passport');
  4. const JWT = require('jsonwebtoken');
  5. const axios = require('axios');
  6. const { isAdmin } = require('../utils');
  7. const transporter = require('../mail/mail');
  8. const { resetMailText, verifyMailText } = require('../mail/text');
  9. const {
  10. createUser,
  11. changePassword,
  12. generateApiKey,
  13. getUser,
  14. verifyUser,
  15. requestPasswordReset,
  16. resetPassword,
  17. } = require('../db/user');
  18. /* Read email template */
  19. const resetEmailTemplatePath = path.join(__dirname, '../mail/template-reset.html');
  20. const verifyEmailTemplatePath = path.join(__dirname, '../mail/template-verify.html');
  21. const resetEmailTemplate = fs
  22. .readFileSync(resetEmailTemplatePath, { encoding: 'utf-8' })
  23. .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN);
  24. const verifyEmailTemplate = fs
  25. .readFileSync(verifyEmailTemplatePath, { encoding: 'utf-8' })
  26. .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN);
  27. /* Function to generate JWT */
  28. const signToken = user =>
  29. JWT.sign(
  30. {
  31. iss: 'ApiAuth',
  32. sub: user.email,
  33. domain: user.domain || '',
  34. admin: isAdmin(user.email),
  35. iat: new Date().getTime(),
  36. exp: new Date().setDate(new Date().getDate() + 7),
  37. },
  38. process.env.JWT_SECRET
  39. );
  40. /* Passport.js authentication controller */
  41. const authenticate = (type, error, isStrict = true) =>
  42. function auth(req, res, next) {
  43. if (req.user) return next();
  44. return passport.authenticate(type, (err, user) => {
  45. if (err) return res.status(400);
  46. if (!user && isStrict) return res.status(401).json({ error });
  47. if (user && isStrict && !user.verified) {
  48. return res.status(400).json({
  49. error:
  50. 'Your email address is not verified.' +
  51. 'Click on signup to get the verification link again.',
  52. });
  53. }
  54. if (user && user.banned) {
  55. return res.status(400).json({ error: 'Your are banned from using this website.' });
  56. }
  57. if (user) {
  58. req.user = {
  59. ...user,
  60. admin: isAdmin(user.email),
  61. };
  62. return next();
  63. }
  64. return next();
  65. })(req, res, next);
  66. };
  67. exports.authLocal = authenticate('local', 'Login email and/or password are wrong.');
  68. exports.authJwt = authenticate('jwt', 'Unauthorized.');
  69. exports.authJwtLoose = authenticate('jwt', 'Unauthorized.', false);
  70. exports.authApikey = authenticate('localapikey', 'API key is not correct.', false);
  71. /* reCaptcha controller */
  72. exports.recaptcha = async (req, res, next) => {
  73. if (!req.user) {
  74. const isReCaptchaValid = await axios({
  75. method: 'post',
  76. url: 'https://www.google.com/recaptcha/api/siteverify',
  77. headers: {
  78. 'Content-type': 'application/x-www-form-urlencoded',
  79. },
  80. params: {
  81. secret: process.env.RECAPTCHA_SECRET_KEY,
  82. response: req.body.reCaptchaToken,
  83. remoteip: req.realIp,
  84. },
  85. });
  86. if (!isReCaptchaValid.data.success) {
  87. return res.status(401).json({ error: 'reCAPTCHA is not valid. Try again.' });
  88. }
  89. }
  90. return next();
  91. };
  92. exports.authAdmin = async (req, res, next) => {
  93. if (!req.user.admin) {
  94. return res.status(401).json({ error: 'Unauthorized.' });
  95. }
  96. return next();
  97. };
  98. exports.signup = async (req, res) => {
  99. const { email, password } = req.body;
  100. if (password.length > 64) {
  101. return res.status(400).json({ error: 'Maximum password length is 64.' });
  102. }
  103. if (email.length > 64) {
  104. return res.status(400).json({ error: 'Maximum email length is 64.' });
  105. }
  106. const user = await getUser({ email });
  107. if (user && user.verified) return res.status(403).json({ error: 'Email is already in use.' });
  108. const newUser = await createUser({ email, password });
  109. const mail = await transporter.sendMail({
  110. from: process.env.MAIL_FROM || process.env.MAIL_USER,
  111. to: newUser.email,
  112. subject: 'Verify your account',
  113. text: verifyMailText.replace(/{{verification}}/gim, newUser.verificationToken),
  114. html: verifyEmailTemplate.replace(/{{verification}}/gim, newUser.verificationToken),
  115. });
  116. if (mail.accepted.length) {
  117. return res.status(201).json({ email, message: 'Verification email has been sent.' });
  118. }
  119. return res.status(400).json({ error: "Couldn't send verification email. Try again." });
  120. };
  121. exports.login = ({ user }, res) => {
  122. const token = signToken(user);
  123. return res.status(200).json({ token });
  124. };
  125. exports.renew = ({ user }, res) => {
  126. const token = signToken(user);
  127. return res.status(200).json({ token });
  128. };
  129. exports.verify = async (req, res, next) => {
  130. const { verificationToken = '' } = req.params;
  131. const user = await verifyUser({ verificationToken });
  132. if (user) {
  133. const token = signToken(user);
  134. req.user = { token };
  135. }
  136. return next();
  137. };
  138. exports.changePassword = async ({ body: { password }, user }, res) => {
  139. if (password.length < 8) {
  140. return res.status(400).json({ error: 'Password must be at least 8 chars long.' });
  141. }
  142. if (password.length > 64) {
  143. return res.status(400).json({ error: 'Maximum password length is 64.' });
  144. }
  145. const changedUser = await changePassword({ email: user.email, password });
  146. if (changedUser) {
  147. return res.status(200).json({ message: 'Your password has been changed successfully.' });
  148. }
  149. return res.status(400).json({ error: "Couldn't change the password. Try again later" });
  150. };
  151. exports.generateApiKey = async ({ user }, res) => {
  152. const { apikey } = await generateApiKey({ email: user.email });
  153. if (apikey) {
  154. return res.status(201).json({ apikey });
  155. }
  156. return res.status(400).json({ error: 'Sorry, an error occured. Please try again later.' });
  157. };
  158. exports.userSettings = ({ user }, res) =>
  159. res.status(200).json({
  160. apikey: user.apikey || '',
  161. customDomain: user.domain || '',
  162. homepage: user.homepage || '',
  163. useHttps: user.useHttps || false,
  164. });
  165. exports.requestPasswordReset = async ({ body: { email } }, res) => {
  166. const user = await requestPasswordReset({ email });
  167. if (!user) {
  168. return res.status(400).json({ error: "Couldn't reset password." });
  169. }
  170. const mail = await transporter.sendMail({
  171. from: process.env.MAIL_USER,
  172. to: user.email,
  173. subject: 'Reset your password',
  174. text: resetMailText
  175. .replace(/{{resetpassword}}/gm, user.resetPasswordToken)
  176. .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN),
  177. html: resetEmailTemplate
  178. .replace(/{{resetpassword}}/gm, user.resetPasswordToken)
  179. .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN),
  180. });
  181. if (mail.accepted.length) {
  182. return res.status(200).json({ email, message: 'Reset password email has been sent.' });
  183. }
  184. return res.status(400).json({ error: "Couldn't reset password." });
  185. };
  186. exports.resetPassword = async (req, res, next) => {
  187. const { resetPasswordToken = '' } = req.params;
  188. const user = await resetPassword({ resetPasswordToken });
  189. if (user) {
  190. const token = signToken(user);
  191. req.user = { token };
  192. }
  193. return next();
  194. };