NodeJS Express Passport Multiple files

Passport

This post to explain how it works in express3-multiple-files in pasport-local
Original code is passport-local created by mjhea0.

My other posts about Passport is following

Separate files

function

Source file Description
routes/basic.js basic route(only render ejs)
routes/user.js user related routes
config/dbschema.js db schema and connection
config/pass.js passport related work
app.js main part

Separtes routing parts, passport parts and mongodb parts.

layout views/

layout file Description
footer.ejs Footer
header.ejs Header
index.ejs Top page(after signin)
login.ejs Sign in page

Logic

app.js

var express = require('express')
  , http = require('http')
  , path = require('path')
  , app = express()
  , db = require('./config/dbschema')
  , pass = require('./config/pass')
  , passport = require('passport')
  , basic_routes = require('./routes/basic')
  , user_routes = require('./routes/user');


// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());

app.use(express.session({ secret: 'keyboard cat' }));
// Initialize Passport!  Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

// Basic pages
app.get('/', basic_routes.index);

// User pages
app.get('/account', pass.ensureAuthenticated, user_routes.account);
app.get('/login', user_routes.getlogin);
app.post('/login', user_routes.postlogin);
//app.get('/admin', pass.ensureAuthenticated, pass.ensureAdmin(), user_routes.admin);
app.get('/logout', user_routes.logout);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

pass.js

var passport = require('passport')
  ,LocalStrategy = require('passport-local').Strategy
  , db = require('./dbschema');

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  db.userModel.findById(id, function (err, user) {
    done(err, user);
  });
});

passport.use(new LocalStrategy(function(username, password, done) {
  db.userModel.findOne({ username: username }, function(err, user) {
    if (err) { return done(err); }
    if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
    user.comparePassword(password, function(err, isMatch) {
      if (err) return done(err);
      if(isMatch) {
        return done(null, user);
      } else {
        return done(null, false, { message: 'Invalid password' });
      }
    });
  });
}));

//Simple route middleware to ensure user is authenticated.  Otherwise send to login page.
exports.ensureAuthenticated = function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) { return next(); }
  res.redirect('/login')
}

dbschema.js

var mongoose = require('mongoose'),
  bcrypt = require('bcrypt'),
  SALT_WORK_FACTOR = 10;

exports.mongoose = mongoose;

// Database connect
var uristring = 
  process.env.MONGOLAB_URI ||
  process.env.MONGOHQ_URL ||
  'mongodb://localhost/test';

var mongoOptions = { db: { safe:true }};

mongoose.connect(uristring, mongoOptions, function(err, res) {
  if (err) {
    console.log('ERROR connecting to:' + uristring + '. ' + err);
  }
  else {
    console.log('Successfully connected to: ' + uristring);
  }
});

var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

// User Schema
var userSchema = new Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true},
  admin: { type: Boolean, required: true },
});

//Bcrypt middleware
userSchema.pre('save', function(next) {
  var user = this;

  if(!user.isModified('password')) return next();

  bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
    if(err) return next(err);

    bcrypt.hash(user.password, salt, function(err, hash) {
      if(err) return next(err);
      user.password = hash;
      next();
    });
  });
});

//Password verification
userSchema.methods.comparePassword = function(candidatePassword, cb) {
  bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
    if(err) return cb(err);
    cb(null, isMatch);
  });
};

// Export user model
var userModel = mongoose.model('User', userSchema);
exports.userModel = userModel;

user.js

var passport = require('passport');

exports.account = function(req, res) {
  res.render('account', { user: req.user });
};

exports.getlogin = function(req, res) {
  res.render('login', { user: req.user, message: req.session.messages });
};

exports.admin = function(req, res) {
  res.send('access granted admin!');
};

//POST /login
//This is an alternative implementation that uses a custom callback to
//acheive the same functionality.
exports.postlogin = function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err) }
    if (!user) {
      req.session.messages =  [info.message];
      return res.redirect('/login')
    }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/');
    });
  })(req, res, next);
};

exports.logout = function(req, res) {
  req.logout();
  res.redirect('/');
};

basic.js

var passport = require('passport');

exports.account = function(req, res) {
  res.render('account', { user: req.user });
};

exports.getlogin = function(req, res) {
  res.render('login', { user: req.user, message: req.session.messages });
};

exports.admin = function(req, res) {
  res.send('access granted admin!');
};

//POST /login
//This is an alternative implementation that uses a custom callback to
//acheive the same functionality.
exports.postlogin = function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err) }
    if (!user) {
      req.session.messages =  [info.message];
      return res.redirect('/login')
    }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/');
    });
  })(req, res, next);
};

exports.logout = function(req, res) {
  req.logout();
  res.redirect('/');
};

Layout

footer.ejs

	</body>
</html>

header.ejs

<!DOCTYPE html>
<html>
	<head>
		<title>Passport-Local Example</title>
	</head>
	<body>
		<% if (!user) { %>
			<p>
			<a href="/">Home</a> | 
			<a href="/admin">Admin Page</a> | 
			<a href="/login">Log In</a>
			</p>
		<% } else { %>
			<p>
			<a href="/">Home</a> | 
			<a href="/admin">Admin Page</a> | 
			<a href="/account">Account</a> | 
			<a href="/logout">Log Out</a>
			</p>
		<% } %>

index.ejs

<% include header %>
<% if (!user) { %>
	<h2>Welcome! Please log in.</h2>
<% } else { %>
	<h2>Hello, <%= user.username %>.</h2>
<% } %>
<% include footer %>

login.ejs

<% include header %>
<% if (message) { %>
<p><%= message %></p>
<% } %>
<form action="/login" method="post">
	<div>
	<label>Username:</label>
	<input type="text" name="username"/><br/>
	</div>
	<div>
	<label>Password:</label>
	<input type="password" name="password"/>
	</div>
	<div>
	<input type="submit" value="Submit"/>
	</div>
</form>
<p><small>Hint - bob:secret</small></p>
<% include footer %>