├── .bowerrc
├── client
├── modules
│ └── app
│ │ ├── views
│ │ ├── app.client.login.html
│ │ ├── app.client.reset.html
│ │ └── app.client.home.html
│ │ ├── app.client.module.js
│ │ ├── services
│ │ └── app.client.services.js
│ │ ├── config
│ │ └── app.client.routes.js
│ │ └── controllers
│ │ └── app.client.controller.js
├── config.js
├── application.js
└── index.html
├── .gitignore
├── server
├── config
│ ├── db.js
│ ├── config.js
│ └── common.js
└── user
│ ├── user.server.routes.js
│ ├── user.server.model.js
│ └── user.server.controller.js
├── bower.json
├── README.md
├── package.json
└── server.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "client/bower_components"
3 | }
--------------------------------------------------------------------------------
/client/modules/app/views/app.client.login.html:
--------------------------------------------------------------------------------
1 |
2 |
Welcome to Login
3 |
--------------------------------------------------------------------------------
/client/modules/app/app.client.module.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Use Application configuration module to register a new module
4 | ApplicationConfiguration.registerModule('app');
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 | node_modules
14 | client/bower_components
15 | npm-debug.log
16 | /.idea
17 |
--------------------------------------------------------------------------------
/client/modules/app/services/app.client.services.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Authentication service for user variables
4 | angular.module('app').factory('userService', ['$resource', function($resource) {
5 | return $resource(':url/:id', {},
6 | {
7 | 'getArray': { isArray: true },
8 | 'save': { method: 'POST' },
9 | 'get': { method: 'GET' }
10 | });
11 | },
12 | ]);
--------------------------------------------------------------------------------
/server/config/db.js:
--------------------------------------------------------------------------------
1 | const Mongoose = require('mongoose')
2 | const config = require('./config')
3 |
4 | Mongoose.connect(config.database.url);
5 | const db = Mongoose.connection;
6 | db.on('error', console.error.bind(console, 'connection error'));
7 | db.once('open', function callback() {
8 | console.log("Connection with database succeeded.");
9 | });
10 |
11 | exports.db = db;
12 |
--------------------------------------------------------------------------------
/server/user/user.server.routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const User = require('./user.server.controller')
3 |
4 | module.exports = function(app){
5 | // API Server Endpoints
6 | app.post('/user', User.create);
7 | app.post('/login', User.login);
8 | app.post('/forgot', User.forgotPassword);
9 | app.post('/reset', User.newPassword);
10 | app.post('/verifyLink', User.verifyEmail);
11 | }
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "login",
3 | "description": "",
4 | "version": "0.0.0",
5 | "homepage": "",
6 | "license": "",
7 | "private": true,
8 | "dependencies": {
9 | "angular": "^1.3.x",
10 | "angular-route": "~1.2.x",
11 | "angular-resource": "~1.2.x",
12 | "angular-ui-router": "0.3.2",
13 | "angular-growl-v2": "^0.7.9"
14 | },
15 | "resolutions": {
16 | "angular": "^1.3.x"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/server/config/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | server: {
3 | host: 'localhost',
4 | port: 3000
5 | },
6 | database: {
7 | host: 'localhost',
8 | port: 27017,
9 | db: 'login',
10 | url: 'mongodb://127.0.0.1:27017/login'
11 | },
12 | key: {
13 | privateKey: '37LvDSm4XvjYOh9Y',
14 | tokenExpiry: 1 * 30 * 1000 * 60 //1 hour
15 | },
16 | email: {
17 | username: "sendermailid",
18 | password: "senderpassword",
19 | verifyEmailUrl: "verifyEmail",
20 | resetEmailUrl: "reset"
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | User Authentication using JWT (JSON Web Token) in Node.js (Express Framework ) and Angular.js
2 |
3 | ### Install an app
4 |
5 | Run the following command in root directory of an app in command prompt.
6 |
7 | ###### *Install node packages*
8 |
9 | npm install
10 |
11 | bower install | sudo bower install --allow-root
12 |
13 | Run the following command in root directory of an app in command prompt.
14 |
15 | node server.js
16 |
17 | You can see the port number in command prompt after sucessfull run
18 |
19 | You can change the settings in server/config/config.js file
20 |
21 | set email and password into config file
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-express-boilerplate",
3 | "description": "node-boilerplate",
4 | "version": "0.1.0",
5 | "main": "server",
6 | "engines": {
7 | "node": ">=6.9.0"
8 | },
9 | "scripts": {
10 | "postinstall": "bower install"
11 | },
12 | "dependencies": {
13 | "body-parser": "^1.13.3",
14 | "cookie-parser": "~1.0.1",
15 | "express": "^4.10.6",
16 | "jsonwebtoken": "^7.0.0",
17 | "mongoose": ">=4.7.3",
18 | "mongoose-auto-increment": "^5.0.1",
19 | "mongoose-unique-validator": "^1.0.5",
20 | "morgan": "^1.8.1",
21 | "nodemailer": "^0.7.1"
22 | },
23 | "author": {
24 | "name": "sonipandey",
25 | "email": "sonipandey.71@gmail.com"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/client/modules/app/config/app.client.routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Setting up route
4 | angular.module('app').config(['$stateProvider', '$urlRouterProvider',
5 | function($stateProvider, $urlRouterProvider) {
6 | // Redirect to home view when route not found
7 | $urlRouterProvider.otherwise('/');
8 |
9 | // Home state routing
10 | $stateProvider.
11 | state('home', {
12 | url: '/',
13 | templateUrl: 'modules/app/views/app.client.home.html'
14 | })
15 | .state('login', {
16 | url: '/login-page',
17 | templateUrl: 'modules/app/views/app.client.login.html'
18 | })
19 | .state('reset', {
20 | url: '/reset/:token',
21 | templateUrl: 'modules/app/views/app.client.reset.html'
22 | })
23 | .state('verify', {
24 | url: '/verifyEmail/:token',
25 | templateUrl: 'modules/app/views/app.client.home.html'
26 | })
27 | }
28 | ]);
--------------------------------------------------------------------------------
/client/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Init the application configuration module for AngularJS application
4 | var ApplicationConfiguration = (function() {
5 | // Init module configuration options
6 | var applicationModuleName = 'login';
7 | var applicationModuleVendorDependencies = ['ngResource', 'ui.router', 'angular-growl'];
8 |
9 | // Add a new vertical module
10 | var registerModule = function(moduleName, dependencies) {
11 | // Create angular module
12 | angular.module(moduleName, dependencies || []);
13 |
14 | // Add the module to the AngularJS configuration file
15 | angular.module(applicationModuleName).requires.push(moduleName);
16 | };
17 |
18 | return {
19 | applicationModuleName: applicationModuleName,
20 | applicationModuleVendorDependencies: applicationModuleVendorDependencies,
21 | registerModule: registerModule
22 | };
23 | })();
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express')
4 | const config = require('./server/config/config')
5 | const bodyParser = require('body-parser')
6 | const app = express()
7 | const db = require('./server/config/db')
8 | const path = require('path')
9 | const logger = require('morgan')
10 |
11 | app.use(bodyParser.urlencoded({ extended: true }));
12 | app.use(bodyParser.json());
13 | app.use(express.static(path.join(__dirname, 'client/')));
14 | app.use(logger('dev'));
15 |
16 | app.get('*', function(req, res) {
17 | res.sendFile(path.resolve('client/index.html')); // load the single view file (angular will handle the page changes on the front-end)
18 | });
19 | /** load routes*/
20 |
21 | require('./server/user/user.server.routes')(app);
22 |
23 | var port = config.server.port;
24 |
25 | app.listen(process.env.PORT || port);
26 |
27 | console.log('App started on port ' + port);
28 |
--------------------------------------------------------------------------------
/client/application.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //Start by defining the main module and adding the module dependencies
4 | angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies);
5 |
6 | // Setting HTML5 Location Mode
7 | angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider',
8 | function($locationProvider) {
9 | // $locationProvider.hashPrefix('!');
10 | $locationProvider.html5Mode({
11 | enabled: true,
12 | requireBase: false
13 | });
14 | }
15 | ]);
16 |
17 | //Then define the init function for starting up the application
18 | angular.element(document).ready(function() {
19 | //Fixing facebook bug with redirect
20 | // if (window.location.hash === '#_=_') window.location.hash = '#!/';
21 | //Then init the app
22 | angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]);
23 | });
--------------------------------------------------------------------------------
/client/modules/app/views/app.client.reset.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Set New Password
4 |
5 |
28 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | NodeApp
12 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/server/user/user.server.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose'),
2 | Schema = mongoose.Schema,
3 | autoIncrement = require('mongoose-auto-increment'),
4 | db = require('../config/db').db;
5 | uniqueValidator = require('mongoose-unique-validator');
6 |
7 | autoIncrement.initialize(db);
8 |
9 | /**
10 | * @module User
11 | * @description contain the details of Attribute
12 | */
13 |
14 | var User = new Schema({
15 |
16 | /**
17 | userName. It can only contain valid email id, should be unique, is required and indexed.
18 | */
19 | username: {
20 | type: String,
21 | unique: true,
22 | required: true
23 | },
24 |
25 | /**
26 | password. It can only contain string, is required field.
27 | */
28 | password: {
29 | type: String,
30 | required: true
31 | },
32 |
33 |
34 | /**
35 | propertyId. It can only contain string.
36 | */
37 | isVerified: {
38 | type: Boolean,
39 | default: false
40 | }
41 |
42 |
43 | });
44 |
45 | User.plugin(autoIncrement.plugin, {
46 | model: 'user',
47 | field: '_id'
48 | });
49 | User.plugin(uniqueValidator);
50 |
51 | User.statics = {
52 | saveUser: function(requestData, callback) {
53 | this.create(requestData, callback);
54 | },
55 | findUserUpdate: function(query, user, callback) {
56 | this.findOneAndUpdate(query, user, callback);
57 | },
58 | updateUser: function(user, callback) {
59 | user.save(callback);
60 | },
61 |
62 | findUser: function(query, callback) {
63 | this.findOne(query, callback);
64 | },
65 |
66 | findUserByIdAndUserName: function(id, username, callback) {
67 | this.findOne({ username: username, _id: id }, callback);
68 | }
69 | }
70 |
71 | var user = mongoose.model('user', User);
72 |
73 | /** export schema */
74 | module.exports = {
75 | User: user
76 | };
77 |
--------------------------------------------------------------------------------
/client/modules/app/controllers/app.client.controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | angular.module('app').controller('AppController', ['$scope', 'userService', 'growl', '$state', '$stateParams',
5 | ($scope, userService, growl, $state, $stateParams) => {
6 | $scope.signup = (data, form) => {
7 | userService.save({url: 'user'}, data).$promise.then((result) => {
8 | growl.success(result.message, {ttl: 5000})
9 | }).catch((error) => {
10 | growl.error(error.data, {ttl: 5000});
11 | })
12 | }
13 |
14 | $scope.login = (data, form) => {
15 | userService.save({url: 'login'}, data).$promise.then((result) => {
16 | console.log(result.username)
17 | if(result.username){
18 | $state.go('login');
19 | }
20 | else{
21 | growl.error(`Oh uh, something went wrong`);
22 | }
23 | }).catch((error) => {
24 | growl.error(error.data, {ttl: 5000});
25 | })
26 | }
27 |
28 | $scope.forgot = (data, form) => {
29 | userService.save({url: 'forgot'}, data).$promise.then((result) => {
30 | growl.success(result.message, {ttl: 5000})
31 | }).catch((error) => {
32 | growl.error(error.data, {ttl: 5000});
33 | })
34 | }
35 |
36 | $scope.reset = (data, form) => {
37 | data.token = $stateParams.token
38 | userService.save({url: 'reset'}, data).$promise.then((result) => {
39 | growl.success(result.message, {ttl: 5000})
40 | $state.go('home')
41 | }).catch((error) => {
42 | growl.error(error.data, {ttl: 5000});
43 | })
44 | }
45 | $scope.goToSignUp = () => {
46 | $scope.signupDiv = true;
47 | $scope.resetDiv = false;
48 | }
49 | $scope.goToSignIn = () => {
50 | $scope.signupDiv = false;
51 | $scope.resetDiv = false;
52 | }
53 | $scope.goToForgotPassword = () => {
54 | $scope.resetDiv = true;
55 | }
56 | $scope.load = () => {
57 | $scope.signupDiv = false;
58 | $scope.resetDiv = false;
59 | if($state.$current.name == 'verify'){
60 | var data = {token: $stateParams.token}
61 | userService.save({url: 'verifyLink'}, data).$promise.then((result) => {
62 | growl.success(result.message, {ttl: 5000})
63 | $state.go('home')
64 | }).catch((error) => {
65 | growl.error(error.data, {ttl: 5000});
66 | })
67 | }
68 | }
69 | $scope.load();
70 | }
71 | ]);
--------------------------------------------------------------------------------
/server/config/common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const nodemailer = require("nodemailer")
3 | const Config = require('./config')
4 | const crypto = require('crypto')
5 | const algorithm = 'aes-256-ctr'
6 | const privateKey = Config.key.privateKey;
7 |
8 | // create reusable transport method (opens pool of SMTP connections)
9 | // console.log(Config.email.username+" "+Config.email.password);
10 | var smtpTransport = nodemailer.createTransport("SMTP", {
11 | service: "Gmail",
12 | auth: {
13 | user: Config.email.username,
14 | pass: Config.email.password
15 | }
16 | });
17 |
18 | exports.decrypt = (password) => {
19 | return decrypt(password);
20 | };
21 |
22 | exports.encrypt = (password) => {
23 | return encrypt(password);
24 | };
25 |
26 | exports.sentMailVerificationLink = (user, token, callback) => {
27 | var textLink = "http://"+Config.server.host+":"+ Config.server.port+"/"+Config.email.verifyEmailUrl+"/"+token;
28 | var from = `Pyrite Team<${Config.email.username}>`;
29 | var mailbody = `Thanks for Registering
Please verify your email by clicking on the verification link below.
Verification Link
`
31 | mail(from, user.username , `Account Verification`, mailbody, function(error, success){
32 | callback(error, success)
33 | });
34 | };
35 |
36 | exports.sentMailForgotPassword = (user, token, callback) => {
37 | var textLink = "http://"+Config.server.host+":"+ Config.server.port+"/"+Config.email.resetEmailUrl+"/"+token;
38 | var from = `Pyrite Team<${Config.email.username}>`;
39 | var mailbody = `Please reset your password by clicking on the link below.
Reset Password Link
`
41 | mail(from, user.username , `Account New password`, mailbody, function(error, success){
42 | callback(error, success)
43 | });
44 | };
45 |
46 |
47 | function decrypt(password){
48 | var decipher = crypto.createDecipher(algorithm, privateKey);
49 | var dec = decipher.update(password, 'hex', 'utf8');
50 | dec += decipher.final('utf8');
51 | return dec;
52 | }
53 | function encrypt(password){
54 | var cipher = crypto.createCipher(algorithm, privateKey);
55 | var crypted = cipher.update(password.toString(), 'utf8', 'hex');
56 | crypted += cipher.final('hex');
57 | return crypted;
58 | }
59 | function mail(from, email, subject, mailbody, callback){
60 | var mailOptions = {
61 | from: from, // sender address
62 | to: email, // list of receivers
63 | subject: subject, // Subject line
64 | //text: result.price, // plaintext body
65 | html: mailbody // html body
66 | };
67 |
68 | smtpTransport.sendMail(mailOptions, function(error, response) {
69 | if (error) {
70 | callback(error, null)
71 | }
72 | else{
73 | callback(null, response)
74 | }
75 | smtpTransport.close(); // shut down the connection pool, no more messages
76 | });
77 | }
78 |
--------------------------------------------------------------------------------
/client/modules/app/views/app.client.home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
User Login
5 |
6 |
29 |
34 |
39 |
40 |
41 |
42 |
User Signup
43 |
44 |
68 |
73 |
78 |
79 |
80 |
81 |
Generate New Password
82 |
83 |
100 |
105 |
110 |
111 |
--------------------------------------------------------------------------------
/server/user/user.server.controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Common = require('../config/common')
3 | const Config = require('../config/config')
4 | const Jwt = require('jsonwebtoken')
5 | const User = require('./user.server.model').User
6 | const privateKey = Config.key.privateKey
7 | const async = require('async')
8 |
9 | exports.create = (req, res) => {
10 | req.body.password = Common.encrypt(req.body.password);
11 | async.waterfall([
12 | function(callback) {
13 | User.saveUser(req.body, (err, user) => {
14 | if (!err) {
15 | callback(null, user)
16 | } else {
17 | if(err.name == 'ValidationError'){
18 | let error = {}
19 | error.statusCode = 409
20 | error.message = `please provide another user email`
21 | callback(error, null);
22 | }
23 | else {
24 | let error = {}
25 | error.statusCode = 500
26 | error.message = `Oh uh, something went wrong`
27 | callback(error, null);// HTTP 403
28 | }
29 | }
30 | })
31 | },
32 | function(user, callback) {
33 | let tokenData = {
34 | username: user.username,
35 | id: user._id
36 | }
37 | Common.sentMailVerificationLink(user, Jwt.sign(tokenData, privateKey), (error, result) => {
38 | if(!error) callback(error, null)
39 | else callback(null, 'done')
40 | });
41 | },
42 | ],
43 | // optional callback
44 | function(err, results) {
45 | if(err){
46 | if(err.statusCode) return res.status(err.statusCode).send(err.message);
47 | else return res.status(500).send(`Oh uh, something went wrong`);
48 | }
49 | else{
50 | return res.json({message: `Please confirm your email id by clicking on link in email`});
51 | }
52 | });
53 |
54 | }
55 |
56 | exports.login = (req, res) => {
57 | User.findUser({username: req.body.username}, (err, user) => {
58 | if (err) {
59 | return res.status(500).send(`Oh uh, something went wrong`);
60 | }
61 | else if (user == null){
62 | return res.status(422).send(`Email not recognised`);
63 | }
64 | else{
65 | if (req.body.password === Common.decrypt(user.password)) {
66 | if(!user.isVerified){
67 | return res.status(401).send(`Your email address is not verified. please verify your email address to proceed`);
68 | }
69 | else{
70 | var tokenData = {
71 | username: user.username,
72 | id: user._id
73 | };
74 | var result = {
75 | username: user.username,
76 | token: Jwt.sign(tokenData, privateKey)
77 | };
78 |
79 | return res.json(result);
80 | }
81 | } else{
82 | return res.status(500).send(`Oh uh, something went wrong`);
83 | }
84 | }
85 | })
86 | }
87 |
88 | exports.forgotPassword = (req, res) => {
89 | async.waterfall([
90 | function(callback) {
91 | User.findUser({username: req.body.username}, (err, user) => {
92 | if (!err) {
93 | if (user === null){
94 | let error = {}
95 | error.statusCode = 422
96 | error.message = `please provide another user email`
97 | callback(error, null);
98 | }
99 | else{
100 | callback(null, user)
101 | }
102 | }
103 | else {
104 | let error = {}
105 | error.statusCode = 500
106 | error.message = `Oh uh, something went wrong`
107 | callback(error, null)
108 | }
109 | })
110 | },
111 | function(user, callback) {
112 | let tokenData = {
113 | username: user.username,
114 | id: user._id
115 | }
116 | Common.sentMailForgotPassword(user, Jwt.sign(tokenData, privateKey), (error, result) => {
117 | if(!error) callback(null, 'success')
118 | else {
119 | let error = {}
120 | error.statusCode = 500
121 | error.message = `Oh uh, something went wrong`
122 | callback(error, null)
123 | }
124 | });
125 | },
126 | ],
127 | // optional callback
128 | function(err, results) {
129 | if(err) {
130 | if(err.statusCode) return res.status(err.statusCode).send(err.message);
131 | else return res.status(500).send(`Oh uh, something went wrong`);
132 | }
133 | else return res.json({message: `reset password link sent to your mail.`});
134 | });
135 | }
136 |
137 | exports.newPassword = (req, res) => {
138 | Jwt.verify(req.body.token, privateKey, (err, decoded) => {
139 | if(err) return res.status(500).send(`Oh uh, something went wrong`);
140 | else{
141 | User.findUserByIdAndUserName(decoded.id, decoded.username, (err, user) => {
142 | if (err) {
143 | return res.status(500).send(`Oh uh, something went wrong`);
144 | }
145 | else if (user == null){
146 | return res.status(422).send(`Email not recognised`);
147 | }
148 | else if (req.body.newPassword !== req.body.confirmNew){
149 | return res.status(400).send(`Password Mismatch`);
150 | }
151 | else{
152 | user.password = Common.encrypt(req.body.newPassword);
153 | User.updateUser(user, (err, user) => {
154 | if (!err) {
155 | return res.json({message: `password changed successfully`});
156 | }
157 | else{ return res.status(500).send(`Oh uh, something went wrong`); }
158 | })
159 | }
160 | })
161 | }
162 | })
163 | }
164 |
165 | exports.verifyEmail = (req, res) => {
166 | Jwt.verify(req.body.token, privateKey, (err, decoded) => {
167 | if(err) return res.status(500).send(`Oh uh, something went wrong`);
168 | else{
169 | User.findUserByIdAndUserName(decoded.id, decoded.username, (err, user) => {
170 | if (err) {
171 | return res.status(500).send(`Oh uh, something went wrong`);
172 | }
173 | else if (user == null){
174 | return res.status(422).send(`Email not recognised`);
175 | }
176 | else if (user.isVerified === true){
177 | return res.json({message: `account is already verified`});
178 | }
179 | else{
180 | user.isVerified = true;
181 | User.updateUser(user, (err, user) => {
182 | if (!err) {
183 | return res.json({message:`account sucessfully verified`});
184 | }
185 | else{ return res.status(500).send(`Oh uh, something went wrong`); }
186 | })
187 | }
188 | })
189 | }
190 | })
191 | }
192 |
--------------------------------------------------------------------------------