I'm trying to configurate my database ,i want the data returned to user doesn't include some sensetive data, so i'm adding an instanceMethods named toPublicJSON that using a function named pick from underscoreJS , but when i use this function (toPublicJSON) I encounter an error : that toPublicJSON is not a function
this my database configuration:
var bcrypt = require("bcrypt");
var _ = require("underscore");
module.exports = function(sequelize, DataType) {
return sequelize.define('users', {
email: {
type: DataType.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
salt: {
type: DataType.STRING
},
hassedPassword: {
type: DataType.STRING
},
password: {
type: DataType.VIRTUAL,
allowNull: false,
validate: {
len: [7, 100]
},
set: function(value) {
var salt = bcrypt.genSaltSync(10);
var hashed_password = bcrypt.hashSync(value, salt);
this.setDataValue('password', value);
this.setDataValue("salt", salt);
this.setDataValue("hassedPassword", hashed_password);
}
}
}, {
hooks: {
beforeValidate: function(user, option) {
if (typeof user.email == 'string')
user.email = user.email.toLowerCase();
}
},
instanceMethods:{
toPublicJSON: function() {
var json = this.toJSON();
return _.pick(json, 'id', 'email', 'createdAt', 'updatedAt');
}
}
});
}
my code :
app.post('/users',function(req,res){
var body=_.pick(req.body,"email","password");
db.users.create(body).then(function(user){
return res.send(user.toPublicJSON());
},function(e){
return res.status(400).json(e);
})
});
and this is the error :
Assuming you are using the latest version of Sequelize they changed slightly how to define instance and class methods. You need to assign functions to the prototype of the result of sequelize.define('users');. You will need to save the results of sequelize.define to a variable, similar to below...
const User = sequelize.define('users', {});
User.prototype.toPublicJSON = function() {
// Your code here
};
return User;
If you're on version 3 let me know.
Good luck :)
EDIT: Further reading material http://docs.sequelizejs.com/manual/tutorial/models-definition.html#expansion-of-models
EDIT 2: You may want to look into scopes. They are essentially pre-baked filters you can apply to your data when querying for them. http://docs.sequelizejs.com/manual/tutorial/scopes.html
If you did that then you could say
sequelize.define('users', {
// Attributes...
}, {
scopes: {
public: {
attributes: ['id', 'email', 'createdAt', 'updatedAt']
}
}
});
db.users.scope('public').findOne({...});
Related
I've defined a user model in Sequelize, and also defined a custom class method and an instance method for it. I'm calling these two methods in my login api (which works fine). The problem is that the class method works perfectly, but the instance method results an error, and I cannot recognize what is wrong with my code. PLEASE HELP.
This is my user model and its methods:
const Sequelize = require("sequelize");
const sequelize = require("../db/db.config");
const bcrypt = require("bcryptjs");
const _ = require("lodash");
const jwt = require("jsonwebtoken");
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
first_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
last_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
email: {
type: Sequelize.STRING(50),
allowNull: false,
validate: {
isEmail: true,
},
},
password: {
type: Sequelize.STRING(100),
allowNull: false,
},
});
User.prototype.testMethod = function () {
console.log("THIS IS A TEST");
};
User.beforeCreate(async (user, options) => {
const hashedPassword = await bcrypt.hash(user.password, 10);
user.password = hashedPassword;
});
User.findByEmailAndPassword = async function (inputEmail, inputPassword) {
try {
const user = await User.findOne({ where: { email: inputEmail } });
if (user === null) {
return null;
}
const passwordMatch = await bcrypt.compare(inputPassword, user.password);
if (!passwordMatch) {
return null;
}
return _.pick(user, "id", "first_name", "last_name", "email");
} catch (error) {
console.log("FIND BY EMAIL AND PASSWORD ERROR: ", error);
}
};
module.exports = User;
And this is my login router:
const express = require("express");
const router = express.Router();
const User = require("../models/user.model");
router.post("/api/login", async (req, res) => {
try {
const user = await User.findByEmailAndPassword(
req.body.email,
req.body.password
);
console.log("USER: ", user);
await user.testMethod();
if (!user) {
return res.status(400).send({
errorMessage: "Username and password combination is not correct!",
});
}
return res.status(200).send(user);
} catch (error) {
res.status(400).send({ errorMessage: error });
}
});
module.exports = router;
Thanks.
First, the reason that you get the issue is that findByEmailAndPassword is returning the regular object from _.pick and you are defining the instance method for Sequelize instance. This instance method can be callable on Sequelize instance and not on regular object.
However, your goal is
What I'm trying to do here is to avoid sending user password in my response body.
defaultScope is great for this use case. It allows you to define some repetitive options on a model.
You can define your User model as
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
...
}, {
defaultScope: {
attributes: {
exclude: ['password']
}
}
});
Defining the defaultScope on the model, this will be applied to many Sequelize functions by default.
Scopes apply to .find, .findAll, .count, .update, .increment and .destroy.
I also tested that it also applied to .findByPk, .findOne.
So, how to use...
Call regular Sequelize findOne function.
const user = User.findOne({
where: {
email: req.body.email,
password: req.body.password
}
});
By default, since defaultScope is applied, this won't return password in response.
In some scenarios where you need to return the password, use unscoped to disable the defaultScope.
// This will return `password` in response.
User.unscoped().fineOne(...)
For reference: https://sequelize.org/master/manual/scopes.html
So i was implementing a bit of change in my server application - switching databases from MongoDb to PostgreSQL (with Sequelize 6) and i had to change the controller functions i had created for mongoose to suit the current database but there was a problem implementing the instance methods, but as usual there was little to no helpful solutions online for this with Sequelize 6. But now there is. Below are the code samples for some of the problems and error messages you may be facing if you come across this post.
The function which calls the instance method:
userController.js (login function)
User.findByPk(req.body.id)
.then(user => {
if (!user) {
return res.status(401).send('Sorry!! You do not have an account with us.')
}
if (!user.validPassword(req.body.password)) {
return res.status(401).send('Invalid Password')
} else {
res.status(200).json({ user })
}
})
.catch(error => {
console.log(error)
res.status(500).send({
message: 'Some error occurred while logging in this User',
error: error.message
});
});
EXAMPLE CODE 1
user.js
'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(models) {
// define association here
}
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
return User;
};
response (Server 500 error message)
{
"message": "Some error occurred while logging in this User",
"error": "user.validPassword is not a function"
}
The instance method in this case is not recognized and the function validPassword() is not run thus a 500 Server error. Let's move to example 2.
EXAMPLE CODE 2
user.js
'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(models) {
// define association here
}
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
User.prototype.validPassword = (password) => {
return bcrypt.compareSync(password, this.password);
};
return User;
};
response (Server 500 error message)
{
"message": "Some error occurred while logging in this User",
"error": "Illegal arguments: string, undefined"
}
The instance method in this case is still not recognized and the function validPassword() is thus not run because over here the parameter this.password for the bcrypt.compareSync() function is not defined (or has been used outside the Model extension) thus a 500 Server error.
And now for the solution.
After around half a day of searching, I found out that for some reason the instanceMethods functionality has been removed in Sequelize v4. As a result, the only way to obtain this functionality is by one of the following:
declaring the function on the model class as Jeffrey Dabo suggested
adding the function on the prototype of the Sequelize model
Very important: If you go with the prototype approach, in order to have access to the this object, you need to declare the function using the function syntax and not as an arrow function, or else it will not work.
Example:
const User = sequelize.define('User', {...});
User.prototype.validatePassword = function (password) {
return bcrypt.compareSync(password, this.password);
}
Placing the instance method function right under the class method function (associations on models) would eventually allow your function validPassword() to be recognized, run and produce the desired response.
user.js
'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(models) {
// define association here
}
validPassword(password) => {
return bcrypt.compareSync(password, this.password);
};
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
return User;
};
I don't think this will help you but I can see that you have a syntax error in your codes, try adding , before
sequelize, modelName: 'User'
{
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
},
sequelize,
modelName: 'User',
}
My login script was working fine but only for one record, all the rest hang and do not return not quite sure why?
It reutrns the email and password from my console.log but not the console.log of the user. Also can confirm that the users email does exist in the db
Below is my basic setup.
User model
var mongoose = require("mongoose");
var bcrypt = require("bcrypt");
var passwordString = require("../helper/generateStrongRandomString");
var schema = new mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
validate: {
validator: function (v) {
return /^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/.test(
v
);
},
message: (props) => `${props.value} is not a valid email address`,
},
},
organisation: {
ref: "organisation",
type: mongoose.Schema.Types.ObjectId,
required: true,
},
client: {
ref: "client",
type: mongoose.Schema.Types.ObjectId,
required: false,
},
forename: {
type: String,
},
surname: {
type: String,
},
password: {
type: String,
default: null,
},
created: {
type: Date,
default: Date.now(),
},
updated: {
type: Date,
default: null,
},
passwordResetString: {
type: String,
default: null,
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
schema.pre("save", function (next) {
var user = this;
if (!user.isNew) {
user.updated = Date.now();
}
if (user.isNew && !user.password) {
user.passwordResetString = passwordString();
}
if (!user.isModified("password")) {
return next();
}
bcrypt
.hash(user.password, 12)
.then((hash) => {
user.password = hash;
next();
})
.catch((err) => console.log(err));
});
schema.pre("save", function (next) {
var user = this;
user.updated = Date.now();
next();
});
function autopopulate(next) {
this.populate("organisation");
this.populate("client");
next();
}
schema.pre("find", autopopulate);
schema.pre("findOne", autopopulate);
var User = new mongoose.model("user", schema);
module.exports = User;
Login route
const router = require("express").Router();
const passport = require("passport");
const catchErrors = require("../middlewares/catchErrors");
router.post(
"/login",
passport.authenticate("local"),
catchErrors(async (req, res) => {
console.log(req.body);
// console.log(req);
})
);
module.exports = router;
Passport js local authentication
passport.use(
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async function (email, password, cb) {
console.log(email, password);
return User.findOne({ email })
.then((user) => {
console.log(user);
if (!user || !bcrypt.compareSync(password, user.password)) {
console.log(26);
return cb(null, false, {
message: "incorrect email or password",
});
}
return cb(null, user, {
message: "Logged in successfully",
});
})
.catch((err) => console.log(err));
}
)
);
For anyone else who stumbles across this, I figured it out. My problem was with my autopopulate and the organisation model not shown above see code below.
function autopopulate(next) {
this.populate("organisation");
this.populate("client");
next();
}
This loads and populates the organisation as part of the user data however my organisation was doing the exact same thing when it was loaded on the user and causing an infinite loop.
Lots of commenting out of code and checking to confirm.
Look out for infinite loops.
I want to understand how sequelize instance methods works and if its possible to use returned object as instance for further usage. Basically I'm just selecting user by its user name, later I want to compare if password matches and if so - update data. But the error says
Unhandled rejection TypeError: user_data.validPassword is not a function
and I'm not even close to instance update..
my User model:
const bcrypt = require('bcryptjs');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_name: DataTypes.STRING,
user_password: DataTypes.STRING,
user_token: DataTypes.STRING,
user_alias_name: DataTypes.STRING,
}, {
tableName: 'oc_users',
instanceMethods: {
generateHash(password) {
return bcrypt.hash(password, bcrypt.genSaltSync(8));
},
validPassword(password) {
return bcrypt.compare(password, this.password);
}
}
});
return User;
};
my method:
...
loginAttempt(cookie) {
return models.User.findOne({
attributes: ['id', 'user_password', 'user_alias_name'],
where: {user_name: this.user}
}).then(user_data => {
if (!user_data) return 'No matching results for such a user';
return user_data.validPassword(this.password).then(result => {
if (result !== true) return 'Invalid password for selected user';
return this.updateAfterLogin(user_data, cookie);
})
})
}
updateAfterLogin(user, cookie) {
return user.update({
user_token: cookie
}).then(()=> {
return {data: 'updated'};
})
}
...
It depends on which version of sequelize you're using and probabily you're using Sequelize v4. On Sequelize v4 classMethods and instanceMethods were removed from sequelize.define.
You may check it at oficial docs for more informations:
http://docs.sequelizejs.com/manual/tutorial/upgrade-to-v4.html#config-options
Removed classMethods and instanceMethods options from sequelize.define. Sequelize models are now ES6 classes. You can set class / instance level methods like this
Old
const Model = sequelize.define('Model', {
...
}, {
classMethods: {
associate: function (model) {...}
},
instanceMethods: {
someMethod: function () { ...}
}
});
New
const Model = sequelize.define('Model', {
...
});
// Class Method
Model.associate = function (models) {
...associate the models
};
// Instance Method
Model.prototype.someMethod = function () {..}
In my node app i am using sequelize as an ORM for my postgresql database.The following is my model:
User:
var bcrypt = require('bcrypt'),
crypto = require('crypto');
var authTypes = ['github', 'twitter', 'facebook', 'google'];
var map_attributes = function() {
var obj = new Object(),
ctx = this;
ctx.attributes.forEach(
function(attr) {
obj[attr] = ctx[attr];
});
return obj;
};
var validatePresenceOf = function(value) {
return value && value.length;
};
module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
name: {
type: DataTypes.STRING,
validate: {
len: {
args: 1,
msg: "Name cannot be blank"
}
}
},
email: {
type: DataTypes.STRING,
validate: {
len: {
args: 1,
msg: "email cannot be blank"
}
}
},
username: {
type: DataTypes.STRING,
validate: {
len: {
args: 1,
msg: "username cannot be blank"
}
}
},
provider: DataTypes.STRING,
//hashed_password: String,
hashed_password: {
type: DataTypes.STRING,
set: function(v) {
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync(v, salt);
this.setDataValue('hashed_password', hash);
}
},
salt: DataTypes.STRING,
/*facebook: {},
twitter: {},
github: {},
google: {}*/
}, {
hooks: {
beforeValidate: function(next) {
if (!this.isNew) return next();
if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) next(new Error('Invalid password'));
else next();
}
}
}, {
instanceMethods: {
authenticate: function(plainText) {
return this.encryptPassword(plainText) === this.hashed_password;
},
makeSalt: function() {
return Math.round((new Date().valueOf() * Math.random())) + '';
},
encryptPassword: function(password) {
if (!password) return '';
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
}
}
});
}
In this model i have to insert data into my database.How can i achieve this. I found this tutorial http://sequelizejs.com/docs/1.7.8/instances but its different from my design , here i am exporting the total model.Thanks in advance.
I tried for this:
index.js:
var Sequelize = require('sequelize');
var util = require('util');
var config = require('config').dbpostgres; // we use node-config to handle environments
// initialize database connection
var sequelize = new Sequelize(
config.dbname,
config.username,
config.password, {
dialect: 'postgres'
});
//var sequelize = new Sequelize('postgres://postgres:postgres#localhost:5432/Geocode', { dialect: 'postgres', protocol: 'postgres' });
// load models
var models = ['user'] //,'sciSphereModel', 'access_token', 'oauth_client', 'request_token', 'article'];
models.forEach(function(model) {
//console.log(model)
var sequelizeModel = sequelize.import(__dirname + '/' + model);
//console.log("seq=" + util.inspect(sequelizeModel));
//sequelizeModel.sync();
/*sequelizeModel.sync().success(function() {
console.log('DB error');
}).error(function(error) {
console.log(error);
})*/
sequelizeModel.sync();
//console.log("sequelizeModel=" + util.inspect(sequelizeModel))
var user = sequelizeModel.build({
name: 'kumar',
email: 'kumar#gmail.com',
username: 'kumar007',
provider: 'local',
hashed_password: 'tyegnaak',
salt: 'uopioann'
})
user.save().complete(function(err) {
console.log("Inside user save");
if ( !! err) {
console.log('The instance has not been saved:', err)
}
else {
console.log('We have a persisted instance now')
}
})
module.exports[model] = sequelizeModel; //Implement sync
});
// export connection
//sequelize.sync();
module.exports.sequelize = sequelize;
app.set('sequelize', sequelize);
Whether i am doing right?.Is this the way to achieve this?
Wouldn't it be sequelize.sync(); not sequelizeModel.sync(); because you'd want to syncronize any model changes with the db. You can also add force to your sync in case the table doesn't match your model.
sequelize.sync({ force: true }); // use to drop before create
I have never tried sync on a model, just create(), save(), find(), updateAttributes() etc.