Update existing user data in database - node.js

Currently, I have managed to the save user information inside my database. However I wanted to update the user information inside my database when they logged in if there is changes of information inside Steam database. So it is the same inside my database.
Below are example of information inside my User schema
const UserSchema = mongoose.Schema({
username:{
type: String,
},
profileURL:{
type: String,
},
profileImageURL:{
type: String,
},
steamId:{
type: String,
}
});
Below are example of my app.js. When the user login, I checked if the user steamId exist inside my database, I want to update the user information such as username, profileURL, profileImageURL and its steamID if exist else I create a new user inside my database. How can I achieve this? Currently, I just return done(null, user).
passport.use(new SteamStrategy({
returnURL: 'http://localhost:3000/auth/steam/return',
realm: 'http://localhost:3000/',
apiKey: ''
},
function (identifier, done){
var steamId = identifier.match(/\d+$/)[0];
var profileURL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=' + 'api_Key' + '&steamids=' + steamId;
User.findOne({ steamId: steamId}, function(err, user){
if(user){
return done(null, user);
}
else{
request(profileURL, function (error, response, body){
if (!error && response.statusCode === 200)
{
var data = JSON.parse(body);
var profile = data.response.players[0];
var user = new User();
user.username = profile.personaname;
user.profileURL = profile.profileurl;
user.profileImageURL = profile.avatarfull;
user.steamId = steamId;
user.save(function(err){
done(err, user);
});
}
else
{
done(err, null);
}
});
}
});
}));

You could do this with an upsert-enabled update call. Try something like this:
request(profileURL, function(err, response, body){
var data = JSON.parse(body);
var user = {
//... construct user object
}
User.findOneAndUpdate({ steamId: steamId }, user, {upsert: true, new: true, setDefaultsOnInsert: true}, function(err, newUser){
if (err) return handleError(err);
done(null, newUser);
});
});

Related

Trying to create new user

Trying to update steam user to User schema if exist, otherwise create new steam user in mongodb. However I got the error below. How can I solve this, am I doing it wrongly?
ValidationError: User validation failed: imageURL: Path `imageURL` is required., username: Path
`username` is required., profileURL: Path `profileURL` is required.
const User = require('./models/user');
User.findOne({ steamId: steamId}, function(err, user){
if(user){
return done(null, user);
}
else{
request(profileURL, function (error, response, body){
if (!error && response.statusCode === 200) {
var data = JSON.parse(body);
var profile = data.response.players[0];
var user = new User();
user.username = profile.personame;
user.profileURL = profile.profileURL;
user.profileImageURL = profile.avatarmedium;
user.steamId = steamId;
user.save(function(err){
done(err, user);
});
}
else{
done(err, null);
}
});
}
});
Removed require: true inside my schema

How to fix "TypeError: cb is not a function" error for comparing passwords

I've been starting to add user authentication into my app and when adding the login route, i've been getting the error "TypeError: cb is not a function". I know it is coming from my login route as all my other routes work fine.
I have tried researching the issue and trying a few fixes that i've found but none have worked. So i'm starting to believe i've messed up somewhere and I can't find where.
Login Route:
router.post('/login', function (req, res) {
User.findOne({ username: req.body.username }, function (err, user) {
if (err || user == null) {
console.log(err);
res.redirect('/login');
}
if (!user.comparePassword(req.body.password)) {
req.flash('invalidDetails', 'Wrong username or password!');
res.redirect('/login');
} else {
req.session.userId = user._id;
req.flash('loggedIn', 'User ' + req.body.username + ' has been logged in!');
return res.redirect('/');
}
});
});
User Model:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10;
var UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
trim: true
},
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true
}
});
UserSchema.statics.authenticate = function (email, password, callback) {
User.findOne({ email: email }).exec(function (err, user) {
if (err) {
return callback(err);
} else if (!user) {
var err = new Error('User not found.');
err.status = 401;
return callback(err);
}
bcrypt.compare(password, hash, function (err, result) {
if (result === true) {
return callback(null, user);
} else {
return callback();
}
});
});
};
UserSchema.pre('save', function (next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) return next(err);
// hash the password along with our new salt
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
UserSchema.methods.comparePassword = function comparePassword(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function (err, isMatch) {
if (err) {
return cb(err);
}
cb(null, isMatch);
});
};
var User = mongoose.model('User', UserSchema);
module.exports = User;
I'm expecting it to compare the password with the password that has been entered into the login form with the password that is stored in the database and log in the user if the password is correct or redirect back to the login page with the invalidDetails flash message if the password is incorrect.
But what i'm actually getting is the "TypeError: cb is not a function" error when trying to login.
You are not passing a callback that's why.
If you want a promise based method you can write something like this
AuthSchema.methods.comparePassword = function(candidatePassword) {
const currentPassword = this.password;
return new Promise((resolve, reject) => {
bcrypt.compare(candidatePassword, currentPassword, function(err, isMatch) {
if (err) return reject(err);
resolve(isMatch);
});
})
};
And now you can call that with await

Mongoose bcryptjs compare password doesn't refer to the document (this)

I have mongoose schema like so
var mongoose = require ('mongoose');
var bcrypt = require('bcryptjs');
var Schema = mongoose.Schema;
var SALT_WORK_FACTOR = 10;
var touristSchema = new Schema ({
local: {
email: String,
password: String
},
facebook: {
id: String,
token: String,
email: String,
name: String,
}
});
touristSchema.pre('save', function(next) {
var user = this;
console.log('bcrypt called by strategy', user);
// if user is facebook user skip the pasword thing.
if (user.facebook.token) {
next();
}
// only hash the password if it has been modified (or is new)
if (!user.isModified('password') && !user.isNew){
console.log('I am in here', user.isNew);
return next();
}
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
console.log('I am in genSalt');
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(user.local.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.local.password = hash;
next();
});
});
});
touristSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.local.password, function(err, isMatch) {
// console.log(this.local.password);
if (err) return cb(err);
cb(null, isMatch);
});
};
module.exports = mongoose.model('users', touristSchema);
and login strategy like this
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done) {
process.nextTick(function() {
User.findOne({ 'local.email': email }, function(err, user) {
if(err)
return done(err);
if(!user)
return done(null, false, req.flash('loginMessage', 'No User Found'));
user.comparePassword(password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
done(null, user);
}
else
done(null, false, req.flash('loginMessage', 'Incorrect password'));
});
});
});
}
));
everything is working as expected but when I try to log in it gives me error:
TypeError: Cannot read property 'password' of undefined.
Turns out that this.local is undefined.
This is very strange as in touristSchema.pre hook we assigned var user = this and on logging this returned user document. When in compare method we are using touristSchema.methods.compare then this should must refer to the document as written in mongoose middleware docs too. I am beating my head over it for a couple of days now and have consumed every available help I could. Any help is greatly appreciated in advance. Thanks
and yes my
mongnodb version is v3.2.15
mongoose version is 4.9.7
which looks compatible to me according to mongoose docs

node.js email verification token

I'm trying to set up a verification step in Mongoose, Express Node application based on this blog post ... http://danielstudds.com/adding-verify-urls-to-an-express-js-app-to-confirm-user-emails-secure-spa-part-6/ that post is over a year old so it kind of surprises me that its the first google result for 'node email verification'. I'm very new to node so I'm reliant on examples. Based on that post I did not see a download so I pieced it together to suit my scenario and here is what my code looks like.
Verification Model
var mongoose = require('mongoose'),
uuid = require('node-uuid'),
User = require('mongoose').model('User');
var verificationTokenSchema = new mongoose.Schema({
_userid : { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
token: {type: String, required: true},
createdAt: {type: Date, required: true, default: Date.now, expires: '4h'}
});
verificationTokenSchema.methods = {
createVerificationToken : function (done) {
var verificationToken = this;
var token = uuid.v4();
verificationToken.set('token', token);
verificationToken.save( function (err) {
if (err) return done(err);
return done(null, token);
});
}
};
exports.verifyUser = function(token, done) {
verificationTokenModel.findOne({token: token}, function (err, doc){
if (err) return done(err);
User.findOne({_id: doc._userId}, function (err, user) {
if (err) return done(err);
user["verified"] = true;
user.save(function(err) {
done(err);
});
});
});
};
var verificationTokenModel = mongoose.model('VerificationToken', verificationTokenSchema);
exports.verificationTokenModel = verificationTokenModel;
Then in my User model I call create like so..
User Model
exports.createUser = function(req, res, next) {
// Do all the stuff that creates the user save it and get the id back
var verificationToken = new verificationTokenModel({_userId: user._id});
verificationToken.createVerificationToken(function (err, token) {
if (err){
err = new Error("Couldn't create verification token");
res.status(400);
return res.send({reason:err.toString()});
}
// Do stuff with the token here and email
This works 'partially' in my db 'verificationtokens' collection the objects don't contain a _userid they contain the _userid (user._id) stored in _id
My first issue is I don't really understand how this works when there isn't a 'constructor'
var verificationToken = new verificationTokenModel({_userId: user._id});
and how do I get this to store the user._id as _userid in the verification collection
I don't use Mongoose, but here is what it happened:
For your first question:
when you run:
var verificationTokenModel = mongoose.model('VerificationToken', verificationTokenSchema);
it creates the constructor.
For the second question:
MongoDB always create documents with a field called "_id", so, the "_id" field in your verification collection is not the "_id" field from your User collection. The reason that _userid is not inserted is because you have a typo:
var verificationToken = new verificationTokenModel({_userId: user._id});
where "_userId" should be "userid"

Why is my node js code able to POST a login form to be matched from mongodb?

I am using node.js I already can post from a signup html page. However the code below
exports.login = function (req, res, next) {
if (req.body && req.body.email && req.body.password) {
var userLogin = new User ({
email: req.body.email,
password: req.body.password
});
userLogin.findOne(function(err) {
if (!err) {
res.redirect('/about.html');
} else {
res.redirect('http://google.com');
next(err);
}
});
} else {
next(new Error('Incorrect POST'));
}
};
The problem I am having is that the userLogin.findOne is not working as it says findOne is undefined.
The model.js file that this is linking to is:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt-nodejs'),
SALT_WORK_FACTOR = 10;
// these values can be whatever you want - we're defaulting to a
// max of 5 attempts, resulting in a 2 hour lock
MAX_LOGIN_ATTEMPTS = 5,
LOCK_TIME = 2 * 60 * 60 * 1000;
var UserSchema = new Schema({
email: { type: String, required: true, lowercase:true, index: { unique: true } },
password: { type: String, required: true },
firstName: {type: String, required: true},
lastName: {type: String, required: true},
phone: {type: Number, required: true},
birthday: {type: Date, required: true},
loginAttempts: { type: Number, required: true, default: 0 },
lockUntil: { type: Number }
});
UserSchema.virtual('isLocked').get(function() {
// check for a future lockUntil timestamp
return !!(this.lockUntil && this.lockUntil > Date.now());
});
//password hashing middleware
UserSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password along with our new salt
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
//password verification
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
UserSchema.methods.incLoginAttempts = function(cb) {
// if we have a previous lock that has expired, restart at 1
if (this.lockUntil && this.lockUntil < Date.now()) {
return this.update({
$set: { loginAttempts: 1 },
$unset: { lockUntil: 1 }
}, cb);
}
// otherwise we're incrementing
var updates = { $inc: { loginAttempts: 1 } };
// lock the account if we've reached max attempts and it's not locked already
if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) {
updates.$set = { lockUntil: Date.now() + LOCK_TIME };
}
return this.update(updates, cb);
};
// expose enum on the model, and provide an internal convenience reference
var reasons = UserSchema.statics.failedLogin = {
NOT_FOUND: 0,
PASSWORD_INCORRECT: 1,
MAX_ATTEMPTS: 2
};
UserSchema.statics.getAuthenticated = function(email, password, cb) {
this.findOne({ email: email }, function(err, user) {
if (err) return cb(err);
// make sure the user exists
if (!user) {
return cb(null, null, reasons.NOT_FOUND);
}
// check if the account is currently locked
if (user.isLocked) {
// just increment login attempts if account is already locked
return user.incLoginAttempts(function(err) {
if (err) return cb(err);
return cb(null, null, reasons.MAX_ATTEMPTS);
});
}
// test for a matching password
user.comparePassword(password, function(err, isMatch) {
if (err) return cb(err);
// check if the password was a match
if (isMatch) {
// if there's no lock or failed attempts, just return the user
if (!user.loginAttempts && !user.lockUntil) return cb(null, user);
// reset attempts and lock info
var updates = {
$set: { loginAttempts: 0 },
$unset: { lockUntil: 1 }
};
return user.update(updates, function(err) {
if (err) return cb(err);
return cb(null, user);
});
}
// password is incorrect, so increment login attempts before responding
user.incLoginAttempts(function(err) {
if (err) return cb(err);
return cb(null, null, reasons.PASSWORD_INCORRECT);
});
});
});
};
module.exports = mongoose.model('User', UserSchema);
In your code, userLogin is an instance of the User model which means you've created a document. As documentation for mongoose queries states, queries are handled using 'static helper methods' which means they aren't available on an instance of a model (i.e. on a document).
I think you'd probably want your code to look more like this where userLogin is an object that holds your conditions for the query and then you could use the User model's .findOne method to execute the query.
var userLogin = {
email: req.body.email,
password: req.body.password
};
User.findOne(userLogin, function(err) {
if (!err) {
res.redirect('/about.html');
} else {
res.redirect('http://google.com');
next(err);
}
});
Edit: OK, so the above answered the question about why findOne was undefined.
The second question asked is "why doesn't this authenticate the user?"
The reason the code is redirecting to (I assume) the '/about.html' page is because that's exactly what the code does...as written.
To authenticate (and we're technically starting a new answer to an originally unasked question): you need to call the UserSchema.statics.getAuthenticated method that is defined in modules.js. That would need code like this (slightly modified from the tutorial where most of this code originated):
User.getAuthenticated('<valid_email>', '<valid_password>', function(err, user, reason) {
if (err) throw err;
if (user) {
// handle login success
// note, handling success is more than a res.redirect call
// if you need to deal with session, state, cookies, etc...
return;
}
// otherwise we can determine why we failed
var reasons = User.failedLogin;
switch (reason) {
case reasons.NOT_FOUND:
case reasons.PASSWORD_INCORRECT:
// note: these cases are usually treated the same - don't tell
// the user *why* the login failed, only that it did
break;
case reasons.MAX_ATTEMPTS:
// send email or otherwise notify user that account is
// temporarily locked
break;
}
});
As a side-node, I'd suggest you look into everyauth or passport.js as modules that can do much of this for you. At that point, your main concern will be writing code for user CRUD while the modules handle sessions, cookies, serialization/deserialization and so on for you. This is doubly true if you have any plans to include OAUTH logins via google, facebook, et al.

Resources