In a hook I want to confirm whether a password has changed before executing encryption process.
Mongoose has a function "isModified" and I believe Sequelize's "changed" function servers the same purpose.
I cannot get the "changed" function to work. I am looking for an example of how it is used.
*******Snippet of code
{
hooks: {
beforeCreate: async (user) => {
if (changed([user.password]) === false) return next();
const salt = await bcrypt.genSalt(12);
user.password = await bcrypt.hash(user.password, salt);
user.passwordConfirmed = undefined;
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compare(password, this.password);
},
},
}
You must to use hook beforeUpdate with previous function. Try something like that:
let { Sequelize, DataTypes } = require('sequelize');
let sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'database.sqlite',
});
(async () => {
try {
await sequelize.authenticate();
console.log('connected');
let Person = sequelize.define('Person', { name: DataTypes.STRING, age: DataTypes.INTEGER });
Person.addHook('beforeUpdate', (person) => {
if (person.previous('name') != person.name) {
console.log('name changed', person.previous('name'), person.name);
}
});
await Person.sync({ force: true });
await Person.create({ name: 'John', age: 30 });
let [person] = await Person.findAll();
person.name = 'Paul';
await person.save();
} catch (error) {
console.error('Unable to connect to the database:', error);
}
})();
Related
I have a function to check if a user exists, and a function to create a new user in my User model.
What I want to do is call them in the router to check if a user with the email adress in req.body already exists.
If it does, I want to return a message, and if not, I want to create the user.
When I try to call the route in Postman, I get this error in node console :
node_modules/express/lib/response.js:257
var escape = app.get('json escape')
TypeError: Cannot read properties of undefined (reading 'get')
User model :
const Sequelize = require("sequelize");
const connexion = require("../database");
const User = connexion.define(
"users",
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
email: {
type: Sequelize.STRING(100),
allowNull: false,
},
password: {
type: Sequelize.STRING(100),
allowNull: false,
},
},
{
freezeTableName: true
}
);
function checkUser(userEmail) {
const findUser = User.findOne({ where: { userEmail } }).catch((err) => {
console.log(err);
});
if (findUser) {
return res.json({ message: "Cette adresse email est déjà enregistrée" });
} else {
return false;
}
}
function createUser(userData) {
console.log(userData);
User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
module.exports = { createUser, checkUser };
user controller :
const createUser = require("../models/User");
const bcrypt = require("bcrypt");
const saltRounds = 10;
addUser = async (req, res) => {
try {
const userData = req.body;
console.log(req.body);
bcrypt.hash(userData.password, saltRounds, async function (err, hash) {
userData.password = hash;
const newUser = await createUser(req.body);
res.status(201).json({ newUser });
});
} catch (err) {
console.log(err);
res.status(500).json("Server error");
}
};
module.exports = addUser;
user router :
const express = require("express");
const router = express.Router();
const addUser = require("../controllers/userController");
const { checkUser } = require("../models/User");
router.post("/", async (req, res) => {
const { email } = req.body;
const alreadyExists = await checkUser(email);
if (!alreadyExists) {
addUser(req.body);
}
});
module.exports = router;
EDIT : Finally I'm trying a more simple way. I will do the check part directly into the createUser function.
But now, it creates the user even if the email already exists ^^
async function createUser(userData) {
console.log(userData);
const findUser = await User.findOne({ where: userData.email }).catch(
(err) => {
console.log(err);
}
);
findUser
? console.log(findUser)
: User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
i think the problem is with this part you are trying to use res but it doesn't exist in your checkUser function
if (findUser) {
return res.json({ message: "Cette adresse email est déjà enregistrée" });
} else {
return false;
}
try this instead
if (findUser) {
return true });
} else {
return false;
}
UPDATE to fix the problem of user creation if it already exists
async function createUser(userData) {
console.log(userData);
const findUser = await User.findOne({ where: userData.email }).catch(
(err) => {
console.log(err);
}
);
if(!findUser){
findUser
? console.log(findUser)
: User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
}
Problem solved by doing this (thanks super sub for your help):
async function createUser(userData) {
console.log(userData);
const email = userData.email;
const findUser = await User.findOne({ where: { email } }).catch((err) => {
console.log(err);
});
if (!findUser) {
User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
}
I am taking password input from the user and encrypting the password using crypto, then saving into the database. This is my code, here I am storing the encrypted password into the encry_password property that comes from the userSchema. But, this is giving me error that "this.securePassword" is not a function.
const mongoose = require("mongoose");
const crypto = require("crypto");
const { v1: uuidv1 } = require("uuid");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
maxlength: 32,
trim: true,
},
lastname: {
type: String,
maxlength: 32,
trim: true,
},
email: {
type: String,
trim: true,
required: true,
unique: true,
},
usrinfo: {
type: String,
trim: true,
},
encry_password: {
type: String,
required: true
},
salt: String,
role: {
type: Number,
default: 0,
},
purchases: {
type: Array,
default: [],
},
}, { timestamps: true });
userSchema.virtual("password")
.set((password) => {
this._password = password;
this.salt = uuidv1();
this.encry_password = securePassword(password, uuidv1());
console.log(this.encry_password);
})
.get(() => {
return this._password;
});
// const authenticate = function (plainPassword, encry_password) {
// return securePassword(plainPassword) === encry_password;
// };
const securePassword = function (plainPassword, salt) {
if (!plainPassword) return "";
try {
return crypto.createHmac("sha256", salt).update(plainPassword).digest("hex");
} catch (error) {
return "";
}
};
module.exports = mongoose.model("User", userSchema);
Route for user signup
exports.signup = (req, res) => {
console.log(req.body);
const user = new User(req.body);
user.save((err, user) => {
if (err) {
console.log(err);
res.status(400).json({
err: "Note able to save the user in database"
});
} else {
res.json(user);
}
});
};
first of all, in this situation you shouldn't use virtual
Virtuals
Virtuals are document properties that you can get and set but that do not get persisted to MongoDB. The getters are useful for formatting or combining fields, while setters are useful for de-composing a single value into multiple values for storage.
but in the scope of virtual, this cannot access to method, you can not access to the method like your manner, it's a example of method usage in mongoose
const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ type: 'dog' });
dog.findSimilarTypes((err, dogs) => {
console.log(dogs); // woof
});
you can check the method documantation:
if you want just access to securePassword in your manner you can like this and delete method mongoose complately because this is not the place to use method:
UserSchema.virtual("password")
.set((password) => {
this._password = password;
this.salt = uuidv1();
console.log("This is running");
this.encry_password = securePassword(password, this.salt);
console.log(encry_password);
})
.get(() => {
return this._password;
});
const authenticate = function (plainPassword, encry_password) {
return securePassword(plainPassword) === encry_password;
};
const securePassword = function (plainPassword, salt) {
if (!plainPassword) return "";
try {
return crypto
.createHmac("sha256", salt)
.update(plainPassword)
.digest("hex");
} catch (error) {
return "";
}
};
if you want to create authenticate service, change your manner, and don't use virtual for password and use pre save
before saving information about users in db this tasks will be done
check the pre documentation
userSchema.pre("save", async function (next) {
try {
this.password = securePassword (plainPassword, salt);
} catch (error) {
console.log(error);
}
});
after created a hash password save informations like this :
const userSchema = new mongoose.Schema({
.
.
.
password: { //convert encry_password to password
type: String,
}
.
.
.
}, { timestamps: true });
//every time want to user save this method called
userSchema.pre('save', function (next) {
this.salt = uuidv1()
this.password = securePassword(this.password, this.salt)
next()
})
//for have a clean routes, you can create a static methods
userSchema.statics.Create = async (data) => {
let model = new User(data);
let resUser = await model.save(); //save your user
return resUser;
};
const securePassword = function (plainPassword, salt) {
if (!plainPassword) return "";
try {
return crypto.createHmac("sha256", salt).update(plainPassword).digest("hex");
} catch (error) {
return "";
}
};
let User = mongoose.model("User", userSchema)
module.exports = {User};
change controller like this :
let {User} = require("./path of user schema")
exports.signup = async (req, res) => {
try {
console.log(req.body);
const user = await User.create(req.body); //create a user
res.json(user);
} catch (error) {
console.log(err);
res.status(400).json({
err: "Note able to save the user in database",
});
}
};
NOTE : in req.body, name of password field, should be password
It looks like the scope of the securePassword function is defined inside userSchema, and you're trying to call it in userSchema.virtual.
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new mongoose.Schema({
email: {
type: string,
unique: true,
required: true,
trim: true
},
password: {
type: string,
required: true
},
authtokens: {
type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'AuthToken' }]
}
});
//hashing a password before saving it to the database
UserSchema.pre('save', function (next) {
if (this.isNew) {
bcrypt.gensalt(10, function(err, salt) {
if (err) return next(err);
bcrypt.hash(this.password, salt, null, function (err, hash){
if (err) return next(err);
this.password = hash;
console.log('user.password ', this.password);
next();
});
});
} else next();
});
I call this from a controller:
'use strict';
var mongoose = require('mongoose'),
User = mongoose.model('User'),
AuthToken = mongoose.model('AuthToken');
exports.createUser = function(req, res, next) {
if (req.body.email && req.body.password && req.body.passwordConf) {
var userData = {
email: req.body.email,
password: req.body.password,
passwordConf: req.body.passwordConf
};
//use schema.create to insert data into the db
User.create(userData, function (err, user) {
console.log('user created ', user.password);
if (err) {
return next(err);
} else {
return res.redirect('/profile');
}
});
} else {
var err = new Error("Missing parameters");
err.status = 400;
next(err);
}
};
When a createUser is called with email user#email.com, password password, I get the output:
user.password $2a$10$wO.6TPUm5b1j6lvHdCi/JOTeEXHWhYernWU.ZzA3hfYhyWoOeugcq
user created password
Also, looking directly in the database, I see this user with plain text password -> password.
Why is user having plaintext password in the database. How can I store the hash instead?
In short, you forgot you were going into a callback which has a different functional scope and you're still referring to this, which is at that time not actually the "model" instance.
To correct this, take a copy of this before you do anything like launching another function with a callback:
UserSchema.pre('save', function(next) {
var user = this; // keep a copy
if (this.isNew) {
bcrypt.genSalt(10, function(err,salt) {
if (err) next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) next(err);
user.password = hash;
next();
});
});
}
});
An alternate approach of course is to modernize things and use Promise results with async/await. The bcrypt library which is actually the "core" and not a fork does this right out of the box:
UserSchema.pre('save', async function() {
if (this.isNew) {
let salt = await bcrypt.genSalt(10);
let hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
});
Aside from the modern approach being generally cleaner code, you also don't need to change the scope of this since we don't "dive in" to another function call. Everything gets changed in the same scope, and of course awaits the async calls before continuing.
Full Example - Callback
const { Schema } = mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const uri = 'mongodb://localhost/crypto';
var userSchema = new Schema({
email: String,
password: String
});
userSchema.pre('save', function(next) {
var user = this; // keep a copy
if (this.isNew) {
bcrypt.genSalt(10, function(err,salt) {
if (err) next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) next(err);
user.password = hash;
next();
});
});
}
});
const log = data => console.log(JSON.stringify(data, undefined, 2));
const User = mongoose.model('User', userSchema);
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
await User.create({ email: 'ted#example.com', password: 'password' });
let result = await User.findOne();
log(result);
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Full Example - Promise async/await
const { Schema } = mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const uri = 'mongodb://localhost/crypto';
var userSchema = new Schema({
email: String,
password: String
});
userSchema.pre('save', async function() {
if (this.isNew) {
let salt = await bcrypt.genSalt(10);
let hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
});
const log = data => console.log(JSON.stringify(data, undefined, 2));
const User = mongoose.model('User', userSchema);
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
await User.create({ email: 'ted#example.com', password: 'password' });
let result = await User.findOne();
log(result);
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Both show the password correctly encrypted, since we actually set the value in the model instance:
{
"_id": "5aec65f4853eed12050db4d9",
"email": "ted#example.com",
"password": "$2b$10$qAovc0m0VtmtpLg7CRZmcOXPDNi.2WbPjSFkfxSUqh8Pu5lyN4p7G",
"__v": 0
}
I am following this graphql tutorial, everything was going ok until I try to use dataloaders.
My server.js is:
const start = async () => {
const mongo = await connectMongo();
const buildOptions = async req => {
const user = await authenticate(req, mongo.Users);
return {
context: {
dataloaders: buildDataloaders(mongo),
mongo,
user
},
schema
};
};
app.use('/graphql', bodyParser.json(), graphqlExpress(buildOptions));
app.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql',
passHeader: `'Authorization': 'bearer token-name#email.com'`
})
);
app.use('/', expressStaticGzip('dist'));
app.use('/attendance', expressStaticGzip('dist'));
app.use('/login', expressStaticGzip('dist'));
spdy.createServer(sslOptions, app).listen(process.env.PORT || 8080, error => {
if (error) {
console.error(error);
return process.exit(1);
} else {
console.info(
`App available at https://localhost:${process.env.PORT || 3000}`
);
}
});
};
My copy and paste dataloaders.js:
const DataLoader = require('dataloader');
async function batchUsers(Users, keys) {
return await Users.find({ _id: { $in: keys } }).toArray();
}
module.exports = ({ Users }) => ({
userLoader: new DataLoader(keys => batchUsers(Users, keys), {
cacheKeyFn: key => key.toString()
})
});
And my resolvers.js:
export default {
Query: {
allLinks: async (root, data, { mongo: { Links } }) =>
Links.find({}).toArray()
},
Mutation: {
createLink: async (root, data, { mongo: { Links }, user }) => {
const newLink = Object.assign({ postedById: user && user._id }, data);
const response = await Links.insert(newLink);
return Object.assign({ id: response.insertedIds[0] }, newLink);
},
createUser: async (root, data, { mongo: { Users } }) => {
const newUser = {
name: data.name,
email: data.authProvider.email.email,
password: data.authProvider.email.password
};
const response = await Users.insert(newUser);
return Object.assign({ id: response.insertedIds[0] }, newUser);
},
signinUser: async (root, data, { mongo: { Users } }) => {
const user = await Users.findOne({ email: data.email.email });
if (data.email.password === user.password) {
return { token: `token-${user.email}`, user };
}
}
},
Link: {
id: root => root._id || root.id,
postedBy: async ({ postedById }, data, { dataloaders: { userLoader } }) => {
return await userLoader.load(postedById);
}
},
User: {
id: root => root._id || root.id
}
};
When I try get my allLinks I got the error:
TypeError: The loader.load() function must be called with a value,but
got: undefined.
Can anyone help me?
So I was able to reproduce the error by creating a link with a user, deleting the user from the Mongo database, and then querying for the postedBy attribute of the Link.
I would suggest dropping all your links and recreating your user (register + sign in), creating a new link, then querying for the postedBy field.
I have the following code :
var ORM = require('../helpers/mysql_orm');
var log = require('../helpers/logger');
function UserModel() {
this.User = ORM.define('User', {
}, {
tableName: 'User',
timestamps: false,
});
}
UserModel.prototype.findOneByCredinitals = function (creditinals) {
this.User.findOne({
attributes:['username','id'],
where:
{
username:creditinals.principal,
password:creditinals.creditinal}
})
.then(function(user) {
console.log("inside then function"+user);
return user;
})
// console.log(result);
},
UserModel.prototype.findAllByLimit = function(req,res) {
}
module.exports= new UserModel;
//app.js
var result = User.findOneByCredinitals({principal: 'admin',creditinal:'danieladenew'});
console.log("returned "+result);
This the result from app.js
------------------------------------------------
returned undefined
inside then function User Sequelize Instance i.e Useriffound and displayed.
How do i return user object from then to app.js code please ?
You need to return the promise in order for the data to appear.
UserModel.prototype.findOneByCredential = function (credentials) {
return this.User.findOne({
attributes: ['username', 'id'],
where: {
username: credential.principal,
password: credential.credential
}
}).then(function(user) {
console.log("inside then function"+user);
return user;
}
});
},
Also, it might be a good idea to add a catch block. If the query fails, you will have no idea otherwise