| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- import { RequestHandler } from 'express';
- import fs from 'fs';
- import path from 'path';
- import passport from 'passport';
- import JWT from 'jsonwebtoken';
- import axios from 'axios';
- import { isAdmin } from '../utils';
- import transporter from '../mail/mail';
- import { resetMailText, verifyMailText } from '../mail/text';
- import {
- createUser,
- changePassword,
- generateApiKey,
- getUser,
- verifyUser,
- requestPasswordReset,
- resetPassword,
- } from '../db/user';
- import { IUser } from '../models/user';
- /* Read email template */
- const resetEmailTemplatePath = path.join(
- __dirname,
- '../mail/template-reset.html'
- );
- const verifyEmailTemplatePath = path.join(
- __dirname,
- '../mail/template-verify.html'
- );
- const resetEmailTemplate = fs
- .readFileSync(resetEmailTemplatePath, { encoding: 'utf-8' })
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN);
- const verifyEmailTemplate = fs
- .readFileSync(verifyEmailTemplatePath, { encoding: 'utf-8' })
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN);
- /* Function to generate JWT */
- const signToken = (user: IUser) =>
- JWT.sign(
- {
- iss: 'ApiAuth',
- sub: () => user.email,
- domain: (user.domain && user.domain.name) || '',
- admin: isAdmin(user.email),
- iat: new Date().getTime(),
- exp: new Date().setDate(new Date().getDate() + 7),
- },
- process.env.JWT_SECRET
- );
- /* Passport.js authentication controller */
- const authenticate = (
- type: 'jwt' | 'local' | 'localapikey',
- error: string,
- isStrict: boolean = true
- ) =>
- function auth(req, res, next) {
- if (req.user) return next();
- return passport.authenticate(type, (err, user) => {
- if (err) return res.status(400);
- if (!user && isStrict) return res.status(401).json({ error });
- if (user && isStrict && !user.verified) {
- return res.status(400).json({
- error:
- 'Your email address is not verified.' +
- 'Click on signup to get the verification link again.',
- });
- }
- if (user && user.banned) {
- return res
- .status(400)
- .json({ error: 'Your are banned from using this website.' });
- }
- if (user) {
- req.user = {
- ...user,
- admin: isAdmin(user.email),
- };
- return next();
- }
- return next();
- })(req, res, next);
- };
- export const authLocal = authenticate(
- 'local',
- 'Login email and/or password are wrong.'
- );
- export const authJwt = authenticate('jwt', 'Unauthorized.');
- export const authJwtLoose = authenticate('jwt', 'Unauthorized.', false);
- export const authApikey = authenticate(
- 'localapikey',
- 'API key is not correct.',
- false
- );
- /* reCaptcha controller */
- export const recaptcha: RequestHandler = async (req, res, next) => {
- if (process.env.NODE_ENV === 'production' && !req.user) {
- const isReCaptchaValid = await axios({
- method: 'post',
- url: 'https://www.google.com/recaptcha/api/siteverify',
- headers: {
- 'Content-type': 'application/x-www-form-urlencoded',
- },
- params: {
- secret: process.env.RECAPTCHA_SECRET_KEY,
- response: req.body.reCaptchaToken,
- remoteip: req.realIP,
- },
- });
- if (!isReCaptchaValid.data.success) {
- return res
- .status(401)
- .json({ error: 'reCAPTCHA is not valid. Try again.' });
- }
- }
- return next();
- };
- export const authAdmin: RequestHandler = async (req, res, next) => {
- if (!req.user.admin) {
- return res.status(401).json({ error: 'Unauthorized.' });
- }
- return next();
- };
- export const signup: RequestHandler = async (req, res) => {
- const { email, password } = req.body;
- if (password.length > 64) {
- return res.status(400).json({ error: 'Maximum password length is 64.' });
- }
- if (email.length > 64) {
- return res.status(400).json({ error: 'Maximum email length is 64.' });
- }
- const user = await getUser(email);
- if (user && user.verified)
- return res.status(403).json({ error: 'Email is already in use.' });
- const newUser = await createUser(email, password);
- const mail = await transporter.sendMail({
- from: process.env.MAIL_FROM || process.env.MAIL_USER,
- to: newUser.email,
- subject: 'Verify your account',
- text: verifyMailText.replace(
- /{{verification}}/gim,
- newUser.verificationToken
- ),
- html: verifyEmailTemplate.replace(
- /{{verification}}/gim,
- newUser.verificationToken
- ),
- });
- if (mail.accepted.length) {
- return res
- .status(201)
- .json({ email, message: 'Verification email has been sent.' });
- }
- return res
- .status(400)
- .json({ error: "Couldn't send verification email. Try again." });
- };
- export const login: RequestHandler = (req, res) => {
- const token = signToken(req.user);
- return res.status(200).json({ token });
- };
- export const renew: RequestHandler = (req, res) => {
- const token = signToken(req.user);
- return res.status(200).json({ token });
- };
- export const verify: RequestHandler = async (req, _res, next) => {
- const user = await verifyUser(req.params.verificationToken);
- if (user) {
- const token = signToken(user);
- req.user = { token };
- }
- return next();
- };
- export const changeUserPassword: RequestHandler = async (req, res) => {
- if (req.body.password.length < 8) {
- return res
- .status(400)
- .json({ error: 'Password must be at least 8 chars long.' });
- }
- if (req.body.password.length > 64) {
- return res.status(400).json({ error: 'Maximum password length is 64.' });
- }
- const changedUser = await changePassword(req.user._id, req.body.password);
- if (changedUser) {
- return res
- .status(200)
- .json({ message: 'Your password has been changed successfully.' });
- }
- return res
- .status(400)
- .json({ error: "Couldn't change the password. Try again later" });
- };
- export const generateUserApiKey: RequestHandler = async (req, res) => {
- const user = await generateApiKey(req.user._id);
- if (user.apikey) {
- return res.status(201).json({ apikey: user.apikey });
- }
- return res
- .status(400)
- .json({ error: 'Sorry, an error occured. Please try again later.' });
- };
- export const userSettings: RequestHandler = (req, res) =>
- res.status(200).json({
- apikey: req.user.apikey || '',
- customDomain: req.user.domain || '',
- homepage: req.user.homepage || '',
- });
- export const requestUserPasswordReset: RequestHandler = async (req, res) => {
- const user = await requestPasswordReset(req.body.email);
- if (!user) {
- return res.status(400).json({ error: "Couldn't reset password." });
- }
- const mail = await transporter.sendMail({
- from: process.env.MAIL_USER,
- to: user.email,
- subject: 'Reset your password',
- text: resetMailText
- .replace(/{{resetpassword}}/gm, user.resetPasswordToken)
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN),
- html: resetEmailTemplate
- .replace(/{{resetpassword}}/gm, user.resetPasswordToken)
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN),
- });
- if (mail.accepted.length) {
- return res.status(200).json({
- email: user.email,
- message: 'Reset password email has been sent.',
- });
- }
- return res.status(400).json({ error: "Couldn't reset password." });
- };
- export const resetUserPassword: RequestHandler = async (req, _res, next) => {
- const user = await resetPassword(req.params.resetPasswordToken);
- if (user) {
- const token = signToken(user);
- req.user = { token };
- }
- return next();
- };
|