TNOCS/node-auth

View on GitHub
src/lib/routes/verify.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { BAD_REQUEST, INTERNAL_SERVER_ERROR, OK } from 'http-status-codes';
import * as bcrypt from 'bcrypt';
import { Request, Response } from 'express';
import { User, IUser } from '../models/user';
import { INodeAuthOptions } from '../models/options';
import { SendMailOptions, Transport, SentMessageInfo } from 'nodemailer';

const error = console.error;
const urlRegex = /\$\{URL\}/g; // regex to replace ${URL} in templates

let verificationURL: string;
let mailService: Transport;
let verifyMailOptions: SendMailOptions;
let confirmMailOptions: SendMailOptions;
let verificationMessageSendCallback: (err: Error, info: SentMessageInfo) => void;
let confirmationMessageSendCallback: (err: Error, info: SentMessageInfo) => void;

/**
 * Initialize the user route, e.g. by setting up the onUserChanged event handler.
 *
 * @export
 * @param {INodeAuthOptions} options
 */
export function init(options: INodeAuthOptions) {
  if (!options.verify) { return; }
  verificationURL = options.verify.baseUrl;
  mailService = options.verify.mailService;
  verifyMailOptions = options.verify.verifyMailOptions;
  confirmMailOptions = options.verify.confirmMailOptions;
  verificationMessageSendCallback = options.verify.verificationMessageSendCallback;
  confirmationMessageSendCallback = options.verify.confirmationMessageSendCallback;
}

function sendConfirmationEmail(user: IUser) {
  if (!confirmMailOptions) { return; }
  const mailOptions = JSON.parse(JSON.stringify(confirmMailOptions)); // clone
  mailOptions.to = user.email;
  mailService && mailService.send(mailOptions, confirmationMessageSendCallback);
}

/**
 * Verify the user's email address, e.g. /api/users/:id?t=jwt
 * where jwt is a token
 *
 * @export
 * @param {Request} req
 * @param {Response} res
 */
export function verifyEmail(req: Request, res: Response) {
  const id = req.params['id'];
  const token = req.query['t'];
  if (!id || !token) {
    res.status(BAD_REQUEST).json({ success: false, message: 'Please create a valid request!' });
    return;
  }
  User.findById(id, (err, user) => {
    if (err || !user) {
      res.status(BAD_REQUEST).json({ success: false, message: 'Please create a valid request!' });
      return;
    }
    bcrypt.compare(user.email, token)
      .then(ok => {
        if (!ok) {
          res.status(BAD_REQUEST).json({ success: false, message: 'Please create a valid request!' });
          return;
        }
        user.update({ verified: true }, (err, result) => {
          if (err) {
            error(err);
            res.status(INTERNAL_SERVER_ERROR).json({ success: false, message: 'Something did not work as expected. Please come back later and try again.' });
            return;
          }
          res.status(OK).json({ success: true, message: 'Your email was verified successfully. Thank you!' });
          sendConfirmationEmail(user);
        });
      })
      .catch(err => {
        // error(err);
        res.status(BAD_REQUEST).json({ success: false, message: 'Please create a valid request!' });
      });
  });
}

/**
 * Send a verification message by encrypting the email and adding it as token.
 *
 * @export
 * @param {IUser} user
 * @param {(err: Error, info: nodemailer.SentMessageInfo) => void} [callback]
 */
export function sendVerificationMessage(user: IUser) {
  if (!verifyMailOptions) { return; }
  bcrypt.hash(user.email, 10, (err, hash) => {
    if (err) {
      error(err);
      return;
    }
    // inject newly-created URL into the email's body and FIRE
    const URL = `${verificationURL}/${user._id.toString()}?t=${hash}`; // e.g. /api/activate/1234?t=5678
    const mailOptions = JSON.parse(JSON.stringify(verifyMailOptions)); // clone

    mailOptions.to = user.email;
    if (mailOptions.html) { mailOptions.html = mailOptions.html.replace(urlRegex, URL); }
    if (mailOptions.text) { mailOptions.text = mailOptions.text.replace(urlRegex, URL); }

    mailService && mailService.send(mailOptions, verificationMessageSendCallback);
  });
}

/**
 * Resend verification email.
 * GET /api/activate?email=[EMAIL]
 *
 * @export
 * @param {Request} req
 * @param {Response} res
 * @returns
 */
export function resendEmail(req: Request, res: Response) {
  const email = req.query['email'];
  if (!email) {
    res.status(BAD_REQUEST).json({ success: false, message: 'Please send your email to activate your account.' });
    return;
  }
  User.findOne( { email: email.toLowerCase()}, (err, user) => {
    if (err || !user) {
      res.status(BAD_REQUEST).json({ success: false, message: 'Please signup first.' });
      return;
    }
    if (user.verified) {
      res.status(BAD_REQUEST).json({ success: false, message: 'User is already verified.' });
      return;
    }
    sendVerificationMessage(user);
    res.status(OK).json({ success: true, message: 'Verification email sent.' });
  });
}