In this git issue AngularFire disabled the auto-login functionality associated with $createUser. (The docs are outdated.)
So what's the best practice / cleanest way to create a user and then log them in?
app.factory('Auth', function($firebaseSimpleLogin, FIREBASE_URL, $rootScope) {
var ref = new Firebase(FIREBASE_URL);
var auth = $firebaseSimpleLogin(ref);
var Auth = {
register: function (user) {
return auth.$createUser(user.email, user.password);
},
login: function (user) {
return auth.$login('password', user);
},
...
Note: $createUser returns a md5 hash but $login uses plaintext password.
I'm new to promises. Thanks!
I didn't realize what Kato meant. But now that I understand promises the solution is pretty straight forward:
register: function (user) {
return auth.$createUser(user.email, user.password)
.then(function() {
return auth.$login('password', user);
});
},
I think this should do it. You have to use q or inject angular.$q.
app.factory('Auth', function($firebaseSimpleLogin, FIREBASE_URL, $rootScope) {
var ref = new Firebase(FIREBASE_URL);
var auth = $firebaseSimpleLogin(ref);
var deferred = Q.defer(); // from Q lib
// var deferred = $q.defer(); //angularjs q
var Auth = {
register: function (user) {
auth.$createUser(user.email, user.password)
.then(function(result) {
deferred.notify('user created.'); // optional notify progress
return auth.$login('password', user); // return a new promise for chaining.
},
function(reason) {
// createUser failed
deferred.reject(reason);
})
.then(function(result) {
// resolve the login promise return from above
deferred.resolve(result);
},
function(reason) {
// login failed
deferred.reject(reason);
});
return deferred;
},
login: function (user) {
return auth.$login('password', user);
},
...
Related
I have a route that allows a user to reset their password by sending them an email. Standard procedure for most websites. In this route, I import the async npm module and use the waterfall method so that I can handle the asynchronous nature of multiple functions. I'm still having a bit of trouble understanding promises, but I'm trying to replace waterfall with a promise or promise chain.
How could I refactor this route with a promise? Here are the steps contained in this route that is currently split up into 4 functions with waterfall.
First the route creates a reset token
Search for user based on email
2.5. If user is found, save user, otherwise return 404
Send email to user containing a reset url
Return a status of 200.
app.post('/forgotPassword', function(req, res, next) {
waterfall([
// generate reset token
function(done) {
crypto.randomBytes(20, function(err, buf) {
var token = buf.toString('hex');
done(err, token);
});
},
function(token, done) {
// search for user with the given email
User.findOne({ email: req.body.email }, function(err, user) {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
return res.status(404).send();
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
done(err, token, user);
});
});
},
function(token, user, done) {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
var mailOptions = {
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
smtpTransport.sendMail(mailOptions, function(err) {
done(err, 'done');
});
}],
function(err) {
// handle error
if (err) return next(err);
res.status(200).send();
});
}); // end POST route '/forgotPassword'
Promise is a very powerful tool. It can be hard to understand it at the beginning, but totally worth the effort! Please let me know if you have any doubts :)
app.post('/forgotPassword', function(req, res, next)
{
new Promise((resolve, reject) =>
{
// generate reset token
crypto.randomBytes(20, (err, buf) =>
{
if(err)
return reject(err);
const token = buf.toString('hex');
resolve(token);
});
})
.then((token) =>
{
return new Promise((resolve, reject) => {
// search for user with the given email
User.findOne({ email: req.body.email }, (err, user) =>
{
if (!user)
return reject(404);
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
if(err)
return reject(err);
resolve(user);
});
});
});
})
.then((user) =>
{
return new Promise((resolve, reject) =>
{
const smtpTransport = nodemailer.createTransport('SMTP',
{
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
const mailOptions = {
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`
};
smtpTransport.sendMail(mailOptions, (err) =>
{
if(err)
return reject(err);
resolve();
});
});
})
.then(() => res.sendStatus(200))
.catch((err) =>
{
//check if the error is the one from the DB where the user was not found
if(err == 404) {
return res.sendStatus(404);
}
return res.status(500).send(err);
});
});
bluebird is one of the most popular promise library.
and it offers promisify function to convert callback hell to promise.
please read this document and play with it.
http://bluebirdjs.com/docs/working-with-callbacks.html
This answer presumes 4 completely sequential steps:
(Function () {
Return Promise (function (resolve, promise) {
// try to do some stuff
if (success) {
Resolve ("pass this value on to next link in the chain");
}
Reject();
})()
.then (function (value) {
// do next step
Return "pass this value on to next link in the chain";
.then (function (value) {
// do next step
Return "pass this value on to next link in the chain";
.catch (function (error) {
// handle any reject or any error in the chain
}
You could in relation to the individual .then also choose to return a Promise. Beware: any errors in the .catch block will be swallowed.
This is just an example with ES6 promise but working code. You can refactor it further to have a clear reusable code. You can replace ES5 functions with ES6 arrow functions.
app.post('/forgotPassword', function(req, res, next) {
var catch = function(err){
return next(err);
}:
var error404 = function(){
return res.status(404).send();
};
var success = function(){
return res.status(200).send();
};
var step1 = new Promise(function(resolve, reject){
crypto.randomBytes(20, function(err, buf) {
if(err){
reject(err);
} else {
var token = buf.toString('hex');
resolve(token);
}
});
});
var step2 = function(token) {
// search for user with the given email
User.findOne({ email: req.body.email }, function(err, user) {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
return {error404: true};
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
return { error: err, token: token, user: user });
});
});
};
var step3 = function(obj) {
if(obj.error){
return catch(obj.error);
} else if(obj.error404) {
return error404();
} else {
var token = obj.token, user = obj.user;
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
var mailOptions = {
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
smtpTransport.sendMail(mailOptions, function(err) {
if(err){
return catch(err);
} else {
return success();
}
});
}
};
step1.then(step2, catch).then(step3);
}); // end POST route '/forgotPassword'
Promises work best when you're dealing with a library where methods already return them, or a library like bluebird that lets you promisify existing callback APIs, otherwise it's a lot of conversions.
If you're still set on refactoring with plain es6, put your promise-wrappers at the lowest possible level, for best effect and cleanest error handling, basically promisify manually:
let convert = (resolve, reject) => (err, res) => err ? reject(err) : resolve(res);
crypto.randomBytesAsync = n=>new Promise((y,n)=>crypto.randomBytes(n,convert(y,n)));
User.findOneAsync = u => new Promise((y, n) => User.findOne(u, convert(y, n)));
User.prototype.saveAsync = () => new Promise((y, n) => this.save(convert(y, n)));
Then use them like this:
app.post('/forgotPassword', function(req, res, next) {
crypto.randomBytesAsync(20).then(buf => {
var token = buf.toString('hex');
// search for user with the given email
return User.findOneAsync({ email: req.body.email }).then(user => {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
res.status(404).send();
throw;
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
return user.saveAsync().then(() => {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: { user: config.sendgridUser, pass: config.sendgridPassword }
});
smtpTransport.sendMailAsync =
o => new Promise((y, n) => smtpTransport.sendMail(o, convert(y, n)));
return smtpTransport.sendMailAsync({
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
});
});
})
})
.then(() => res.status(200).send())
.catch(err => next(err));
}); // end POST route '/forgotPassword'
I have this code inside of a file called Auth.js .
hasRole: function(){
console.log(currentUser);
return currentUser.role; //role is an array of objects but gives undefined
}
I need to resolve the value of hasRole before redirecting anywhere just after login.
login looks like this:
var role=[];
login: function(user, callback) {
var cb = callback || angular.noop;
var deferred = $q.defer();
$http.post('/auth/local', {
email: user.email,
password: user.password
}).
success(function(data) {
$cookieStore.put('token', data.token);
currentUser = User.get();
console.log(currentUser);//gives an unresolved promise (output is given after this piece of code.
role = currentUser.role; //gives undefined
deferred.resolve(data);
return cb();
}).
error(function(err) {
this.logout();
deferred.reject(err);
return cb(err);
}.bind(this));
return deferred.promise;
}}
and is called as follows:
$scope.login = function(form) {
$scope.submitted = true;
if(form.$valid) {
Auth.login({
email: $scope.user.email,
password: $scope.user.password
})
.then( function() {
// Logged in, redirect to home
var role = Auth.hasRole();
console.log(role) //gives undefined.
//Need to redirect on basis of value of role
/*if(role.priority >= 1){
$location.path('/admincontrol');
}else{
$location.path('/');
}*/
})
.catch( function(err) {
$scope.errors.other = err.message;
});
}
};
How do I access currentUser.role here? I need to have its value in $scope.login just after login and just before redirecting so that I can redirect on basis of its value.
Edit: User service looks like this:
'use strict';
angular.module('createProjectApp')
.factory('User', function ($resource) {
// return $resource('/api/users/:id/:controller', {
return $resource('/api/customUsers/:id/:controller', {
id: '#_id'
},
{
changePassword: {
method: 'PUT',
params: {
controller:'password'
}
},
get: {
method: 'GET',
params: {
id:'me'
}
}
});
});
currentUser when consoled in login looked like this:
User.get() is itself a function that returns a promise. You are going to have to wait on the promise fullfillment before continuing, as in
currentUser.then(function() {
console.log(currentUser);
role = currentUser.role; //gives undefined
deferred.resolve(data);
return cb();
}
cb() itself is not needed since you are using promises - but you may have something special in mind.
I've been struggling with a simple test that involves mocking promises with no luck. I'm using jasmine spies. Hope somebody can help me. I've successfully mocked findByUserName method but there appears to be something wrong with the promise. The tests just fails without any error. Please see my code:
Module CertificationSettingsManager.FindUser being tested:
'use strict';
var CertificationSettingsManagerFindUser = function(usersRepository, accountsRepository, userName, next) {
if (userName) {
usersRepository.findByUserName(userName)
.then(function(user) {
if (user && user.password) {
delete user.password;
}
return user;
})
.then(function(user) {
if (user) {
accountsRepository.findAllAssignedTo(user.userId).then(function(results) {
user.hasAccountsAssigned = (results.count > 0);
user.belongsRepHierarchy = true;
user.isMemberOfReconOrPrep = true;
next(null, user);
});
}
})
.catch(function(err) {
if (err) {
next(err);
}
});
} else {
next();
}
};
module.exports = CertificationSettingsManagerFindUser;
And this is the test spec:
'use strict';
var findUserModule = require('./CertificationSettingsManager.FindUser');
var Q = require('q');
describe('CertificationSettingsManager findUser module', function() {
var userSpy;
var accounstSpy;
var nextCallbackSpy;
var inputUserTemplate;
var accountsResult;
beforeEach(function() {
inputUserTemplate = {
userId: 1234
};
accountsResult = {
count: 0
};
userSpy = jasmine.createSpyObj('UserRepository', ['findByUserName']);
accounstSpy = jasmine.createSpyObj('ProfileRepository', ['findAllAssignedTo']);
nextCallbackSpy = jasmine.createSpy('nextCallback spy');
});
it('when userName was not supplied it should call next with no parameters', function() {
findUserModule(null, null, null, nextCallbackSpy);
expect(nextCallbackSpy).toHaveBeenCalledWith();
});
//done callback passed so it can be asynchronous
it('if user has password, it should remove the password', function(done) {
inputUserTemplate.password = 'there is some password here';
accounstSpy.findAllAssignedTo.and.returnValue(Q.resolve(accountsResult));
userSpy.findByUserName.and.returnValue(Q.resolve(inputUserTemplate));
findUserModule(userSpy, accounstSpy, 'rassiel', function(){
expect(inputUserTemplate.password).toBeUndefined();
// done being called
done();
});
});
});
The second test: it('if user has password, it should remove the password' is failing, but when I try to debug the test it never hits the then inside:
usersRepository.findByUserName(userName)
.then(function(user) {
**UPDATED!!!****
After adding the done callback to the it method, the test is working now... that's what I was missing. Then done should be called after the expects statements.
Is there a better way of doing this?
I'm using NodeJS, with bcrypt-nodejs (https://github.com/shaneGirish/bcrypt-nodejs) and Bluebird for promises. Came up with this code and been wondering if there is better way to do the same thing. I have module with:
var Promise = require("bluebird"),
bcrypt = Promise.promisifyAll(require('bcrypt-nodejs'));
// ....[some othe code here]
Users.prototype.setPassword = function(user) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(user.password, result);
});
};
then from another module I call users.setPassword as below:
app.post('/api/v1/users/set-password', function(req, res, next) {
users.setPassword(req.body).then(function(result) {
// Store hash in your password DB.
console.log(result[1]);
res.json({
success: true
})
})
.catch(function(err) {
console.log(err);
});
});
It always ends up with "[Error: No callback function was given.]" message as bcrypt.hashAsync seems to require 4 parameters. Original, non-promisified hash method requires 3 only though. When I add empty callback to hashAsync, it works fine:
Users.prototype.setPassword = function(user) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(user.password, result,function() {});
});
};
Is there any better way to do this, without providing empty callback as above?
EDIT:
In response to Bergi's comment.. the function will set password eventually, I just didn't get that far when posted the question. Now got this far, please let me know if something is not quite right though:
Users.prototype.setPassword = function(user) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(user.password, result, null);
})
.then(function(result) {
// store in database
console.log("stored in database!");
return result;
});
};
bcrypt.hashAsync seems to require 4 parameters. Original, non-promisified hash method requires 3 only though.
It's the other way round rather. From the docs:
hash(data, salt, progress, cb)
data - [REQUIRED] - the data to be encrypted.
salt - [REQUIRED] - the salt to be used to hash the password.
progress - a callback to be called during the hash calculation to signify progress
callback - [REQUIRED] - a callback to be fired once the data has been encrypted.
The original method took 4 arguments, hashAsync will take 3 and return a promise.
However, in your code you were only passing two. You don't need to pass an empty function though, that the parameter is not [REQUIRED] means you can pass null (or any other falsy value) for it. bcrypt will create such an empty function itself. So use
function (data) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(data.password, result, null);
});
}
This is my promisified bcrypt from a project I did a while back. Bluebird isn't really necessary for such a small, simple library.
module.exports = {
makeUser: function(username, password){
return new Promise(function(resolve, reject) {
bcrypt.genSalt(10, function(err, salt){
bcrypt.hash(password, salt, null, function(err, hash) {
if (err) {
console.log("hashing the password failed, see user.js " + err);
reject(err);
}
else {
console.log("hash was successful.");
resolve(hash);
}
})
})
})
.then(function(hash){
return db.createUser(username, hash)
})
},
login: function(username, password){
return db.userFind(username)
.then(function(userObj){
if(!userObj){
console.log("did not find " + username + " in database.");
return new Promise(function(resolve, reject){
resolve({login:false, message:"Your username and/or password are incorrect."})
}
}
else {
console.log("found user: " + userObj._id, userObj);
return new Promise(function(resolve, reject){
bcrypt.compare(password, userObj.hashword, function(err, bool) {
resolve({bool:bool,
user:userObj._id,
mindSeal: userObj
})
})
})
}
})
}
}
Example Usage:
app.post('/signup', function(req, res) {
var username = req.body.username;
var password = req.body.password;
var user = handler.userExists(username)
.then(function(answer){
if (answer !== null){
console.log(req.body.username + " was taken")
res.send({login: false, message: req.body.username + " is taken"});
return null;
} else if (answer === null) {
console.log("username not taken")
return handler.makeUser(username, password);
}
})
.catch(function(err){
console.log("error during user lookup:", err);
res.status(404).send({message:"database error:", error:err});
})
if (user !== null){
user
.then(function(x){
console.log("this is returned from handler.makeUser: ", x)
console.log(x.ops[0]._id)
req.session.user = x.ops[0]._id;
var mindSeal = {
userSettings: {
username: x.ops[0]._id,
newCardLimit: null,
tValDefault: 128000000,
lastEdit: req.body.time,
todayCounter: 0,
allTimeCounter: 0,
cScaleDefault: {0: 0.9, 1: 1.2, 2: 1.8, 3: 2.5},
accountMade: req.body.time
},
decks: {}
};
handler.setMindSeal(req.session.user, mindSeal, req.body.time);
res.send({
login: true,
mindSeal: mindSeal
});
})
.catch(function(error){
console.log("make user error: " + error);
res.status(401).send({message:"failed.",error:error,login:false});
})
}
});
app.post('/login', function(req, res) {
var username = req.body.username;
var password = req.body.password;
handler.login(username, password)
.then(function(obj){
if (obj.bool){
console.log("username and password are valid. login granted.");
req.session.user = obj.user;
console.log("obj is:", obj)
var mindSeal = {decks:obj.mindSeal.decks, userSettings:obj.mindSeal.userSettings};
console.log("mindSeal sending:", mindSeal);
res.status(200).send({
login: true,
message:"Login Successful",
mindSeal: obj.mindSeal
});
}
else {
console.log("password invalid")
res.status(401).send({login: false, message:"Your username and/or password are incorrect."})
}
})
.catch(function(error){
console.log(error);
res.status(404).send({message:"database error:", error:err});
})
});
conceptual example only; borrowed and slightly modified some old code of mine on the fly. Working code (I see things I'd like to improve in it now, but it works) here: https://github.com/finetype/mindseal/blob/master/server.js
Maybe you could use another bcrypt library, with a better API which removes the need for promises.
Users.prototype.setPassword = function(user) {
return TwinBcrypt.hashSync(user.password, 10);
};
Or, with progress tracking :
Users.prototype.setPassword = function(user) {
function progress(p) {
console.log( Math.floor(p*100) + '%' );
}
TwinBcrypt.hash(user.password, 10, progress, function(result) {
// store in database
console.log("stored in database!");
return result;
});
};
I've got a single page which is an account settings page. In it, I allow my users to update their avatar (if they've attached an image), change their email (if it has been changed from the original), and change their name and password.
Right now, I'm using async's waterfall method, but am swapping out async for Q since I prefer the syntax (and api). I'm wondering if this is the way that I should be using Q in replacement of async's waterfall.
I'm doing something like this:
exports.settingsAccountPOST = function(req, res) {
var doesEmailExist = function() {
var deferred = Q.defer();
User.findByEmail({
email: req.body.email
}, function(err, user) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(user);
}
});
return deferred.promise;
};
var updateEmail = function(email) {
var deferred = Q.defer();
User.updateEmail({
userId : req.session.user.id,
email : req.body.email
}, function(err, updated) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(updated);
}
});
return deferred.promise;
};
var updateName = function() {
var deferred = Q.defer();
if (req.body.name) {
User.updateName({
userId: req.session.user.id,
name: req.body.name
}, function(err, updated) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(updated);
}
});
return deferred.promise;
}
};
doesEmailExist().then(function(email) {
if (!email) {
return(updateEmail(email));
}
}).then(function() {
return(updateName())
}).then(function() {
res.redirect('/account')
});
};
Say that there is an error with the email address being used. Is there a way to "pass" it to the final call? Use case: Updated password properly, but email update didn't work, so I want to show a session flash to the user telling them they updated their password properly, but there was an issue with updating their email.
I was looking in the docs and it seems I may need to use:
.fin(function () {
});
Is this correct? If so, what should I be passing into that? Just push to an object the error that occurred within the chain and then loop through all errors and display them to the user? Or just return immediately and display the error?
If you are using Q.defer you are generally doing something wrong.
var findByEmail = Q.nbind(User.findByEmail, User);
var updateEmail = Q.nbind(User.updateEmail, User);
var updateName = Q.nbind(User.updateName, User);
//later on...
exports.settingsAccountPOST = function (req, res) {
findByEmail({
email: req.body.email
})
.then(function (user) {
if (!user) {
return updateEmail({
userId: req.session.user.id,
email: req.body.email
});
}
})
.then(function () {
return updateName({
userId: req.session.user.id,
name: req.body.name
})
})
.then(function () {
res.redirect("/account");
})
.catch(function(e){
//Handle any error
});
};