authController.ts 7.3 KB

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