authController.js 6.8 KB

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