Search code examples
javascriptnode.jsexpresssequelize.jspassport.js

Referencing multiple models with Passport.js with Sequelize


So I have successfully implemented a user authentication using Passport.js.
Right now I am trying to implement a random URL generator for resetting passwords/confirming accounts/etc. But I cannot seem to reference the database table through Sequelize.

How do I reference an already-defined model in Sequelize outside the module it was defined in?

In the code below user is passed via function(passport, user). Adding a third parameter to its call in server.js (e.g. require('./app/config/passport.js')(passport, data.user, data.hashedURL) ) does not fix the problem.

Below is what I think are the relevant portions. I can post more if need be. Error point is indicated by an arrow in passport.js module below.
Thanks so much in advance for any light you can shed!

server.js

var express       = require('express'),
    expHandlebars = require('express-handlebars'),
    session       = require('express-session'),
    cookieParser  = require('cookie-parser'),
    bodyParser    = require('body-parser'),

    app           = express(),
    fs            = require('fs'),
    path          = require('path'),
    passport      = require('passport'),
    flash         = require('connect-flash');

var passport = require('passport');
var env = require('dotenv').config();

app.engine('handlebars', hbs = require('express-handlebars')
    .create({
        extname:        '.handlebars', 
        defaultLayout:  'main',
        layoutsDir:     path.join(__dirname, '/views/layouts/'),
        partialsDir:    path.join(__dirname, 'views/partials/')
    }).engine
);
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, '/views'));
app.use(express.static(path.join(__dirname, '/public')));    

//bodyParser
app.use(bodyParser.urlencoded({ 
    extended: true 
}));
app.use(bodyParser.json());

//passport
app.use(session(
        { 
            secret:           'ahahawouldntyouliketoknow?', 
            cookie: {
                maxAge:        259200,  //30 days
                secure:        false
            },
            resave:            true,
            saveUninitialized: true
})); 

app.use(passport.initialize());
app.use(passport.session());// session secret
app.use(flash());

//Models
var data = require('./app/data/');

//Routes
var authRoute = require('./app/routes/auth.js')(app, passport);

//load passport strategies
require('./app/config/passport.js')(passport, data.user);

app.listen(5000, function(err) {
    if (!err) {
        console.log("Site is live");
    } else {
        console.log(err);
    }
}); 

/app/routes/auth.js

module.exports = function(app, passport) {  
    app.get('/', function (req, res, next) {
        res.render('home', {
            page_title: "Seeing is Believing",
            inc_style: true,
            style_sheet: "style/home.css",
        });
    });

    //Routing for 'forgot password' functionality
    app.get('/forgot', function(req, res) {
        console.log("!" + req.flash('error'));
        res.send();
    });

    app.post('/forgot', passport.authenticate('user-forgot-password', {
        successRedirect: '/forgot',
        failureRedirect: '/',
        failureFlash: true
    }));

    ...

};

/app/config/passport.js

var bCrypt          = require('bcrypt-nodejs'),
    LocalStrategy   = require('passport-local').Strategy,
    CustomStrategy  = require('passport-custom').Strategy,
    Sequelize       = require('sequelize'),
    verify          = require('./verify');

module.exports = function(passport, user) {
    var User = user;
    var Hashed_URL = Sequelize.models("HashedURL");

    console.log(Hashed_URL);

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

    // Deserialize User 
    passport.deserializeUser(function(user_id, done) {
        User.findByPk(user_id).then(function(user) {
            if (user) {
                done(null, user.get());
            } else {
                done(user.errors, null);
            }
        });
    });

    //
    function makeURL(length) {
        var result           = '';
        var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        var charactersLength = characters.length;

        for ( var i = 0; i < length; i++ ) {
            for ( var j = 0; j < 5; j++ ) {
                result += characters.charAt(Math.floor(Math.random() * charactersLength)) + '-';
            }
        }

        return result;
    }

    //
    var generateHash = function(password) {
        return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
    };

    //Forgot password
    passport.use('user-forgot-password', new CustomStrategy(function(req, done) {
        console.log("0");
        User.findOne({
                where: { 
                    'login_key':    req.body.user_email
                }
        }).then(function(user) {
            console.log("1");
            if (!user) {
                return done(null, false, { 
                    message: "Unable to find username \'".concat(login_key).concat("\'.")
                });
            }

            var randomURL = makeURL(39);
            var data = {
                    hashed_url:         randomURL,
                    uid:                user.id,
                    email_info:         "",
                    email_contents:     "<h1>I LEIK PUGS</h1>",
                    attempts:           1
            };

            Hashed_URL.create(data).then(function(newURL, created) {    <--- this is where the error occurs
                if (!newURL) {
                    return done(null, false);
                }

                if (newURL) {
                    return done(null, newUser);
                }
            });

            return done(null, user);
        });
    }));

...

};

/app/data/user.js

module.exports = function(sequelize, Sequelize) {
    var User = sequelize.define('user', {
        id: {
            type: Sequelize.INTEGER.UNSIGNED,
            allowNull: false,
            unique: true,
            autoIncrement: true,
            primaryKey: true
        },

        ...

    });

    return User;
};

/app/data/hashedURL.js

module.exports = function(sequelize, Sequelize) {
    var hashed_URL = sequelize.define('HashedURL', {
        expiration_time: {
            type: 'TIMESTAMP',
            allowNull: false,
            defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
            primaryKey: true
        },

        ...

    });

    return hashed_URL;
};

/app/data/index.js

var fs = require("fs");
var path = require("path");
var Sequelize = require("sequelize");
var env = process.env.NODE_ENV || "development";
var config = require(path.join(__dirname, '..', '..', 'config', 'config.json'))[env];
var sequelize = new Sequelize(config.database, config.username, config.password, config);
var db = {};

fs
    .readdirSync(__dirname)
    .filter(function(file) {
        return (file.indexOf(".") !== 0) && (file !== "index.js");
    })
    .forEach(function(file) {
        var model = sequelize.import(path.join(__dirname, file));
        db[model.name] = model;
    });

Object.keys(db).forEach(function(modelName) {
    if ("associate" in db[modelName]) {
        db[modelName].associate(db);
    }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

Solution

  • Thanks to i.brod's comment I was able to solve the problem.

    This is what I have right now:

    auth.js

    module.exports = function(app, passport) {  
        app.get('/', function (req, res, next) {
            res.render('home', {
                page_title: "Seeing is Believing",
                inc_style: true,
                style_sheet: "style/home.css",
            });
        });
    
        //Routing for 'forgot password' functionality
        app.get('/login', function(req, res) {
            console.log(req.flash('error'));
            res.send();
        });
    
        app.post('/login', passport.authenticate('user-login-email', {
            successRedirect: '/dashboard',
            failureRedirect: '/',
            failureFlash: true
        }));
    
        app.get('/reset/?code=:hash', function(req, res, next) {
            passport.authenticate('user-reset-password', {
                successRedirect: res.render('inside', { 
                                    page_title: "Change Your Password",
                                    portal: function() {
                                        return "reset";
                                    }
                                }),
                failureRedirect: '/',
                failureFlash: true
            })(req, res, next);
        });
    
        app.post('/reset', function(req, res) {
            console.log(req.flash('error'));
            res.send();
        });
    
        ...
    
    };
    

    passport.js

    module.exports = function(passport, user) {
        var User = user;
        var Hashed_URL = db["hashed_url"];
    
        passport.serializeUser((obj, done) => {
              if (obj instanceof User) {
                  done(null, { id: obj.id, type: 'User' });
              } else {
                  done(null, { id: obj.id, type: 'Hashed_URL' });
              }
        });
    
        passport.deserializeUser((obj, done) => {
            if (obj.type === 'User') {
                User.findByPk(obj.id).then((user) => done(null, user));
            } else {
                Hashed_URL.findByPk(obj.id).then((hashedURL) => done(null, hashedURL));
            }
        });
    
        //
        function makeURL(length) {
            var result           = '';
            var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
            var charactersLength = characters.length;
    
            for ( var i = 0; i < length; i++ ) {
                for ( var j = 0; j < 8; j++ ) {
                    result += characters.charAt(Math.floor(Math.random() * charactersLength));
                }
    
                if (i < (length - 1)) {
                    result += '-';
                }
            }
    
            return result;
        }
    
        //
        var generateHash = function(password) {
            return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
        };
    
        // Forgot password (send email)
        passport.use('user-forgot-password', new CustomStrategy(function(req, done) {
            User.findOne({
                    where: { 
                        'login_key':    req.body.user_email
                    }
            }).then(function(user) {
                if (!user) {
                    return done(null, false, { 
                        message: "Unable to find username \'".concat(login_key).concat("\'.")
                    });
                }
    
                var randomURL = 'f' + makeURL(26);
                var data = {
                        hashed_url:         randomURL,
                        id:                 user.id,
                        email_info:         "{'to':'[email protected]'}",
                        email_contents:     "<h1>I LEIK PUGS</h1>",
                        attempts:           1
                };
    
                Hashed_URL.create(data).then(function(newURL, created) {
                    if (!newURL) {
                        console.log("A");
                        return done(null, false);
                    }
    
                    if (newURL) {
                        console.log("B");
                        return done(null, newURL);
                    }
                });
            });
        }));
    
        ...
    
    };
    

    I do not think I altered anything else. Note: the random URL generator was just for testing, I think JWT or hashing would be more ideal for generating temporary secure URLs.

    I'll try to post as much of the final program as I can on my git when it is complete.