Q: How can I evade storing duplicated users with Sails? - mongodb

I have coded a very simple sails app that just has passport authentication implemented. I use mongoDB as local database and I can't get to deny the creation of users with duplicated email. (I already have unique: true in the email attribute). Any idea what could I be missing?
var bcrypt = require('bcrypt');
module.exports = {
attributes: {
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
minLength: 6,
required: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(user, cb) {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) {
console.log(err);
cb(err);
} else {
user.password = hash;
cb();
}
});
});
}
};
Here I create users:
signup: function (req, res) {
User.create(req.params.all()).exec(function (err, user) {
if (err) return res.negotiate(err);
req.login(user, function (err){
if (err) return res.negotiate(err);
return res.redirect('/welcome');
});
});
}

The proper way to get errors when adding users is to check the Error object returned by the User.create() method (Promise or exec() method).
Example with Bluebird Promise :
User.create({ email : 'foo#bar.com', password : 'secret' })
.then((newUser) => {
/* do something with newly created user `newUser` */
/* eg : return res.view('user/added.ejs', newUser); */
})
.catch((err) => {
/* do something with the Error object `err` */
/* It should tell you if email already exists */
/* eg : return res.badRequest(err.message); */
});
Example with exec() method :
User.create({ email : 'foo#bar.com', password : 'secret' })
.exec((err, newUser) => {
if (err) {
/* do something with the Error object `err` */
} else {
/* do something with newly created user `newUser` */
}
});

Related

Typescript Error in mongoose schemas pre method: Unexpected aliasing of 'this' to local variable

I'm trying to implement authentication based on MongoDB and NestJS for an Ionic application and getting the following error message after making a POST request to the route api/users:
[Nest] 85372 - 03/26/2020, 14:04:49 [ExceptionsHandler] Cannot read property 'password' of undefined +23790ms
Inside my users.schema.ts file a get the errror message:
Unexpected aliasing of 'this' to local variable.eslint(#typescript-eslint/no-this-alias)
My users.schema.ts looks like this (commented the line with the error):
import * as mongoose from 'mongoose';
import * as bcrypt from 'bcryptjs'
export const UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true
},
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
unique: true,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
UserSchema.pre('save', function (next) {
const user = this; // This is marked as an error in vs code
if (!user.isModified('password')) return next();
bcrypt.genSalt(10, (err, salt) => {
if (err) return next(err);
bcrypt.hash(this.user.password, salt, (err, hash) => {
if (err) return next();
this.user.password = hash;
next();
});
});
});
UserSchema.methods.checkPassword = (attempt, callback) => {
bcrypt.compare(attempt, this.user.password, (err, isMatch) => {
if (err) return callback(err);
callback(null, isMatch);
})
}
I tried to implement the same schema with an arrow function, but then I get the following error message after making a POST request to api/users:
[Nest] 85947 - 03/26/2020, 14:09:30 [ExceptionsHandler] Cannot read property 'isModified' of undefined +22567ms
UserSchema.pre('save', (next) => {
if (!this.user.isModified('password')) return next();
bcrypt.genSalt(10, (err, salt) => {
if (err) return next(err);
bcrypt.hash(this.user.password, salt, (err, hash) => {
if (err) return next();
this.user.password = hash;
next();
});
});
});
What am I doing wrong here?
You cannot use arrow functions, because you loose the track of this. Read the official documentation of arrow functions, there are some very good examples about "what is this in an array function". MDN Arrow function expressions
There are two solutions:
Remove the arrow-function callback for bcrypt
UserSchema.pre('save', function (next) {
if (!this.isModified('password')) return next();
bcrypt.genSalt(10, function (err, salt) {
if (err) return next(err);
bcrypt.hash(this.user.password, salt, (err, hash) => {
if (err) return next();
this.user.password = hash;
next();
});
});
});
Disable eslint rule and always use the user variable
UserSchema.pre('save', function (next) {
const user = this;
if (!this.isModified('password')) return next();
bcrypt.genSalt(10, (err, salt) => {
if (err) return next(err);
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) return next();
user.password = hash;
next();
});
});
});

unable hash the password in mean stack

This is the code from routes file.
router.put('/reset/:token', function(req, res, next) {
console.log('reseting the password');
User.findOne({resetPasswordToken:req.params.token}, function(err, user) {
if(err) {
return next(err);
}
if (!user) {
return res.status(422).json({errors: [{msg: 'invalid reset token'}]});
}
user.resetPasswordToken ='';
user.resetPasswordExpires = '';
user.password = req.body.password;
User.addUser(user, (err, user) => {
if(err){
res.json({success: false, msg:'password has not changed'});
} else {
res.json({success: true, msg:'password has changed'});
}
});
});
});
This part of the code is from my schema file.
const UserSchema = mongoose.Schema({
password: {
type: String,
required: true
},
resetPasswordToken: {
type: String
},
resetPasswordExpires: {
type: Date
}
});
const User = module.exports = mongoose.model('User', UserSchema);
module.exports.addUser = function(newUser, callback){
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if(err) throw err;
newUser.password = hash;
newUser.save(callback);
});
});
}
When I try to rest the password it is storing as I've given the input. It is not hashing the password. For example, I have given the password as "zp12345", in the database it is storing as "password" : "zp12345".
For solve the problem you need to fix your addUser method:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
module.exports.addUser = function(newUser, callback){
bcrypt.hash(newUser.password, bcrypt.genSaltSync(10), null, (err, hash) => {
if (err) {
return next(err);
}
newUser.password = hash;
newUser.save(callback);
})
};
Here there is another example: Mongoose Pre Save Changing Password
And this is the library documentation: Bcrypt Nodejs

I have an error with passport.js implementation in Sails.js

I have a problem with Sails Passport implementation. This error appears in my terminal:
/home/tatico/sinGualichoPassport/config/passport.js:19
if (!user.validPassword(password)) {
^
TypeError: user.validPassword is not a function
at /home/tatico/sinGualichoPassport/config/passport.js:19:17
at returnResults (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/basic.js:180:9)
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/basic.js:86:16
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:83:7
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:52:16
at Object.async.forEachOf.async.eachOf (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:236:30)
at Object.async.forEach.async.each (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:209:22)
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:436:11
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:574:5
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:52:16
at Object.async.forEachOf.async.eachOf (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:236:30)
at Object.async.forEach.async.each (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:209:22)
at _buildChildOpts (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:464:9)
at _execChildOpts (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:432:8)
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:81:10
at wrapper (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/lodash/index.js:3592:19)
at applyInOriginalCtx (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/utils/normalize.js:421:80)
at wrappedCallback (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/utils/normalize.js:324:18)
at callback.success (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/switchback/lib/normalize.js:33:31)
at _switch (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/switchback/lib/factory.js:58:28)
at /home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/lib/waterline/adapter/dql.js:166:7
at wrapper (/home/tatico/.npm-global/lib/node_modules/sails/node_modules/waterline/node_modules/lodash/index.js:3592:19)
This is my Files:
config/passport.js:
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy
bcrypt = require('bcrypt');
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
module.exports = {
http: {
customMiddleware: function(app){
console.log('Express midleware for passport');
app.use(express.static('public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(function(req,res,next){
// Set the loggedUser in locals *does it work?
// to get it from the view
res.locals.loggedUser = req.user;
next();
});
}
}
};
UserController.js
module.exports = {
auth: function(req, res) {
return res.view();
},
create: function(req, res, next) {
User.create( req.params.all(), function createdUser(err, user){
if (err) {
return res.negotiate(err);
}
req.session.authenticated = true;
req.session.user = user;
return res.json(user);
});
},
login: function(req, res, next) {
// Use Passport LocalStrategy
require('passport').authenticate('local', function(err, user, info){
if ((err) || (!user)) next(err);
req.login(user, function(err){
if (err) return res.redirect('/user/auth');
// Redirect to the user page.
return res.redirect('/user/' + user.id);
});
})(req, res);
},
logout: function(req, res){
// Call Passport method to destroy the session.
req.logout();
// Redirect to home page.
return res.redirect('/');
}
};
User.js:
module.exports = {
attributes: {
email: {
type: 'string',
required: true,
email: true,
unique: true
},
password: {
type: 'string',
required: true
},
username: {
type: 'string',
required: true,
unique: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
// Lifecycle Callbacks
beforeCreate: function(values, next) {
hashPassword(values, next);
},
beforeUpdate: function(values, next) {
if(values.password) hashPassword(values, next);
else next();
}
}
var bcrypt = require('bcrypt');
function hashPassword(values, next) {
bcrypt.hash(values.password, 10, function(err, hash) {
if (err) return next(err);
values.password = hash;
next();
});
}
Im new in Sails, and I suppose this is a not good implementantion, Anyone can Help me and give a little feed back to improve my understanding of Sails and Passport? Thanks.
ValidPassword is not defined anywhere which is leading to this error.

Is there a documentation or a tutorial for integrating passport.js into sails.js? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed last month.
Improve this question
Every tutorial or unfinished documentation out there doensn't work. This is why I ask here: Is there a simple tutorial, which really works, for passport and sails?
follow this steps two integrate passport with sails js
first :-
List these dependencies inside application_directory/package.json under dependencies
//application_directory/package.json
{
...
"dependencies": {
...
"passport": "~0.1.16",
"passport-local": "~0.1.6",
"bcrypt": "~0.7.6"
}
...
}
2-
To create user model run the following command:
sails generate model user
3- model user.js will look like the following
var bcrypt = require('bcrypt');
module.exports = {
attributes: {
username: {
type: 'string',
required: true,
unique: true
},
password: {
type: 'string',
required: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(user, cb) {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) {
console.log(err);
cb(err);
}else{
user.password = hash;
cb(null, user);
}
});
});
}
};
4- To create a controller in sails type the command
sails generate controller
AuthController will look like the following:
var passport = require('passport');
module.exports = {
login: function (req, res) {
res.view();
},
process: function(req, res){
passport.authenticate('local', function(err, user, info) {
if ((err) || (!user)) {
return res.send({
message: 'login failed'
});
res.send(err);
}
req.logIn(user, function(err) {
if (err) res.send(err);
return res.send({
message: 'login successful'
});
});
})(req, res);
},
logout: function (req,res){
req.logout();
res.send('logout successful');
}
};
module.exports.blueprints = {
actions: true,
rest: true,
shortcuts: true
};
5- add the following code to application_directory/config/routes.js
module.exports.routes = {
// (This would also work if you had a file at: `/views/home.ejs`)
'/': {
view: 'home/index'
},
'/login': {
controller: 'AuthController',
action: 'login'
},
'/logout': {
controller: 'AuthController',
action: 'logout'
}
......
}
6- Inside application_directory/config create a file passport.js and add the following code to that
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy;
module.exports = {
express: {
customMiddleware: function(app){
console.log('Express midleware for passport');
app.use(passport.initialize());
app.use(passport.session());
}
}
};
7- Inside /api/services/ create a file passport.js and add the following code to that
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
bcrypt = require('bcrypt'); < /code>
//helper functions
function findById(id, fn) {
User.findOne(id).done(function (err, user) {
if (err) {
return fn(null, null);
} else {
return fn(null, user);
}
});
}
function findByUsername(u, fn) {
User.findOne({
username: u
}).done(function (err, user) {
// Error handling
if (err) {
return fn(null, null);
// The User was found successfully!
} else {
return fn(null, user);
}
});
}
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
findById(id, function (err, user) {
done(err, user);
});
});
passport.use(new LocalStrategy(
function (username, password, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
findByUsername(username, function (err, user) {
if (err)
return done(null, err);
if (!user) {
return done(null, false, {
message: 'Unknown user ' + username
});
}
bcrypt.compare(password, user.password, function (err, res) {
if (!res)
return done(null, false, {
message: 'Invalid Password'
});
var returnUser = {
username: user.username,
createdAt: user.createdAt,
id: user.id
};
return done(null, returnUser, {
message: 'Logged In Successfully'
});
});
})
});
}
));
8- Modify the authenticated.js file present inside /api/policies/
/**
* Allow any authenticated user.
*/
module.exports = function (req, res, ok) {
// User is allowed, proceed to controller
var is_auth = req.isAuthenticated()
if (is_auth) return next();
// User is not allowed
else return res.redirect("/login");
};

Waterline + Sails 0.10rc8/0.10rc9: beforeCreate not executing in sequence

My models are based on waterline:
var Waterline = require('waterline');
var User = Waterline.Collection.extend({..});
Here is my User.js
/**
* User.js
*
* #description :: User Model
* #docs :: http://sailsjs.org/#!documentation/models
*/
var bcrypt = require('bcrypt');
var crypto = require('crypto');
var Waterline = require('waterline');
var User = Waterline.Collection.extend({
connection: 'mongodb',
attributes: {
firstName: {
type: 'string'
},
lastName: {
type: 'string'
},
email: {
type: 'email',
required: true,
unique: true
},
username: {
type: 'string',
required: true,
unique: true
},
slug: {
type: 'string',
unique: true
},
picUrl: {
type: 'string',
unique: true
},
password: {
type: 'string',
required: true
},
activated: {
type: 'boolean',
defaultsTo: false
},
activationToken: {
type: 'string'
},
// Add a reference to Questions/Stars
starredQuestions: {
collection: 'question',
via: 'star',
dominant: true
},
/**
* Strips the password out of the json
* object before its returned from waterline.
* #return {object} the model results in object form
*/
toJSON: function () {
// this gives you an object with the current values
var obj = this.toObject();
delete obj.password;
delete obj.email;
delete obj.activationToken;
delete obj.activated;
// return the new object without password
return obj;
},
/**
* Adds a method called fullName to the response object
* #return {string} firstName and LastName concat'd
*/
fullName: function () {
return this.firstName + ' ' + this.lastName
}
},
/**
* Hash the users password with bcrypt
* #param {object} user the object of the submitted user data
* #param {Function} cb[err, user] the callback to be used when bcrypts done
*/
beforeCreate: function (user, cb) {
console.log('inside before create');
// create SLUG for better URLs.
if (!user.username) {
return cb({err: ["Must have a username!"]});
}
user.slug = user.username.replace(/\s+/g, '').toLowerCase();
user.username = user.username.toLowerCase();
user.stars = 0;
user.likes = 0;
// Create password + salt
crypto.generate({saltComplexity: 10}, user.password, function (err, hash) {
if (err) {
return cb(err);
} else {
user.password = hash;
user.activated = false; //make sure nobody is creating a user with activate set to true, this is probably just for paranoia sake
user.activationToken = crypto.token(new Date().getTime() + user.email);
return cb(null, user);
}
});
}
});
Here is the code which calls the Create() method:
create: function (req, res) {
var params = req.params.all();
User.findOne({'username' : params.username}, function (err, user) {
User.create(params).exec(function (err, user) {
if (err) {
res.send(500, err);
} else {
if (sails.config.user.requireUserActivation) {
var emailTemplate = res.render('email/email.ejs', {user: user}, function (err, list) {
nodemailer.send({
name: user.firstName + ' ' + user.lastName,
from: sails.config.nodemailer.from,
to: user.email,
subject: 'New Account Acivation Required',
messageHtml: list
}, function (err, response) {
sails.log.debug('nodemailer sent', err, response);
});
// seed some questions here.
/*Question.create(params, function (err, question) {
if (err) {
res.send(500, err);
} else {
}
});*/
res.redirect('/success');
});
} else {
res.redirect('/success');
}
}
});
});
},
I faced this on rc8 and then upgraded to rc9 and still facing the same issue.
What can be wrong?
Update: I added full code as requested by #particlebanana