I'm new to node, and learning all about promises, and pg-promise specifically. This is what I want to do using Express and pg-promise:
check email,
if not found check username,
if not found create a new user.
return user id
I've got my repo set up (db.users) which does the sql which is working great.
In my authorization handler I'm not sure how to make the repo calls follow one another. The syntax seems clunky to me. Here's what I have so far:
exports.signup = function( req, res, next ) {
const username = req.body.username;
const email = req.body.email;
const password = req.body.password;
// See if a user with the given email exists
db.users.byEmail({email: email})
.then(user => {
if (user) {
return res.status(422).send({ error: 'Email is in use'});
} else {
return null; <-- must I return something here?
}
})
.then(() => {
db.users.getUsername({username: username})
.then(user => {
if (user) {
return res.status(422).send({ error: 'Email is in use'});
} else {
return null; <-- must I return something here?
}
...etc
})
Maybe pg-promises don't chain together like this? Should they be nested within one another or maybe be completely separate blocks? Also, not quite sure where the catch goes. I've tried every way I can think of, following various tutorials, but I get errors like 'can't set headers that already set' and 'promises aren't being returned'. If any kind person can guide me here, I'd really appreciate it.
You must use a task when executing multiple queries, so they can share one connection, or else the connection management will suffer from performance issues.
db.task(t => {
return t.users.byEmail({email})
.then(user => {
return user || t.users.getUsername({username});
});
})
.then(user => {
if (user) {
res.status(422).send({error: 'Email is in use'});
} else {
// do something else
}
})
.catch(error => {
// process the error
});
It will work well, presuming that your repositories were set up as shown in pg-promise-demo.
The previous 2 answers were really bad advise, and completely against what pg-promise says you should do. See Tasks versus root/direct queries.
And when doing multiple changes, you would usually use a transaction - use tx instead of task.
With guidance from vitaly-t I altered his answer to include separate error messages depending on which thing failed. I also moved up the next "then" into the transaction block to use the "t" of the transaction for the next step which creates user. Much thanks for vitaly-t!
db.task(t => {
return t.users.byEmail({email})
.then(user => {
if (user) {
throw new Error('Email is in use');
} else {
return t.users.byUsername({username});
}
})
.then((user) => {
if (user) {
throw new Error('Username is taken');
} else {
return t.users.addNew({username, email, password});
}
})
})
.then(user => {
res.json({token: tokenForUser(user), username: user.username, aCheck: user.is_admin});
})
.catch(error => {
res.status(422).json({ 'error': error.message});
});
Checking username and email existence is independent of each other. I think Promise.all() will suit your need more than sequentially chaining promise.
exports.signup = function (req, res, next) {
const username = req.body.username;
const email = req.body.email;
const password = req.body.password;
Promise.all([
db.users.byEmail({
email: email
}),
db.users.getUsername({
username: username
})
]).then((results)=> {
if (results[0] || results[1]) {
return res.status(422).send({
error: 'Email is in use' // same error msg as per your snippet
});
}
// here, code for creating a new user
});
};
Related
I used MVC to make a NodeJS server and this is one of the controllers:
module.exports.create_user = async function (req, res) {
// console.log(req.body);
// console.log(req.user);
await Company.findOne({ user: req.body.user }, function (err, user) {
if (user) {
return res.redirect('/login');
}
else {
if (req.body.password == req.body.confirm_password) {
Company.create({
"country": req.body.country,
"username": req.body.user,
"password": req.body.password
});
}
else {
console.log('Passwords didnt match');
}
}
});
req.session.save(() => {
return res.redirect('/profile');
})
}
What this code supposed to do?
It searches if a user already exists; if yes, it will redirect to /login.
If no such user exists, it should create a new user and redirect to /profile.
What does this code do?
Regardless of whether the user exists or not, the code always redirects to /login. Also, a user is created in the database, so every time a new user wants to signup, the user needs to signup and then go to sign in to get access to /profile
What is the problem here which doesn't allow redirect to /profile? And how to fix it?
Let me know if you need anything else
Use username instead of user to find a user
Company.findOne({ username: req.body.user });
You are mixing callback style with async/await, await keyword does not affect on your, it will not wait until the query finished. await keyword just working when you wait for a Promise like object (then able object).
I guess you are using mongoose, the current version of mongoose supports Promise return style.
module.exports.create_user = async function (req, res) {
// console.log(req.body);
// console.log(req.user);
try {
// Use `username` instead of `user` to find a user
const user = await Company.findOne({ username: req.body.user }); // callback is not passed, it will return a Promise
if (user) {
return res.redirect('/login');
}
if (req.body.password == req.body.confirm_password) {
await Company.create({ // wait until user is created
"country": req.body.country,
"username": req.body.user,
"password": req.body.password
});
// then redirect page
req.session.save(() => {
return res.redirect('/profile');
});
} else {
console.log('Passwords didnt match');
// what happen when password didn't match
// return res.redirect('/login'); ???
}
} catch (error) {
// something went wrong
// return res.redirect('/login'); ???
}
}
passport.checkAuthentication = async function (req, res, next) {
console.log(req.user);
let auth_status = await req.isAuthenticated() ? "sucess" : "failure";
console.log(`Authentication ${auth_status}`);
// if the user is signed in, then pass on the request to the next function(controller's action)
if (await req.isAuthenticated()) {
return next();
}
// if the user is not signed in
return res.redirect('/login');
}
I did a but more work on this and possibly the controller is working fine and the problem could be in middleware. In the signup case discussed above, the middelware always logs 'Authentication failure' to console.
I have an API that simply logs in a user. I am testing out certain test cases for when the username or password are invalid. For some reason, I can't detect the thrown error is not being returned to the API.
The API looks like this:
// routes/users.js
router.post('/users/login', async (req, res) => {
//Login a registered user
try {
const { email, password } = req.body
const user = await User.findByCredentials(email, password)
if (!user) {
return res.status(401).send({ error: 'Login failed! Check authentication credentials' })
}
const token = await user.generateAuthToken()
res.send({ user, token })
} catch (error) {
res.status(400).send(error)
}
})
And here is the method in the model that should return the error. Using, the debugger I can step through the code and it looks like all the statements are being executed but the error is returned as an empty object as such, Error: [object Object]
// models/user.models.js
userSchema.statics.findByCredentials = async (email, password) => {
// Search for a user by email and password.
const user = await User.findOne({ email} )
if (!user) {
throw new Error({ error: 'Invalid user name' })
}
const isPasswordMatch = await bcrypt.compare(password, user.password)
if (!isPasswordMatch) {
throw new Error({ error: 'Invalid password' })
}
return user
}
Looking at the code, I don't think (!user) should be considered an error, as the query just simply found no user record that matches the query condition. As far as handling the real error, try below:
If you want to test this, try:
User.findOne({email}, function(err, user) {
if (err) {
//true error
} else {
if (!user) {
//this is when no user matching the email was found
} else {
//user matching email was found
}
}
}
Because there was no runtime error, there would be no error object in the case the no user was found:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
So this seems to do the trick. Not sure why this works and the original approach did not.
The difference is basically a user defined function handling the error message.
userSchema.statics.findByCredentials = async (email, password) => {
// Search for a user by email and password.
const user = await User.findOne({ email })
function myError(message) {
this.message = message
}
myError.prototype = new Error()
if (!user) {
throw new myError('Username provided is incorrect, please try again')
}
const isPasswordMatch = await bcrypt.compare(password, user.password)
if (!isPasswordMatch) {
throw new myError('Password provided is incorrect, please try again')
}
return user
}
I've have had a working version of mongoose instance methods working before. I'm not sure what is different about it this time around. The only thing I have done differently this time is that I separated the mongoose connection function outside of the server.js file into a config file that will be imported and call the connect() function.
I will mostly use this instance method in passport with the local strategy to log in the user. When I go to call my instance method on the user instance that was found by the previous UserModel.findOne({ email }) the verify(password) instance method is not called and does not throw any errors.
For testing purposes, I've tried to hard code a UserModel.findOne() right into connection field and I do get a user back. I then decided to call my instance method right off of the returned user instance named verify().
I have also attempted changing the name of the method to comparePassword, I've tried testing with statics to check if it is even called at all (which it wasn't), I've also tried to research other ways to import my schemas and models, and that does not seem to work. I have experimented with Async/Await hasn't changed the output
File: mongo.db.js
const connect = () => {
return new Promise((resolve, reject) => {
mongoose.connect(
config.get('DB.STRING'),
{ useCreateIndex: true, useNewUrlParser: true },
async (err) => {
if (err) reject(err)
resolve()
// TESTING INSTANCE METHODS
await mongoose.connection
.collection('users')
// HARD CODED TEST EMAIL
.findOne({ email: 'braden_feeney#hotmail.com' }, (err, result) => {
if (err) reject(err)
console.log(result)
console.log(result.verify('test1234'))
})
},
)
})
}
const close = () => {
return mongoose.disconnect()
}
export default { connect, close }
File: passport.config.js
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
// Find the user given the email
const user = await User.findOne({ email })
// If not found
if (!user) return done(null, false)
// Check if the password is correct
const isValididated = await user.verify(password)
// If not matched
if (!isValididated) return done(null, false)
// Return the user
done(null, user)
} catch (error) {
done(error, false)
}
},
),
)
File: users.model.js
const UserSchema = new Schema(
// HIDDEN FOR SECURITY
{ ... },
{ versionKey: false, timestamps: true },
)
// HIDDEN FOR SECURITY - PRE SAVE WORKS AS EXPECTED
UserSchema.pre('save', async function(next) { ... })
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = function(password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
export default model('User', UserSchema)
When I call user.verify(password) I expect to see a Boolean value either get returned from the function.
The actual result is an Error thrown stating
TypeError: user.verify is not a function
This section does not make sense:
async (email, password, done) => {
try {
const user = await User.findOne({ email })
if (user) // <-- if you have a user record
return done(null, false) // <-- you return!
// There was no valid user ... how would there be user.verify?
const isValididated = await user.verify(password)
if (!isValididated) return done(null, false)
done(null, user)
} catch (error) {
done(error, false)
}
}
You seem to return on valid user but calling user.verify when you do not have a user. So it seems your flow is somewhat problematic.
After tinkering around with how mongoose creates instance methods, I tried using another way to make the method work. I had to wrap the bcrypt.compare() in a promise because my code wasn't waiting for a response.
File: users.model.js
UserSchema.method('verify', function(password) {
return new Promise((resolve, reject) => {
bcrypt.compare(password, this.password, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
})
I still prefer the way that is mentioned in my question because I believe it looks cleaner and is not relying on a string to be the method name.
All you have to do is just mark this function with "async".
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = async function (password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
& Another thing that you've to keep in mind is that when you're retrieving user from your DB make sure to use the "await" keyword there as well.
I was getting the same error and then I noticed that I forgot to use the "await" keyword and my code goes to the next line without even waiting for the "user" to arrive from DB and we don't have a user or instance and we're calling "compare" method on that instance which hasn't arrived from DB.
That's why you were getting error that comparePassword is not a function.
Basically I want store a user inside the database, before doing this operation I need to validate the fields of that user. So I have this structure:
exports.save = function(req, res)
{
let errors = validate(req.body);
if(!errors.length)
{
//add user
res.status(200).send(true);
}
else{
res.status(500).send("oh bad");
}
};
function validate(user)
{
let errors = [];
if (User.findOne({ email: user.email })) {
errors.push({ msg: 'Email already registered!' });
}
}
the code above simply cannot works because NodeJS handle the operation asynchronous. I can fix this "problem" doing something like:
User.findOne({ email: email }).then(user => {
if (!user) {
//add user
res.status(200).send(true);
});
but I want add the checking inside the function validate, is there a way to do that? Sorry if this question could be stupid, but I'm new to NodeJS
Since nothing's inherently making exports.save synchronous, it's probably easiest to make validate asynchronous (and let's also make it modern by using promises and async).
exports.save = async function(req, res) {
const errors = await validate(req.body);
if (!errors.length) {
//add user
res.status(200).send(true);
} else {
res.status(500).send("oh bad");
}
};
async function validate(body) {
const errors = [];
const user = await User.findOne({ email: body.email });
if (user) {
errors.push({ msg: "Email already registered!" });
}
return errors;
}
Error: data and hash arguments required
i am doing simple, login signup and forgot password in node js using
bcrypt hash
code : for login
app.post('/login', (req, res) => {
console.log('login');
let {email, password} = req.body;
User.updateOne({email: email}, ' email password', (err, userData) => {
if (!err) {
let passwordCheck = bcrypt.compareSync(password, userData.password);
if (passwordCheck) {
console.log('login2');
req.session.user = {
email: userData.email,
id: userData._id
};
req.session.user.expires = new Date(Date.now() + 3 * 24 * 3600 * 1000);
res.status(200).send('You are logged in, Welcome!');
} else {
res.status(401).send('incorrect password');
console.log('login3');
}
} else {
res.status(401).send('invalid login credentials');
console.log('login4');
}
});
});
code for signUp :
app.post('/signup', (req, res) => {
let {email, password} = req.body;
let userData = {password: bcrypt.hashSync(password, 5, null), email };
console.log('out save');
let newUser = new User(userData);
newUser.save().then(error => {
if (!error) {
console.log('in save');
return res.status(200).json('signup successful');
} else {
if (error.code === 11000) {
return res.status(409).send('user already exist!');
} else {
console.log(JSON.stringigy(error, null, 2));
return res.status(500).send('error signing up user');
}
}
});
});
i have tried console logging few lines and turned out that the code doesn't go into signup
newUser.save();
tell me where i'm going wrong
The issue is with this line newUser.save().then(error => {. Do you notice the .then(). That is a resolved promise so it wouldn't be returning an error. Typically you would see something like this.
Promise()
.then((result) => {
// result is a resolved promise
})
.catch((error) => {
// error is a rejected promise
})
So you should try changing your code to this:
newUser.save()
.then(result => {
console.log('in save')
return res.status(200).json('signup successful')
})
.catch(error => {
if (error.code === 11000) {
return res.status(409).send('user already exist!')
} else {
console.log(JSON.stringigy(error, null, 2))
return res.status(500).send('error signing up user')
}
})
It looks like you're using mongoose, here is the API docs for Document.prototype.save() https://mongoosejs.com/docs/api.html#document_Document-save
Their documentation uses callback functions for the most part but if you scroll to the end of the .save() documentation you will see they show one example with a promise.
bcrypt.compareSync takes 2 parameters; passwordToCheck, passwordHash
You are getting error "bcrypt Error: data and hash arguments required"
This error means one or both parameters are either null or undefined,
In your case you need to make sure that password, userData.password are correctly going in function bcrypt.compareSync