sequelize.js custom validator, check for unique username / password - javascript

Imagine I have defined the following custom validator function:
isUnique: function () { // This works as expected
throw new Error({error:[{message:'Email address already in use!'}]});
}
However, when I attempt to query the DB I run into problems:
isUnique: function (email) { // This doesn't work
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function () { // This gets called
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
});
}
How can I query the ORM in a custom validator and trigger a validation error based on the response from the ORM?

You can verify if the email already exists like that:
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail:true
},
unique: {
args: true,
msg: 'Email address already in use!'
}
}

Here's a simplified sample of a functioning isUnique validation callback (works as of SequelizeJS v2.0.0). I added comments to explain the important bits:
var UserModel = sequelize.define('User', {
id: {
type: Sequelize.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true
},
email: {
type: Sequelize.STRING,
validate: {
isUnique: function(value, next) {
UserModel.find({
where: {email: value},
attributes: ['id']
})
.done(function(error, user) {
if (error)
// Some unexpected error occured with the find method.
return next(error);
if (user)
// We found a user with this email address.
// Pass the error to the next method.
return next('Email address already in use!');
// If we got this far, the email address hasn't been used yet.
// Call next with no arguments when validation is successful.
next();
});
}
}
}
});
module.exports = UserModel;

With Sequelize 2.0, you need to catch Validation Errors.
First, define the User Model with a custom validator:
var User = sequelize.define('User',
{
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({where: {email: value}})
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
other_field: Sequelize.STRING
});
module.exports = User;
Then, in the controller, catch any Validation Errors:
var Sequelize = require('sequelize'),
_ = require('lodash'),
User = require('./path/to/User.model');
exports.create = function (req, res) {
var allowedKeys = ['email', 'other_field'];
var attributes = _.pick(req.body, allowedKeys);
User.create(attributes)
.then(function (user) {
res.json(user);
})
.catch(Sequelize.ValidationError, function (err) {
// respond with validation errors
return res.status(422).send(err.errors);
})
.catch(function (err) {
// every other error
return res.status(400).send({
message: err.message
});
});

Success callback is called even if no user is found. You have to check if the function passes a user as an argument:
isUnique: function (email) {
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function (u) { // This gets called
if(u){
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
}
});
}

Define the User Model with a custom validator:
const { DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const UserModel = sequelize.define('user', {
id: {
type: DataTypes.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isUnique: (value, next) => {
UserModel.findAll({
where: { email: value },
attributes: ['id'],
})
.then((user) => {
if (user.length != 0)
next(new Error('Email address already in use!'));
next();
})
.catch((onError) => console.log(onError));
},
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
},
});
module.exports = UserModel;

Related

How to create model with association M-N via Sequelize

Stack: NodeJs, PostgreSQL, Sequelize, Express.
I have two associated models (User and Role) through User_Roles.
When I create new user via Sequelize.create(), how can i fill the User_Roles table to define which roles (i get the array filled with role ID) the user have. The roles are already defined in table (roles)
The models:
const User = sequelize.define('users', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
email: { type: DataTypes.STRING(320), allowNull: false, unique: true, isEmail: true },
});
const Role = sequelize.define('roles', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
title: { type: DataTypes.STRING(64), allowNull: false, unique: true },
});
const User_Roles = sequelize.define('user_roles', {}, { timestamps: false });
// Associations
User.belongsToMany(Role, { through: User_Roles });
Role.belongsToMany(User, { through: User_Roles });
UserController:
async create(req, res, next) {
const { email, roles } = req.body;
if (!email) {
return next(ApiError.badRequest('Invalid request'));
}
if (!Array.isArray(roles) || !roles.length) {
return next(ApiError.badRequest('Invalid request'));
}
const isExistThisEmail = await User.findOne({ where: { email } });
if (isExistThisEmail) {
return next(ApiError.badRequest('The email already exists'));
}
const user = await User.create({ email });
return res.status(201).json(createdUser);
}
You can use dynamically added methods (according to associations) in the model:
await user.setRoles([1,2,3]) // or indicate the roles instances
See special methods/mixins

bcrypt compare method is returning false even if I used the same string when I hashed it

When I use the same password I hashed and stored in database as a parameter in compare method I get false. Really don't know whats the problem here:
router:
router.post("/users/login", async (req, res) => {
try {
const user = await User.findByCredentials(
req.body.email,
req.body.password
);
const token = await user.generateAuthToken();
console.log("Route token -->", token);
res.send({ user, token });
} catch (e) {
res.status(400).send(errors.e400);
}
});
Method for email and password validation ( calling compare method here):
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email });
if (!user) throw new Error("User does not exist.");
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw new Error("Invalid password.");
return user;
};
password hashing:
userSchema.pre("save", async function () {
try {
this.password = await bcrypt.hash(this.password, 8);
} catch (e) {
console.log(e);
}
});
user schema:
const userSchema = new mongoose.Schema({
username: {
type: String,
trim: true,
required: true,
unique: true,
},
email: {
type: String,
trim: true,
required: true,
unique: true,
validate(value) {
if (!validator.isEmail(value))
throw new Error("Email address is not valid.");
},
},
password: {
type: String,
trim: true,
required: true,
minLength: 7,
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
});

Can't push items into mongodb arrays

I'm trying to make a simple social media app using react, express and mongodb.
This is the user model:
const UserSchema = new mongoose.Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true},
followers: { type: Array, required: false },
following: { type: Array, required: false },
likes: { type: Array, required: false},
},
{ collection: 'users' }
)
This is the express server:
app.post('/api/follow', async (req, res) => {
const {token, username} = req.body
if (token === null)
{
return res.json({status: 'error'})
}
const user = await User.findOne({username}).lean()
const _visitor = jwt.verify(token, JWT_SECRET)
const visitor = await User.findOne({username: _visitor.username})
if (!user)
{
return res.json({status: 'error', error: 'User not found.'})
}
if (!visitor)
{
return res.json({status: 'error', error: 'User not found.'})
}
visitor.following.push(user._id)
user.followers.push(me._id)
return res.json({status: 'ok'})
})
But when I check the mongodb compass the following and followers arrays are empty.
The best way is to use findOneAndUpdate() method to update a value.
Also, if you are updating from two different collections you can use transactions. This is optional but can be useful to avoid inconsitences in your DB.
So your code can be something similar to this:
const updateVisitor = await User.findOneAndUpdate(
{
username: _visitor.username
},
{
$push:{
following: user._id
}
})
Example here
An the same code for user:
const updateUser = await User.findOneAndUpdate(
{
username: username
},
{
$push:{
followers: me._id
}
})

MongoDB only updates partially

My model has "id", "liked", "likedBy" and "matched" fields.
I can update my database and add id according to my hypotethical likes; it stores target's id to my current user's liked field, current user's id to target's likedBy field.
I'm trying to achieve, if a user has both liked and likedBy id matching then put liked id to my matched field on both users, but I can't for some reason. It just doesn't care if statement there.
Any ideas why?
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
}
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
//check matching
if (user.likedBy === user.liked) {
user.matched.unshift({ user: req.user.id });
}
await user.save();
await loggedUser.save();
res.status(200).send("Liked!");
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
My Schema:
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
picture: {
data: Buffer,
contentType: String,
},
age: {
type: Number,
required: true,
},
gender: {
type: String,
required: true,
},
job: {
type: String,
required: true,
},
desc: {
type: String,
default: "Hasn't written anything yet.",
},
liked: [{}],
likedBy: [{}],
matched: [{}],
});
module.exports = User = mongoose.model("user", UserSchema);
I found the mistake I made.
I'm trying to compare objects, which isn't possible really. I got index of my array then extracted the data I need and stored it into value1 & value2.
I found my mistake the moment I console.log'ed my conditions as below:
if(console.log(user.liked) === console.log(user.likedBy)){
...}
Working version:
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
} else {
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
const value1 = user.likedBy[0].user;
const value2 = user.liked[0].user;
if (value1 === value2) {
user.matched.unshift({ user: req.user.id });
loggedUser.matched.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
res.status(200).send("Liked & Matched!");
} else {
res.status(200).send("Liked!");
}
}
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});

mongoose schema method returning undefined

I want to create a method that validates the user's password by using bcrypt.compare()
here is the code below.
UserSchema.methods.validatePassword = async (data) => {
console.log(this.email); // returns undefined
console.log(this.first_name); // returns undefined
return await bcrypt.compare(data, this.password);
};
here is the UserSchema I created
const UserSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }
);
when getting this.password in my schema .pre('save', ..) it works but shows undefined when I use schema methods. :(
here is the implementation of the method
const verifySignIn = async (req, res, next) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({
status: 'failed',
message: 'User Not found.',
});
}
const isValid = await user.validatePassword(password);
if (!isValid) {
return res.status(401).send({
message: 'Invalid Password!',
data: {
user: null,
},
});
}
next();
} catch (err) {
Server.serverError(res, err);
}
};
In the guide it says:
Do not declare methods using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so your method will not have access to the document ...
So in this case, you just need to change UserSchema.methods.validatePassword = async (data) => {... to UserSchema.methods.validatePassword = async function(data) {...

Categories

Resources