I'm making an authenticate function. It actually works with hardcoded users, but when I start getting users from Firebase, things start getting asynchronous and issues with timing start happening.
I've got a kind of long-winded Javascript function here that I believe returns a promise.
function authenticate({ username, password }) {
return users.then((querySnapshot) => {
return querySnapshot.forEach(doc => {
let user = doc.data();
if (user.username.toUpperCase() == username.toUpperCase())
return bcrypt.compare(password, user.password).then(function (result) {
console.log(password);
console.log(user.password);
console.log(result);
if (result) {
const token = jwt.sign({ sub: user.id }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
};
}
})
})
})
}
Console logging seems to confirm that this is a promise. I'll be honest, I copy-pasted a lot of the code inside, so I'm still not entirely sure how it works, but the promise syntax is at least something I'm confident in. After I go through a list of users pulled from Firebase and check that both username and password match, the guts of if (result) should run. result does come back as true, which is correct for what I'm trying to log in with, but my password form rejects me because it continues processing before the authenticate method is finished.
In another Javascript file, I have the method that calls this one.
function authenticate(req, res, next) {
console.log(req.body);
userService.authenticate(req.body)
.then(user => console.log(user))
//.then(user => user ? res.json(user) : res.status(400).json({ message: 'Username or password is incorrect' }))
.catch(err => next(err));
}
I'm learning a lot about asynchronous programming recently but this is defying my expectations a bit. Surely doing .then() on authenticate() should run authenticate(), get a promise, even if it's unresolved, then wait for it to resolve before executing the rest of the statements? The current issue is that the method goes ahead, finds no value for user, then throws a 400, which I think is an issue with asynchronicity. Can anyone explain why the outer authenticate function isn't waiting and how I could make it do that?
There are two possible issues:
Result of forEach
The forEach function returns undefined, see Array.prototype.forEach(). If you need the result of the iteration, you can use Array.prototype.map()
Waiting for the Promise
The following statement sounds like the code does not await the result properly:
my password form rejects me because it continues processing before the authenticate method is finished.
If you have a javascript promise, you can use the await keyword in order to continue the function execution only if the promise is either resolved or rejected. Have a look at the examples here: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/await
In your example, the authenticate function would look like this:
async function authenticate(req, res, next) {
console.log(req.body);
await userService.authenticate(req.body)
.then(...)
.catch(...);
}
Note the async and await keywords. That way it only returns after the userService.authenticate(..) call is fully processed.
Firebase QuerySnapshot has a docs property of type Array<QueryDocumentSnapshot<T>>. You can use that and the Array.find to search for the user. You should also await for bcrypt.compare while you search for user.
function authenticate({ username, password }) {
return users.then(async (querySnapshot) => {
const { usersDocs: docs } = querySnapshot;
const userDoc = usersDocs.find(doc => {
return doc.data().username === username;
});
if (userDoc) {
let user = doc.data();
const pwdCompareResult = await bcrypt.compare(password, user.password);
console.log(password);
console.log(user.password);
console.log(pwdCompareResult );
if (pwdCompareResult ) {
const token = jwt.sign({ sub: user.id }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
}
}
}
})
}
Please consider using Firebase Authentication instead
Your auth implementation is not reliable and it transfers sensitive data to every users device, like usersname and password hashes. Firebase has a very solid authentication system that you should be using.
Related
My application (React) uses the service of Firestore. When a user changes his username, I must verify that this username is unique. For that, I want to use a Cloud function for more reliability.
However, my function always goes into an infinite loop. I don't see how I can get out of it.
Here is my function.
Any help would be much appreciated.
exports.checkUsername = functions.firestore
.document('/users/{userId}')
.onUpdate((change, context) => {
const before = change.before.data();
const after = change.after.data();
if (after.username === before.username) {
return null;
}
db.collection('users').where('username', '==', after.username).get()
.then((query) => {
if (!query.empty) {
// Username not available
return change.before.ref.update({ username: before.username });
}
// Username available
return null;
})
.catch(() => {
return change.before.ref.update({ username: before.username });
})
});
You're not dealing with promises correctly. Your function needs to return a promise that resolves after all the asynchronous work is complete. That's how Cloud Functions knows it's safe to terminate and clean up. Right now your function returns nothing in the case where the before and after usernames are not equal.
Minimally, you should return the final promise from the chain:
return db.collection('users').where('username', '==', after.username).get()
.then(...)
.catch(...)
I am running this node.js code to create customer on stripe account function deploy successful but failed to create customer on stripe I am not getting what I am missing.
Fire base functions folder is also not showing the function there.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')("secret key here");
var customer;
stripe.customers.create(
{
email: 'customer#example.com',
},
{
maxNetworkRetries: 2,
}
);
When you use APIs to services (viz. stripe.com and firebase) outside your complete control, you must check for errors. If you're using a service incorrectly the error will explain, or at least hint, what you're doing wrong.
The stripe-node API documentation suggests you invoke stripe.customer.create() as an awaited function, like this:
const customer = await stripe.customers.create({
email: 'customer#example.com',
});
This is easy if you call it from an async function. You should use this sort of code in your async function to check for errors back from stripe.com.
try {
const customer = await stripe.customers.create({
email: 'customer#example.com',
});
/* here's your customer object */
}
catch (error) {
console.error ('stripe', error);
}
If you do not call it from an async function, you can wait for results using the Promises scheme.
stripe.customers.create({
email: 'customer#example.com',
})
.then ( function (customer) {
/* here's your customer object */
})
.catch ( function (error) {
console.error ('stripe', error);
});
If you haven't yet figured out how async/await or Promises work, the time has come for you do to that.
So i'm having a problem where i'm attempting to have an async call to my database to check if the username and password of an account exist before then checking to see if they are valid.
I'm running into the problem that the server skips my database call and then proceeds to the check before the information from the database is grabbed.
The code:
class HandlerGenerator {
login (req, res) {
let username = req.body.username;
let password = req.body.password;
let checkUsername = "";
let checkPassword = "";
var lData = {
username: req.body.username,
password: req.body.password
};
// For the given username fetch user from DB
var db = req.db;
var getUser = async () => {
var result = await (
User.findOne(lData , function(err, userLogin){
if(err){
console.log(err);
return
}
console.log(userLogin);
console.log("------====----");
console.log(userLogin.username);
checkUsername = userLogin.username;
checkPassword = userLogin.password;
console.log("------====----");
console.log(checkUsername);
console.log(checkPassword);
})
);
console.log("--00--");
console.log('result' + result)
return result;
};
console.log("does this work?");
if (username && password) {
console.log("------==2==----");
console.log(checkUsername)
console.log(checkPassword)
if (username === checkUsername && password === checkPassword) {
let token = jwt.sign({username: username},
config.secret,
{ expiresIn: '24h' // expires in 24 hours
}
);
// return the JWT token for the future API calls
res.json({
success: true,
message: 'Authentication successful!',
token: token
});
} else {
res.send(403).json({
success: false,
message: 'Incorrect username or password'
});
}
} else {
res.send(400).json({
success: false,
message: 'Authentication failed! Please check the request'
});
}
}
index (req, res) {
res.json({
success: true,
message: 'Index page'
});
}
}
When I run this the "Does this work?" Comment always runs first and i'm confused on what I am messing up
You have two main issues here.
First, async returns an AsyncFunction, which returns a Promise when called. This means that at this point, nothing in getUser has been executed. Not only you need to actually call getUser to start running what is inside it, but you also need to await the result, otherwise you have absolutely no guarantee that the execution will be over.
Instead of going through this trouble, making your login function async seems a more reasonable choice, as you are trying to do asynchronous operations inside it. You could then remove the getUser function and only keep the var result = await User.findOne(....) part.
The other issue, as multiple persons said in the comments, is that you need to await on a Promise.
Looking at your function call, it looks like your findOne functions uses a callback rather than a promise. Do check the documentation, some libraries support both and might indeed return a promise if you do not pass in any callback.
If it does, you should not pass any callback. The result of the "awaited" call should be userLogin.
Otherwise, wrapping the function so that it returns a promise is straightforward.
The basic pattern is as follows
// this is the function that we want to wrap
function funcToWrap(someParam, callback) {
// does stuff
callback(null, "some result");
}
// this is the wrapper
function wrappedFunction(someParam) {
return new Promise((resolve, reject) => {
funcToWrap(someParam, (err, res) => {
if (err === null) {
reject(err);
} else {
resolve(res);
}
});
});
This transforms your callback-based function into a promise-based one. You can then of course await on wrappedFunc and use it as any other promise.
This is such a common pattern that a lot of libraries already implement this functionality. For example, the Promise library Bluebird provides a promisify function which does exactly this.
http://bluebirdjs.com/docs/api/promise.promisify.html
So instead of writing all of this yourself, you could simply write
var wrappedFunction = Promise.promisify(funcToWrap);
I'm very new to async and await, I'm hoping someone can help me out.
I have a function call register and within that function I register a user, and then send some data about them to the server to build a "user profile" so to speak.
Problem I'm having is that I also have a function called login and that is also async and redirects the user as soon as they are registered.. meaning the "user profile" data never gets sent.
Here is my register function:
async register(user: User) {
try {
const result = await this.afAuth.auth.createUserWithEmailAndPassword(user.email, user.password);
await this.afAuth.auth.currentUser.updateProfile({
displayName: user.displayName,
photoURL: ""
}).then(() => {
let currentUser = result;
let date = new Date().getTime();
let userData = {
email: currentUser.email,
displayName: currentUser.displayName,
uid: currentUser.uid,
created: date,
}
this.database.list('users').set(userData.uid, userData).then(() => {
return;
}); //I want to continue after this line is called.
return;
}, function(error) {
console.log(error)
});
} catch(e) {
console.log(e);
}
}
Is my await in the wrong location? I want the login function to get called once the data is .set...
Any ideas what I'm doing wrong.. I'd really appreciate any help. Thanks!
The point of using async / await is that you don't have to deal with then() or catch().
async function register(user: User) {
try {
const result = await this.afAuth.auth.createUserWithEmailAndPassword(user.email, user.password);
await this.afAuth.auth.currentUser.updateProfile({
displayName: user.displayName,
photoURL: ""
});
let currentUser = result;
let date = new Date().getTime();
let userData = {
email: currentUser.email,
displayName: currentUser.displayName,
uid: currentUser.uid,
created: date,
};
await this.database.list('users').set(userData.uid, userData)
// Do something after everything above is executed
return;
} catch(e) {
console.log(e);
}
};
await lets you wait on a promise. One of the largest benefits of async-await is not having to use then syntax or any explicit promises. Not sure why you mix styles. It is extremely simple to 'do more stuff' after an await statement.
Here is some pseudocode. I took out the try catch because your entire function is in it, which makes it rather pointless. Also, if any promise rejects, it is translated into an exception. If an exception occurs within a promise, it is translated into a rejection which is then translated into an exception. Other exceptions outside of promises are plain old exceptions.
I took the liberty of adding the TypeScript tag to your question because you posted TypeScript syntax. TypeScript is not Javascript.
async register(user: User) {
const result = await createUser(...);
await updateProfile(...);
const userData = buildUserData(...);
await setUserData(database, userData);
console.log('more stuff after setUserData');
}
I have a basic mongoose authentication, with bcryptjs to hash passwords. Both bcrypt and mongoose return promises. In my routes.js I have the following script which gets stuck after finding the User in the db:
routes.post('/auth', (req, res)=> {
User.findOne({'local.username': req.body.username})
.then(
user=> Promise.all([user, user.validate(req.body.password)])
)
.then(
results => {
console.log(results);
res.json({token: jwt.sign({id: results[0]._id}, config.secret)});
}
)
.catch(
err=> console.log(err)
);
});
As you can see I find the user, and then try to call its validate method (which gets called), but it won't resolve the promise nor throw an error. In my user.js which defines my UserSchema I have this code to compare passwords:
UserSchema.methods.validate = function (password) {
return bcrypt.compare(password, this.local.password);
};
This is called, but the returned promise seems like it vanishes, it is not resolved, the results variable is never logged.
One more thing, if I edit user validation code to this:
UserSchema.methods.validate = function (password) {
return bcrypt.compare(password, this.local.password).then(
results => {
console.log(results)
}
)
};
I get true logged to console, so it must be working but I don't want to resolve my promise here, I want to attach the .then(...) in my router, isn't it possible?
What am I doing wrong?
UPDATE:
If I put the compare method in the routes.js it works, but that is not what I want to do, I want to keep it in the user.js, but I thought this might point out the problem which I still cannot see. I guess I have to call then() immediately on the promise, but I don't see why.
User.findOne({'local.username': req.body.username})
.then(
user=> Promise.all([user, bcrypt.compare(req.body.password,user.local.password)])
)
.then(
results => {
console.log(results);
res.json({token: jwt.sign({id: results[0]._id}, config.secret)});
}
)
.catch(
err=> console.log(err)
);
First of all why using Promise.all here? Especially i don't see the need for doing something like Promise.resolve(user). Without knowing how user.validate is working, i would write it like
routes.post('/auth', (req, res)=> {
let userId
User.findOne({'local.username': req.body.username})
.then(user => {
userId = user._id
return user.validate(req.body.password)
})
.then(results => {
console.log(results);
res.json({token: jwt.sign({id: userId}, config.secret)});
})
.catch(err => console.log(err))
});
I found out, that the problem is with mongoose. It wraps the module methods, and promises "get lost" somewhere. The solution to this is to use a sync compare method, or provide a callback.
Also I created an issue with this on github:
https://github.com/Automattic/mongoose/issues/4856
You are not doing anything with the Promise.all you call in the then.
Instead of
user=> Promise.all([user, user.validate(req.body.password)])
You should then it:
user.validate(req.body.password)
.then(results => {
// Do stuff with results here...
});