Hapi Js, Mongoose, MongoDB

In Previous post we talked about how to CRUD node.js application using Hapi.js framework, Mongodb DB and Angular.js https://www.cronj.com/blog/hapi-mongoose/

It’s highly recommended to look into previous post to understand this in better way.

Here we will learn how to implement token based user authentication.

Repo Link: https://github.com/Cron-J/JWT-Hapi-Mongoose-Mongodb-with-email-verification-and-forgot-password

User Story

    1. Register new user. Registration requires a valid user email address which will be his user name and password.
    2. One email address can be registered only once.
    3. After registration a verification link is send to user email address.
    4. Verify your account by clicking on link send to your email address and on successful verification you will be redirected to login page.
    5. If you are not verified your account after creation, you will not be allowed to land to dashboard after success login.
    6. You can only go to dashboard if your account is verified.
    7. Will have the option to resend the password to registered email address in case you forget.
    8. Will also have the option to resend verification link to registered email address to verify account.

Mongoose Schema

var User = new Schema({
    // saves user email, validation of email address is done in paylod
    userName: {
        type: String,
        unique: true,
        required: true
    },
    // hashed password is saved
    password: {
        type: String,
        required: true
    },
    // here we defines the user role like admin, customer, etc..
    scope: {
        type: String,
        enum: ['Customer'],
        required: true
    },
    //it tells about the user account/email verification. By default it is false which is not verified and changes to true when account/email gets verified
    isVerified: {
        type: Boolean,
        default: false
    }
});

for complete model code visit repo : user.js

Creating and configuring server

var Hapi = require('hapi'), // includes module for hapi framework

    Routes = require('./routes'),//includes file which has all the routes for an application

    Db = require('./config/db'),//includes file which has connection to mongodb and mongoose

    Moment = require('moment'),//a lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates

    Config = require('./config/config'); // includes file which has app configuration details such as hostname, hostport, etc ..


var app = {};
app.config = Config;

var privateKey = app.config.key.privateKey;
var ttl = app.config.key.tokenExpiry;

//creates server
var server = new Hapi.Server();
server.connection({ port: app.config.server.port });

// Validate function to be injected 
var validate = function(token, callback) {
    // Check token timestamp
    var diff = Moment().diff(Moment(token.iat * 1000));
    if (diff > ttl) {
        return callback(null, false);
    }
    callback(null, true, token);
};

// Plugins, register hapi-auth-jwt to server
server.register([{
    register: require('hapi-auth-jwt')
}], function(err) {
    server.auth.strategy('token', 'jwt', {
        validateFunc: validate,
        key: privateKey
    });

    // add routes to server
    server.route(Routes.endpoints);
});

// start server.
server.start(function() {
    console.log('Server started ', server.info.uri);
});

to view complete server.js code visit repo : server.js

Database Connection

var Mongoose = require('mongoose'); 
var config = require('./config'); // includes file which has app configuration details such as hostname, hostport, etc ..

Mongoose.connect('mongodb://' + config.database.host + '/' + config.database.db);  
var db = Mongoose.connection;
db.on('error', console.error.bind(console, 'connection error'));  
db.once('open', function callback() {  
    console.log("Connection with database succeeded.");
});

exports.Mongoose = Mongoose;  
exports.db = db;  

to view complete database connection code visit repo : db.js

Routes

var User      = require('./controller/user'), // includes user controller
    Static    = require('./static'); // include file which loads client side code to browser when app runs

// API Server Endpoints
exports.endpoints = [
    { method: 'GET',  path: '/{somethingss*}', config: Static.get },
    { method: 'POST', path: '/user', config: User.create},
    { method: 'POST', path: '/login', config: User.login},
    { method: 'POST', path: '/verifyEmail', config: User.verifyEmail},
    { method: 'POST', path: '/forgotPassword', config: User.forgotPassword},
    { method: 'POST', path: '/resendVerificationEmail', config: User.resendVerificationEmail}
];

to view complete routes code visit repo : routes.js

Controller
This is a sample code to create user i.e. POST /user. To see complete code visit controller.js

var Joi = require('joi'), // validator for JavaScript objects.
    Boom = require('boom'), // provides a set of utilities for returning HTTP errors
    Common = require('./common'),// includes file which has set of common functions like send email, used in whole project
    Config = require('../config/config'), // includes file which has app configuration details such as privateKey, etc ..
    Jwt = require('jsonwebtoken'), // JsonWebToken implementation for node.js
    User = require('../model/user').User; // includes user mongoose schema 

var privateKey = Config.key.privateKey;

exports.create = {
    validate: {
        payload: {
            userName: Joi.string().email().required(), //ensures to be a valid email address and mandatory filled 
            password: Joi.string().required() //ensures to be mandatory filled
        }
    },
    handler: function(request, reply) {
        request.payload.password = Common.encrypt(request.payload.password); // encrypt password before saving.
        request.payload.scope = "Customer"; // set the user scope to Customer

        // saveUser is a method defined in model to save user
        User.saveUser(request.payload, function(err, user) {
            if (!err) {

                // prepare a data which is signed and send in token
                var tokenData = {
                    userName: user.userName,
                    scope: [user.scope],
                    id: user._id
                };

                // prepare a token for verification link which is send in email. send email method and link preparation method is written in common.js file common.js.
                Common.sentMailVerificationLink(user,Jwt.sign(tokenData, privateKey));
                reply("Please confirm your email id by clicking on link in email");
            } else {
                if (11000 === err.code || 11001 === err.code) {
                    reply(Boom.forbidden("please provide another user email"));
                } else reply(Boom.forbidden(err)); // HTTP 403
            }
        });
    }
};

To see complete code visit controller.js

API Available


Create User

POST: http://localhost:8000/user

{
"userName": "email@domain.com",
"password": "cronj"
}
An email is send to email@domain.com to verify account created. Email contains a verification link which on click will verify your account and redirect to login page.

Verification Link will look like http://127.0.0.1:8000/verifyEmail/token, where verifyEmail is the url for login page and token you get is used to verify account. Using token you get you need to hit the below api

Verify User

POST: http://localhost:8000/verifyEmail

Authorization Header: Bearer token

Resend Verification Email

POST: http://localhost:8000/resendVerificationEmail

{
"userName": "email@domain.com",
"password": "cronj"
}

User Login

POST: http://localhost:8000/login

{
"userName": "email@domain.com",
"password": "cronj"
}

Forgot Password

POST: http://localhost:8000/forgotPassword

{
"userName": "email@domain.com"
}

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.