3

I am using passport.js in conjunction with Sequelize version 6.3.3. The model file was scaffolded using the sequelize-cli and I have the following problems: I am unable to use either the instance methods or prototype methods that I have defined on my model classes. I keep getting the error "model.methodName is not a function".

Here is the code for my model: User.js

'use strict';
const { Model } = require('sequelize');
const bcrypt = require('bcryptjs');

module.exports = (sequelize, DataTypes) => {
    class User extends Model {
        static associate(models) {
            // define association here
        }

        testInstanceMethod() {
            console.log('Test passed');
        }
    }

    User.init(
        {
            email: {
                type: DataTypes.STRING,
                notNull: true,
                unique: true,
                validate: {
                    isEmail: {
                        args: true,
                        msg: 'Please enter a valid email',
                    },
                },
            },
            password: {
                type: DataTypes.STRING,
                notNull: true,
                validate: {
                    len: {
                        args: [8, 255],
                        msg: 'Password must be 8+ characters long',
                    },
                },
            },
        },
        {
            sequelize,
            modelName: 'User',
        }
    );

    // Hash password before user is created
    User.addHook('beforeCreate', async (user, options, next) => {
        try {
            // generate a salt
            const salt = await bcrypt.genSalt(10);
            // hash the password using the salt we generated above
            const hashedPassword = await bcrypt.hash(user.password, salt);
            // set user's password to the hashed password
            user.password = hashedPassword;
        } catch (error) {
            console.log(error);
            next(error);
        }
    });

    User.prototype.isValidPassword = async (user, userInputtedPassword) => {
        try {
            return await bcrypt.compare(userInputtedPassword, user.password);
        } catch (error) {
            throw new Error(error);
        }
    };

    return User;
};

passport.js

const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const db = require('../../models');
const LocalStrategy = require('passport-local');

/* ******************* */
// HANDLE JWT
/* ****************** */

// Passport jwt options object
let options = {};
// grab jwt from the header
options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
options.secretOrKey = process.env.TOKEN_SECRET;

// Configure and use passport.js
passport.use(
    new JwtStrategy(options, async (jwtPayload, done) => {
        try {
            // find user with an id matching the jwt subscriber id
            const user = await db.User.findAll({ where: { id: jwtPayload.sub } });
            if (user) {
                // if user is found, return it
                return done(null, user);
            } else {
                // if not found, return false
                done(null, false);
            }
        } catch (error) {
            // catch errors
            done(error, false);
        }
    })
);

/* ******************* */
// LOCAL STRATEGY
/* ****************** */
passport.use(
    new LocalStrategy(
        {
            usernameField: 'email',
        },
        async (email, password, done) => {
            try {
                // find user when given the email
                const user = await db.User.findAll({ where: { email: email } });

                // if no user is found. handle it
                if (!user) {
                    return done(null, false);
                }

                // test instance method
                user.testInstanceMethod();

                const passwordMatch = await user.isValidPassword(password);
                // if password doesn't match then handle it
                if (!passwordMatch) {
                    return done(null, false);
                }

                console.log('successful login');
                return done(null, user);
            } catch (error) {
                return done(error);
            }
        }
    )
);

If you consider the code in the passport.js file, the instance level method (testInstanceMethod) is throwing the error "Type error user.testInstanceMethod is not a function". If you comment that line out, the prototype method (isValidPassword) throws the error "Type error user.isValidPassword is not a function". What am I doing wrong?

1 Answers1

2

You should get one user to access its methods:

// users is an array, see findAll method in Sequelize documentation
const users = await db.User.findAll({ where: { email: email } });
if (!users.length) {
  return done(null, false);
}

// test instance method
users[0].testInstanceMethod();

const passwordMatch = await users[0].isValidPassword(password);

OR better use findOne if you have one one user with a certain email:

const user = await db.User.findOne({ where: { email: email } });
if (!user) {
  return done(null, false);
}

// test instance method
user.testInstanceMethod();

const passwordMatch = await user.isValidPassword(password);

Anatoly
  • 20,799
  • 3
  • 28
  • 42
  • Thanks! There were a dozen articles on how to create instance methods, and either I missed them saying this or they didn't really say it! This helps – Nick McLean Dec 07 '22 at 14:29