Sequelize - beforeCreate not being called - node.js

I am trying to create a user model with sequelize in nodejs, but it doesn't seem that my beforeCreate isn't actually being called. I have looked at the documentation and multiple examples on the internet but I can't get it working.
The model code is as follows:
"use strict";
const bcrypt = require('bcrypt');
require('dotenv').config()
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
},
emailAddress: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
lastLogin: {
type: DataTypes.DATE,
allowNull: true
}
});
function cryptPassword(password, callback) {
bcrypt.genSalt(process.env.SALT_ROUNDS, function(err, salt) {
if (err)
return callback(err);
bcrypt.hash(password, salt, function(err, hash) {
return callback(err, hash);
});
});
};
User.beforeCreate(function(model, options) {
cryptPassword(model.password, function(err, hash) {
model.password = hash;
});
});
return User;
};

you should define your hook inside of your model
var User = sequelize.define("User", {
username: ...
}, {
hooks: {
beforeCreate: function(){...}
}
});

Related

Error with passeportjs and sequelize on express app

I have a problem and I cannot find a similar post for my problem, I want to go on a route, I have a message "Could not read property 'findOne' of undefined" but my model is good and works, I have it have tested with findall() in the file and works ....
I use express, sequelize and passeport.js in the back-end
Thank you in advance for your help
const { User } = require('../models')
const LocalStrategy = require('passport-local').Strategy
const passport = require('passport')
const bcrypt = require('bcrypt')
const verifPass = (password, user) => {
return bcrypt.compareSync(password, user.user_password)
}
passport.serializeUser((user, done) => {
done(null, user.user_email)
})
passport.deserializeUser((email, done) => {
User.findOne({where: {user_email: email}}).then(user => {
done(null, user)
}).catch(err => {
done(err, null)
})
})
passport.use(new LocalStrategy(
function(email, password, done) {
User.findOne({where: {user_email: email}}).then(user => {
if (!user){
return done(null, false)
}
if (!verifPass(password, user)){
return done(null, false)
}
return done(null, user)
}).catch(err => {
return done(err, null)
})
}
));
module.exports = passport
The user model
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate({ Role, Role_User }) {
// define association here
this.belongsTo(Role, {foreignKey: "user_role_id" })
}
};
User.init({
user_name: {
type: DataTypes.STRING,
allowNull: false
},
user_surname: {
type: DataTypes.STRING,
allowNull: false
},
user_phone: {
type: DataTypes.STRING,
allowNull: false
},
user_email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
validate: {
isEmail: true
}
},
user_last_connection: {
type: DataTypes.DATE,
allowNull: false
},
user_password: {
type: DataTypes.STRING,
allowNull: false
},
user_function: {
type: DataTypes.STRING,
allowNull: true
},
user_data_hiring: {
type: DataTypes.DATE,
allowNull: true
},
user_data_departure: {
type: DataTypes.STRING,
allowNull: true
},
user_notes: {
type: DataTypes.STRING,
allowNull: true
},
user_role_id: {
type: DataTypes.STRING,
allowNull: false,
references: {
model: "Roles",
key: "id"
}
},
isDeleted: {
type: DataTypes.BOOLEAN,
allowNull: false,
}
}, {
sequelize,
modelName: 'User',
}, );
return User;
};
error log
-------------------EDIT-----------------
fix the first error message by create method outside passport.use
const findUser = async (email) => {
return await User.findOne({where: {user_email: email}})
}
passport.use(new LocalStrategy.Strategy({
usernameField: 'email',
passwordField: 'password'
}, async (email, password, done) => {
try {
const user = await findUser(email)
console.log(user)
if (user && verifPass(password, user)){
done(null, user)
}else{
done(null, false)
}
}catch (error) {
done(error)
}
}))
but now an other unknow function...
log error 2
I really don't understand in the doc it doesn't make sense to happen like that.
the login controller :
router.post('/',
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.json(req.user)
});
-------------------SOLUTION-----------------
After several hours, I found that it was actually a sensible dependency to save sessions that was causing the problem: https://www.npmjs.com/package/connect-session-sequelize
I replaced it with: https://www.npmjs.com/package/express-mysql-session and it works perfectly

Multi foreign key on hasMany association Sequelize

In sequelize, if I have an association like :
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true});
Sequelize will automatically add UserId column to Article table. But now I want to add more like UserName and Email to Article so when I query an article I have author's name and I do not need to query again by UserId.
How can I do that ? I tried
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true, foreignKey: {'UserId': 'id', 'UserName': 'name' } });
but it only UserId appear in Article table.
Here is my User model:
"use strict";
var bcrypt = require('bcryptjs');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
primaryKey: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
primaryKey: true
},
role: {
type: DataTypes.STRING,
allowNull: false
},
activeToken: {
type: DataTypes.STRING,
allowNull: false,
},
status: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
avatar: {
type: DataTypes.STRING,
allowNull: true
},
phone: {
type: DataTypes.STRING,
allowNull: true
}
}, {
instanceMethods: {
updatePassword: function (newPass, callback) {
var self = this;
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(newPass, salt, function (err, hashed) {
self.update({
password: hashed
}).then(callback);
});
});
},
updateStatus: function (status, callback) {
this.update({
status: status
}).then(callback);
},
comparePassword: function (password, callback) {
bcrypt.compare(password, this.password, function (err, isMatch) {
if (err) {
throw err;
}
callback(isMatch);
});
},
updateRole: function (newRole, callback) {
this.update({
role: newRole
}).then(callback);
}
},
classMethods: {
createUser: function (newUser, callback) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(newUser.password, salt, function (err, hash) {
newUser.password = hash;
User.create(newUser).then(callback);
});
});
},
getUserById: function (id, callback) {
var query = {
where: {
id: id
}
}
User.findOne(query).then(callback);
},
getUserByUsername: function (username, callback) {
var query = {
where: {
username: username
}
};
User.findOne(query).then(callback);
},
getUserByEmail: function (email, callback) {
var query = {
where: {
email: email
}
};
User.findOne(query).then(callback);
},
getAllUser: function (callback) {
User.findAll().then(callback);
},
deleteUser: function (userId, callback) {
var query = {
where: {
id: userId
}
};
User.destroy(query).then(callback);
},
associate: function (models) {
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true, onUpdate: 'cascade', foreignKey: {'UserId': 'id', 'UserName': 'name' } });
User.hasMany(models.Comment, { onDelete: 'cascade', hooks: true, onUpdate: 'cascade' });
User.hasMany(models.Device, { onDelete: 'cascade', hooks: true, onUpdate: 'cascade' });
}
},
tableName: 'User'
});
return User;
};
You don't need to run 2 Queries to get user information. Just add a belongsTo relation from Article to User
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true});
Article.belongsTo(models.User);
and query the Articles like this
Article.findAll({ where: <condition>, include: {
model: User,
attributes: ['name', 'email']
} });
you will have the result in the following format:
[.....
{
articleId: 23,
articleTitle: "<Some String>",
User: {
name: "User Name",
email: "User Email"
}
}
........
]
On a separate note, your User model has multiple primary keys, which may not work as expected. If you want to use a composite primary key then there is a separate approach for that.

NodeJs mongoose CastError on method findById

I have a problem with mongoose, when i use the method findByIdi receive error :
CastError: Cast to ObjectId failed for value "protected" at path "_id"
My _id is valid tested by mongoose.Types.ObjectId.isValid(_id);
I also tested to convert my string _id to ObjectId: mongoose.Types.ObjectId(_id) same error...
My Model is :
var UserSchema = new Schema({
_id: {type:ObjectIdSchema, default: function () { return new ObjectId()} },
email: { type: String, unique: true, required: true },
pseudonyme: { type: String, unique: true, required: true },
password: { type: String, required: true }})
I use node v6.7.0 and mongoose v4.6.5
Thks in advance for your help,
Full Code :
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
//payload { _id: "58109f58e1bc7e3f28751cdb",email: "antoine.drian#laposte.net",exp: 1477494763,firstName: "antoine",iat: 1477484683,lastName: "drian"}
var isValid = mongoose.Types.ObjectId.isValid(payload._id);
if(!isValid) done(null, false);
var ObjectId = mongoose.Types.ObjectId;
var _id = ObjectId(payload._id);
User.findById( _id , function(err, user) {
if (err) { return done(err, false); }
if (user) {
done(null, user);
} else {
done(null, false);
}
});
});
models/User.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectIdSchema = Schema.ObjectId;
var ObjectId = mongoose.Types.ObjectId;
var bcrypt = require('bcrypt');
// set up a mongoose model
var UserSchema = new Schema({
// _id: {type: Schema.Types.ObjectId, auto: true},
email: { type: String, unique: true, required: true },
pseudonyme: { type: String, unique: true, required: true },
password: { type: String, required: true },
profile: {
firstName: { type: String, required: true },
lastName: { type: String, required: true },
birthdate: { type: Date },
gender: { type: String, enum: ['Male', 'Female'] },
}
}, {
timestamps: true
});
UserSchema.pre('save', function(next) {
var user = this;
if (this.isModified('password') || this.isNew) {
bcrypt.genSalt(10, 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();
});
});
} else {
return next();
}
});
UserSchema.methods.comparePassword = function(passw, cb) {
bcrypt.compare(passw, this.password, function(err, isMatch) {
if (err) {
return cb(err);
}
cb(null, isMatch);
});
};
module.exports = mongoose.model('User', UserSchema);
I found the solution it was just a route conflict between two routes with ExpressJS.
There is no relation between the both.
Thks all people for your help
Try removing the _id from your schema.
var UserSchema = new Schema({
email: { type: String, unique: true, required: true },
pseudonyme: { type: String, unique: true, required: true },
password: { type: String, required: true }
});
Try using the playload_id directly without casting it like below
User.findById( payload._id , function(err, user) {
if (err) { return done(err, false); }
if (user) {
done(null, user);
} else {
done(null, false);
}
});
first don't define _id in your Schema, and change 'isValid', use this instead
var UserSchema = new Schema({
email: { type: String, unique: true, required: true },
pseudonyme: { type: String, unique: true, required: true },
password: { type: String, required: true }
})
and if is there an error keep it as first parameter EX : done(err) otherwise use null, EX: done(null, result)
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
var _id = mongoose.mongo.ObjectId(payload._id);
User.find( {_id : _id} , function(err, user) {
if (err) { return done(err); }
if (user) {
done(null, user);
} else {
done(new Error('User not found!!!'));
}
});
});

Using BCrypt with Sequelize Model

I'm trying to use the bcrypt-nodejs package with my sequelize model and was tring to follow a tutorial to incorporate the hashing into my model, but I'm getting an error at generateHash. I can't seem to figure out the issue. Is there a better way to incorporate bcrypt?
Error:
/Users/user/Desktop/Projects/node/app/app/models/user.js:26
User.methods.generateHash = function(password) {
^
TypeError: Cannot set property 'generateHash' of undefined
at module.exports (/Users/user/Desktop/Projects/node/app/app/models/user.js:26:27)
at Sequelize.import (/Users/user/Desktop/Projects/node/app/node_modules/sequelize/lib/sequelize.js:641:30)
model:
var bcrypt = require("bcrypt-nodejs");
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('users', {
annotation_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.DATE,
field: 'first_name'
},
lastName: {
type: DataTypes.DATE,
field: 'last_name'
},
email: DataTypes.STRING,
password: DataTypes.STRING,
}, {
freezeTableName: true
});
User.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
User.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
return User;
}
Methods should be provided in the "options" argument of sequelize.define
const bcrypt = require("bcrypt");
module.exports = function(sequelize, DataTypes) {
const User = sequelize.define('users', {
annotation_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.DATE,
field: 'first_name'
},
lastName: {
type: DataTypes.DATE,
field: 'last_name'
},
email: DataTypes.STRING,
password: DataTypes.STRING
}, {
freezeTableName: true,
instanceMethods: {
generateHash(password) {
return bcrypt.hash(password, bcrypt.genSaltSync(8));
},
validPassword(password) {
return bcrypt.compare(password, this.password);
}
}
});
return User;
}
Other alternative: Use hook and bcrypt async mode
User.beforeCreate((user, options) => {
return bcrypt.hash(user.password, 10)
.then(hash => {
user.password = hash;
})
.catch(err => {
throw new Error();
});
});
There's a tutorial out there on how to get a sequelize/postgreSQL auth system working with hooks and bcrypt.
The guy who wrote the tutorial did not use async hash/salt methods; in the user creation/instance method section he used the following code:
hooks: {
beforeCreate: (user) => {
const salt = bcrypt.genSaltSync();
user.password = bcrypt.hashSync(user.password, salt);
}
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
}
}
Newer versions of Sequelize don't like instance methods being declared this way - and multiple people have explained how to remedy this (including someone who posted on the original tutorial):
The original comment still used the synchronous methods:
User.prototype.validPassword = function (password) {
return bcrypt.compareSync(password, this.password);
};
All you need to do to make these functions asyncronous is this:
Async beforeCreate bcrypt genSalt and genHash functions:
beforeCreate: async function(user) {
const salt = await bcrypt.genSalt(10); //whatever number you want
user.password = await bcrypt.hash(user.password, salt);
}
User.prototype.validPassword = async function(password) {
return await bcrypt.compare(password, this.password);
}
On the node.js app in the login route where you check the password, there's a findOne section:
User.findOne({ where: { username: username } }).then(function (user) {
if (!user) {
res.redirect('/login');
} else if (!user.validPassword(password)) {
res.redirect('/login');
} else {
req.session.user = user.dataValues;
res.redirect('/dashboard');
}
});
All you have to do here is add the words async and await as well:
User.findOne({ where: { username: username } }).then(async function (user) {
if (!user) {
res.redirect('/login');
} else if (!await user.validPassword(password)) {
res.redirect('/login');
} else {
req.session.user = user.dataValues;
res.redirect('/dashboard');
}
});
Bcrypt Is no longer part of node, so I included example with new module of crypto
I am sharing this code from one of working project.
My config file
require('dotenv').config();
const { Sequelize,DataTypes ,Model} = require("sequelize");
module.exports.Model = Model;
module.exports.DataTypes = DataTypes;
module.exports.sequelize = new Sequelize(process.env.DB_NAME,process.env.DB_USER_NAME, process.env.DB_PASSWORD, {
host: process.env.DB_HOST,
dialect: process.env.DB_DISELECT,
pool: {
max: 1,
min: 0,
idle: 10000
},
//logging: true
});
My user model
const { sequelize, DataTypes, Model } = require('../config/db.config');
var crypto = require('crypto');
class USERS extends Model {
validPassword(password) {
var hash = crypto.pbkdf2Sync(password,
this.SALT, 1000, 64, `sha512`).toString(`hex`);
console.log(hash == this.PASSWORD)
return this.PASSWORD === hash;
}
}
USERS.init(
{
ID: {
autoIncrement: true,
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true
},
MOBILE_NO: {
type: DataTypes.BIGINT,
allowNull: false,
unique: true
},
PASSWORD: {
type: DataTypes.STRING(200),
allowNull: false
},
SALT: {
type: DataTypes.STRING(200),
allowNull: false
}
},
{
sequelize,
tableName: 'USERS',
timestamps: true,
hooks: {
beforeCreate: (user) => {
console.log(user);
user.SALT = crypto.randomBytes(16).toString('hex');
user.PASSWORD = crypto.pbkdf2Sync(user.PASSWORD, user.SALT,
1000, 64, `sha512`).toString(`hex`);
},
}
});
module.exports.USERS = USERS;
And Auth Controller
const { USERS } = require('../../../models/USERS');
module.exports = class authController {
static register(req, res) {
USERS.create({
MOBILE_NO: req.body.mobile,
PASSWORD: req.body.password,
SALT:""
}).then(function (data) {
res.json(data.toJSON());
}).catch((err) => {
res.json({
error: err.errors[0].message
})
})
}
static login(req, res) {
var message = [];
var success = false;
var status = 404;
USERS.findOne({
where:{
MOBILE_NO: req.body.mobile
}
}).then(function (user) {
if (user) {
message.push("user found");
if(user.validPassword(req.body.password)) {
status=200;
success = true
message.push("You are authorised");
}else{
message.push("Check Credentials");
}
}else{
message.push("Check Credentials");
}
res.json({status,success,message});
});
}
}
Old question, but maybe can help someone, you can use sequelize-bcrypt
Example:
const { Sequelize, DataTypes } = require('sequelize');
const useBcrypt = require('sequelize-bcrypt');
const database = new Sequelize({
...sequelizeConnectionOptions,
});
const User = database.define('User', {
email: { type: DataTypes.STRING },
password: { type: DataTypes.STRING },
});
useBcrypt(User);
Usage
User.create({ email: 'john.doe#example.com', password: 'SuperSecret!' });
// { id: 1, email: 'john.doe#example.com', password: '$2a$12$VtyL7j5xx6t/GmmAqy53ZuKJ1nwPox5kHLXDaottN9tIQBsEB3EsW' }
const user = await User.findOne({ where: { email: 'john.doe#example.com' } });
user.authenticate('WrongPassword!'); // false
user.authenticate('SuperSecret!'); // true

Node Sequelize migrations/models is it possible to share the same code?

I'm new at Sequelize so be patient.
I started up a new project using Sequelize
and migrations so I've got like this:
migrations/20150210104840-create-my-user.js:
"use strict";
module.exports = {
up: function(migration, DataTypes, done) {
migration.createTable("MyUsers", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
first_name: {
type: DataTypes.STRING
},
last_name: {
type: DataTypes.STRING
},
bio: {
type: DataTypes.TEXT
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
}
}).done(done);
},
down: function(migration, DataTypes, done) {
migration.dropTable("MyUsers").done(done);
}
};
models/myuser.js:
"use strict";
module.exports = function(sequelize, DataTypes) {
var MyUser = sequelize.define("MyUser", {
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
bio: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
}
}
});
return MyUser;
};
as you can see the table definition
is both on the migration and the model file.
I'm wondering if there is a way to share
the code ?
I mean I don't like to have logic in two files
if a field change I've to update twice.
UPDATE
following the Yan Foto example below
a different way may be cleaner.
schemas/users
'use strict';
module.exports = {
name: 'users',
definition : function(DataTypes) {
return {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstname: {
type:DataTypes.STRING
},
lastname: {
type:DataTypes.STRING
},
email: {
type: DataTypes.STRING,
unique: true
},
username: {
type:DataTypes.STRING,
unique: true
}
};
}
};
models/users
'use strict';
var Schema = require('../schemas/users');
module.exports = function(sequelize, DataTypes) {
return sequelize.define(
Schema.name,
Schema.definition(DataTypes),
{
freezeTableName: true ,
instanceMethods: {
countTasks: function() {
// how to implement this method ?
}
}
}
);
};
migrations/20150720184716-users.js
'use strict';
var Schema = require('../schemas/users');
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.createTable(
Schema.name,
Schema.definition(Sequelize)
);
},
down: function (queryInterface, Sequelize) {
return queryInterface.dropTable(Schema.name);
}
};
I wondered the same thing as I started using sequelize and here is my solution. I define my models as bellow:
module.exports = {
def: function(DataTypes) {
return {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
username: DataTypes.STRING,
password: DataTypes.STRING,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
}
},
config: {}
};
Where def defines the attributes and config is the optional options object accepted by define or migration methods. And I import them using the following code:
fs.readdirSync(__dirname + '/PATH/TO/models')
.filter(function(file) {
return (file.indexOf('.') !== 0) && (file !== basename);
})
.forEach(function(file) {
var name = file.substring(0, file.lastIndexOf(".")),
definition = require(path.join(__dirname + '/models', file));
sequelize['import'](name, function(sequelize, DataTypes) {
return sequelize.define(
name,
definition.def(DataTypes),
definition.config
);
});
});
For the migrations I have a similar approach:
const path = require('path');
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.createTable(
'users',
require(path.join(__dirname + '/PATH/TO/models', 'user.js')).def(Sequelize)
);
},
down: function (queryInterface, Sequelize) {
return queryInterface.dropTable('users');
}
};

Resources