The following is an implementation of the local passport strategy, and is available for cloning or download from https://bitbucket.org/phidip/passportonewithavatars/src/master/. Choose to clone with SSH or HTTPS as befits your own setup.
This application connects to the Mongo database in a different way from Traversy.
This application also requires the user to supply an avatar ingafe file. This will be stored in the database. That aspect is not related to Passport, and it will be explained in Section E.1
code/passportOneWithAvatars/app.js
const cookieParser = require('cookie-parser');
const createError = require('http-errors');
const express = require('express');
const helmet = require("helmet"); // nml added
const logger = require('morgan');
const path = require('path');
/* part I from Traversy video */
const flash = require('connect-flash');
const session = require('express-session');
const passport = require('passport');
/* end Traversy */
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const app = express();
app.locals.pretty = app.get('env') === 'development'; // pretty print html
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(helmet()); // nml added
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
/* part II from Traversy Video */
app.use(session( // setup session
{
secret: '998537qporhgpfangæ143+575?)(%lfjgaæ', // footprints of the keyboard cat
resave: true,
saveUninitialized: true
}));
// Passport middleware
app.use(passport.initialize()); // init passport
app.use(passport.session()); // connect passport and sessions
require('./config/passport')(passport);
// Connect flash
app.use(flash());
// Global variables
app.use(function(req, res, next) {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
next();
});
/* end Traversy */
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
code/passportOneWithAvatars/routes/users.js
const express = require('express');
const router = express.Router();
const auth = require("../controllers/authController.js");
const { ensureAuthenticated, forwardAuthenticated } = require('../config/auth');
/* registration form */
router.get('/register', forwardAuthenticated, auth.register);
/* receive registration data */
router.post('/register', auth.postRegister);
/* login form */
router.get('/login', forwardAuthenticated, auth.login);
/* handle login */
router.post('/login', auth.postLogin)
/* logout, kills session and redirects to frontpage */
router.get('/logout', auth.logout);
/*
* This is a REST endpoint that GETs an image from the database
* it is requested from an HTML img tag
* re the pug file dashboard.pug, and the endpoint handler
* in auth.lookupImage
*/
router.get('/getimage/:userid', ensureAuthenticated, auth.lookupImage);
module.exports = router;
code/passportOneWithAvatars/routes/index.js
const express = require('express');
const index = require("../controllers/indexController.js");
const router = express.Router();
const { ensureAuthenticated, forwardAuthenticated } = require('../config/auth');
/* GET home page. */
router.get('/', forwardAuthenticated, index.frontpage);
/* dashnord for insiders */
router.get('/dashboard', ensureAuthenticated, index.dashboard);
module.exports = router;
code/passportOneWithAvatars/controllers/authController.js
const bcrypt = require('bcryptjs');
const formidable = require('formidable'); // required for image upload
const fs = require('fs'); // required for reading temp image file
const mongoose = require('mongoose');
const mongoUtil = require("../models/MongoHandler");
const passport = require('passport');
const User = require('../models/User');
const saltRounds = 10;
exports.register = function (req, res) {
res.render('register', {
title: 'Register'
});
};
exports.postRegister = async function (req, res) {
let form = new formidable.IncomingForm();
form.parse(req, async function(err, fields, files) {
if (err) { console.error(err); }
let { name, uid, email, password, passwordr } = fields;
let errors = [];
if (!name || !uid || !email || !password || !passwordr) {
errors.push({ msg: 'Please enter all fields' });
}
if (password != passwordr) {
errors.push({ msg: 'Passwords do not match' });
}
if (password.length < 32) {
errors.push({ msg: 'Password must be at least 32 characters' });
}
if (errors.length > 0) { // respond if errors
res.render('register', {
errors,
name,
uid,
email,
password,
passwordr
});
}
let db = await mongoUtil.mongoConnect(); // connect
let user = await User.findOne({ uid: uid });
if (user) {
errors.push({ msg: 'users already exists' });
res.render('register', { // respond if already exists
errors,
name,
uid,
email,
password,
passwordr
});
}
try {
db = await mongoUtil.mongoConnect(); // connect
let hash = await bcrypt.hash(password, saltRounds); // hash password and create obj
let newUser = new User({name: name, uid: uid, email: email, password: hash});
newUser.avatar.data = await fs.readFileSync(files.avatar.path); // add to obj with read uploaded image
newUser.avatar.contentType = files.avatar.type; // get its mimetype
await newUser.save();
req.flash('success_msg', 'You are now registered and can log in');
res.render('login', {title: 'Login'});
} catch (err) {
errors.push({ msg: 'an error occurred' });
res.render('register', { // respond if already exists
errors,
name,
uid,
email,
password,
passwordr
});
}
})
};
exports.login = function (req, res) {
res.render('login', {
title: 'Login'
});
};
exports.postLogin = async function (req, res, next) {
await passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
};
exports.logout = function (req, res) {
req.logout();
res.redirect('/');
};
/*
* REST endpoint for serving image
*/
exports.lookupImage = async function (req, res) {
let query = {uid: req.params.userid};
const db = await mongoUtil.mongoConnect();
let user = await User.findOne(query);
res.contentType(user.avatar.contentType);
res.send(user.avatar.data);
};
code/passportOneWithAvatars/config/passport.js
const bcrypt = require('bcryptjs');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/User');
module.exports = function(passport) {
passport.use(
new LocalStrategy({
usernameField: 'username'
},
async function (username, password, done) {
try {
let user = await User.findOne({ username: username });
if (!user) {
return done(null, false, { error_msg: 'Unknown user' });
}
let isMatch = await bcrypt.compare(password, user.password);
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { error_msg: 'Password incorrect' });
}
} catch (err) {
return done(null, false, {error_msg: 'unspecified error'});
}
})
);
passport.serializeUser(function(user, done) { // invoked on login
done(null, user);
});
passport.deserializeUser(async function(user, done) { // invoked when using session
done(null, user);
});
};
code/passportOneWithAvatars/config/auth.js
module.exports = {
ensureAuthenticated: function(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
req.flash('error_msg', 'Please log in to view that resource');
res.redirect('/users/login');
},
forwardAuthenticated: function(req, res, next) {
if (!req.isAuthenticated()) {
return next();
}
res.redirect('/');
}
};
code/passportOneWithAvatars/config/Params.js
<xi:include></xi:include>
code/passportOneWithAvatars/models/MongoHandler.js
/*
* MongoConnect as a Singleton
*/
const mongoose = require('mongoose');
const MongoParams = require('../config/MongoParams').MongoParams;
const PARAMS = {
useNewUrlParser: true,
useUnifiedTopology: true
};
class MongoConnection {
static conn = false;
static async mongoConnect() {
if (!MongoConnection.conn) {
try {
await mongoose.connect(MongoParams.CONSTR, PARAMS);
MongoConnection.conn = mongoose.connection;
console.log(`Connected to mongo server`);
} catch (err) {
console.log('something mongo???' + err);
}
}
return MongoConnection.conn
}
}
module.exports = MongoConnection;
code/passportOneWithAvatars/views/register.pug
extends layout
block content
main.otherpage
aside
section
h2 Please Enter Your Details to #{title}
include messages.pug
form(method='post' action='/users/register' enctype='multipart/form-data')
p
label Name
br
input(type='text' minlength='2' name='name' required)
p
label LoginID
br
input(type='text' minlength='2' name='uid' required)
p
label Email
br
input(type='email' minlength='6' name='email' required)
p
label Password
br
input(type='password' minlength='32' name='password' required)
p
label Repeat Password
br
input(type='password' minlength='32' name='passwordr' required)
p
label Avatar
br
input(type='file' name='avatar' placeholder='optional')
p
label
input(type='submit', value='Send')
aside
code/passportOneWithAvatars/views/login.pug
extends layout
block content
main.otherpage
aside
section
h2 Please #{title}
include messages.pug
form(method='post' action='/users/login')
p
label LoginID
br
input(type='text' minlength='2' name='uid')
p
label Password
br
input(type='password' minlength='32' name='password')
p
label
input(type='submit', value='Login')
aside