TNOCS/node-auth

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

Summary

Maintainability
B
6 hrs
Test Coverage
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express-serve-static-core';
import { User, IUser, IUserModel } from '../models/user';
import { CRUD } from '../models/crud';
import { INodeAuthOptions } from '../models/options';
import { getToken } from './user';
import { UNAUTHORIZED, UNPROCESSABLE_ENTITY } from 'http-status-codes';

// export type CRUD = 'create' | 'update' | 'delete';
export type UserChangedEvent = (user: IUser, req: Request, change: CRUD) => IUser | void;

let expiresIn: string;
let secretKey: string;

/**
 * Initialize the user route, e.g. by setting up the onUserChanged event handler.
 *
 * @export
 * @param {INodeAuthOptions} options
 */
export function init(options: INodeAuthOptions) {
  secretKey = options.secretKey;
  expiresIn = options.expiresIn ? options.expiresIn : '1d';
}

export function login(req: Request, res: Response) {
  const email = req['body'].email;
  const pwd = req['body'].password;

  const token = getToken(req);
  if (token) {
    return renewToken(req, res, token);
  }
  if (!email || !pwd) {
    return res.status(UNPROCESSABLE_ENTITY).json({ success: false, message: 'Authentication failed. Body should contain an email and password property.' });
  }
  // find the user
  User.findOne({ email: email.toLowerCase() }, (err: Error, user: IUserModel) => {
    if (err || !user) {
      res.status(UNAUTHORIZED).json({ success: false, message: 'Authentication failed.' }); // User not found
    } else if (user) {
      // console.log(JSON.stringify(user, null, 2));
      // check if password matches
      user.comparePassword(pwd, (err, isMatch) => {
        if (isMatch && !err) {
          const json = <IUser>user.toJSON();
          delete json.password;
          // if user is found and password is right create a token
          const token = jwt.sign(json, secretKey, {
            expiresIn: expiresIn
          });
          // return the information including token as JSON
          res.json({ success: true, token: token, user: json });
        } else {
          res.status(UNAUTHORIZED).json({ success: false, msg: 'Authentication failed.' }); // Wrong password
        }
      });
    }
  });
};

/**
 * Renews an existing token if it is still valid.
 *
 * @param {Request} req
 * @param {Response} res
 * @param {string} token
 */
function renewToken(req: Request, res: Response, token: string) {
  // renew token?
  jwt.verify(token, secretKey, (err, user) => {
    if (err) {
      res.status(UNAUTHORIZED).json({ success: false, msg: 'Authentication failed.' }); // Wrong token
    } else {
      const jwtDecoded = <{ email: string }>jwt.decode(token);
      // find the user
      User.findOne({ email: jwtDecoded.email.toLowerCase() }, (err: Error, user: IUserModel) => {
        if (err || !user) {
          res.status(UNAUTHORIZED).json({ success: false, message: 'Authentication failed.' }); // User not found
        } else if (user) {
          const json = <IUser>user.toJSON();
          delete json.password;
          // if user is found and password is right create a token
          const token = jwt.sign(json, secretKey, {
            expiresIn: expiresIn
          });
          // return the information including token as JSON
          res.json({ success: true, token: token, user: json });
        } else {
          res.status(UNAUTHORIZED).json({ success: false, msg: 'Authentication failed.' }); // Wrong password
        }
      });
    }
  });
};