How To Use Variables Down A Promise Chain - javascript

I am using nodejs with express framework and mongodb/mongoose to store my data.
I have a register function which does 4 things. Creates a user, creates a token, assigns the token to the user and finally sends an email.
I initially did this using callbacks which worked fine. Im trying to use promises now i have required bluebird to do this. However when one promise is complete i need to use that returned variable in the next promise.
Register Function
module.exports.register = function(req, res) {
var input = req.body;
var newUser = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
var promise = newUser.save();
promise.then(function(user) {
return createToken('new', null, user._id);
}).then(function(token) {
user.tokens.push(token._id);
return user.save();
}).then(function(user) {
//Do Email Stuff
}).catch(function(err) {
return res.json("Could Not Register");
});
}
Create Token Function
var createToken = function(type, expiry, userid) {
var token = uuid.v4();
return new Promise(function(resolve, reject) {
var newToken = Token({
type:type,
token: token,
expiry: expiry,
user: userid
});
var promise = newToken.save();
promise.then(function(token) {
resolve(token);
}).catch(function(err) {
reject(err);
});
});
};
So where im doing "user.tokens.push" it can't find the user. ive read in bluebird that i can use somethign called binding? and then use "this". Could anyone show me how to do this properly.
Also if there is an an error in each promise i'd like the catch method to be dynamic. Instead of just "Could not register" it would be "Could Not Save User" or "Could Not Save Token" depending on which promise failed.
And if theres a way to make this even cleaner let me know.

there's no shame in using a var in the scope of the register function to store the value of user for later use
module.exports.register = function(req, res) {
var input = req.body;
var newUser = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
var sUser; // store user in this var
newUser.save()
.then(function(user) {
sUser = user; // save value user to use later on
return createToken('new', null, user._id);
}).then(function(token) {
sUser.tokens.push(token._id); // sUser is user, huzzah
return sUser.save();
}).then(function(user) {
//Do Email Stuff
}).catch(function(err) {
// use the content of err to return a meaningful error
return res.json("something more meaningful based on the content of err");
});
}
You should also avoid the new Promise antipattern in createToken
var createToken = function(type, expiry, userid) {
var token = uuid.v4();
var newToken = Token({
type:type,
token: token,
expiry: expiry,
user: userid
});
return newToken.save();
};
That produces an identical result to your code
another way would be to send user to the createToken function and rewrite your code like this
module.exports.register = function(req, res) {
var input = req.body;
var newUser = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
newUser.save()
.then(function(user) {
return createToken('new', null, user);
}, function(err) { // optionally change errors to meaningful error messages
throw "newUser.save failed";
}).then(function(user) {
//Do Email Stuff
// you could throw "email failed" if there's an error
}).catch(function(err) {
// err could be "newUser.save failed", "newToken.save failed", "email failed"
// use the content of err to return a meaningful error
return res.json("something more meaningful based on the content of err");
});
}
var createToken = function(type, expiry, user) {
var token = uuid.v4();
var newToken = Token({
type:type,
token: token,
expiry: expiry,
user: user._id
});
return newToken.save()
.then(function(token) {
user.tokens.push(token._id);
return user.save();
}) // you could add the following to make the errors suited to you
.catch(function(err) {
throw "newToken.save failed";
});
};

Related

Error: Stripe: Argument "intent" must be a string, but got: undefined

I'm trying to integrate the stripe payment gateway to my event application. I'm currently only implementing the server side, and the code below is the attend to the event functionality in the userController.js script, which should, upon clicking on the register to the event button, redirect the user to the checkout page, where the user enters his/her card details. Once the payment is completed, the user should be added to the attendees list and so on. However, I encounter an error that I'm unable to solve. I'm just trying to learn node by learning by doing, so any help would be really appreciated!
Code:
// #desc Register to an event
// #route POST /api/events/attend
// #access Private
const attendToEvent = asyncHandler(async (req, res) => {
const {eventID} = req.body
const paymentSuccessful = false
// Check for event
validEventID = mongoose.Types.ObjectId.isValid({eventID});
const event = await Event.findOne({_id:eventID})
const user = req.user
if (event == null) {
res.status(400)
throw new Error('Invalid Event id.')
}
registered = await User.findOne({attendsTo: event._id})
console.log(`user registered: ${registered}`)
if (registered != null) {
res.status(400)
throw new Error('You are already registered to this event.')
}
var customer = await stripe.customers.create({
name: user.username,
email: user.email,
source: req.body.stripeToken
})
const paymentMethods = await stripe.paymentMethods.list({
customer: customer.id,
type: 'card',
});
//Get create customer on stripe and get the payment
stripe.paymentIntents.create({
amount:1000,
currency: 'usd',
customer: customer.id,
payment_method: paymentMethods.id
}).then(function(confirm) {
return stripe.paymentIntents.confirm(
paymentMethods.id,
{payment_method: 'pm_card_visa'}
)
}).then(function(result) {
if (result.error) {
return console.log('Payment unsuccessful')
} else {
paymentSuccessful = true
return paymentSuccessful
}
})
if (!registered && paymentSuccessful) {
User.findOneAndUpdate(
{ _id: user._id },
{ $push: { attendsTo: event._id } },
function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
});
Event.findOneAndUpdate(
{_id: event._id},
{$push: {attendees: user._id}},
function (error, success) {
if (error) {
console.log(error);
}
else {
console.log(success);
}
}
);
//res.render("../completed.html")
res.status(201).send(`Successfully registered to event: ${event.eventname}`)
}
})
Error:
Error: Stripe: Argument "intent" must be a string, but got: undefined (on API request to `POST /payment_intents/{intent}/confirm`)
In your PaymentIntent confirmation call, you are incorrectly passing in a PaymentMethod Id instead of a PaymentIntent Id
}).then(function(confirm) {
return stripe.paymentIntents.confirm(
paymentMethods.id, // <--- Here
{payment_method: 'pm_card_visa'}
)
})
Look at Stripe's Doc, you will see the Id should be in format of pi_xxx, which is a Payment Intent Id format. In this case it should be confirm.id.

Separating Mongoose code from Express Router

So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..
Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function
Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {

ExpressJS variable undefined

I have an ExpressJS app that when a user makes a POST request to a route, it should lookup the ID in the MongoDB using req.params.formId
I have some console.log statements tfor debugging and so I can see what info is being returned.
The route should lookup the ID passed and when it finds it, use the req.body data and also a field from the MongoDB document but this just seems to return as undefined
Here is the code for the route:
app.post("/api/v1/forms/:formId", (req, res) => {
const { name, email, message } = req.body;
console.log(req.body);
Form.findById(req.params.formId, Form.recipient, err => {
if (err) {
res.send(err);
} else {
const formRecipient = Form.recipient;
const newForm = {
name,
email,
message,
recipient: formRecipient
};
console.log(newForm);
const mailer = new Mailer(newForm, contactFormTemplate(newForm));
try {
mailer.send();
res.send(req.body);
} catch (err) {
res.send(err);
}
}
});
});
So an example, if I make a POST request to localhost:5000/api/v1/forms/5ad90544883a6e34ec738c19 the console.log of newForm shows { name: ' Mr Tester',
email: 'person#example.com',
message: 'Hi there',
recipient: undefined }
The forms Mongoose schema has a field named recipient
the correct way is to provide the fields you want to get as the second argument:
Form.findById(req.params.formId, 'recipient', (err, form) => {
if (err) {
// error handling code
} else {
const formRecipient = form.recipient;
}
...
});
here's the Docs

Amazon Cognito newPasswordRequired await for page load

I am implementing Amazon Cognito User Pool authentication in my web app, and I ran in this problem - how to redirect user on newPasswordRequired callback to '/new-password' and await for inputs?
So in other words, my expected flow is User logs in with temporary password(here I fire cognitoUser.authenitcateUser), after that I redirect user to '/new-password' route and user sees the new password form(newPasswordRequired callback is triggered here).
Problem now is that SDK expects me to pass passwords in the newPassword callback but I don't know them yet(since the user will put it in new password form).
Code:
async login(values) {
const details = {
Username: values.username,
Password: values.password,
};
const authDetails = new AuthenticationDetails(details);
const userData = {
Username: details.Username,
Pool: poolData,
};
const cognitoUser = await new CognitoUser(userData);
await cognitoUser.authenticateUser(authDetails, {
onSuccess: (result) => {
console.log(userData);
axios.defaults.headers.common.Authorization = result.getIdToken().getJwtToken();
browserHistory.push('/entities');
},
onFailure: (error) => {
console.log(userData);
throw new Error(error);
},
newPasswordRequired: () => {
browserHistory.push('/new-password');
console.log(cognitoUser);
console.log(cognitoUser.getAuthenticationFlowType(), 'YOU NEED TO CHANGE PASSWORD');
const userData = {
Username: cognitoUser.username,
Pool: poolData,
};
cognitoUser.completeNewPasswordChallenge(
values.newPassword,
{},
{
onSuccess: (user) => {
console.log('success', user);
},
onFailure: (error) => {
console.log(error);
},
},
);
},
});
}
Another thing I tried was to create separate method which is responsible for calling cognitoUser.completeNewPasswordChallenge but then User Pool thinks that I am not authenticated.
My react component looks like this:
<form onSubmit={this.props.handleSubmit(AWSApi.login.bind(this))}></form>
You need cognitoUser.Session for do cognitoUser.completeNewPasswordChallenge call after redirection. You can keep cognitoUser.Session in localstorage or set as a get parameter (yoururl?session=cognitoUser.Session) before redirection. Then in new page you can create new cognitoUser and set your old response session.
e.g.:
var userPool = new AmazonCognitoIdentity.CognitoUserPool(POOL_DATA);
var userData = {
Username: username,
Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.Session = <your saved session>
cognitoUser.completeNewPasswordChallenge(....

401 - unauthorized access : Auth0 and azure

I have connected my Database to auth0 and when I try the connection is returns 401 unauthorized access. I have allowed auth into my firewall and the password is correct. How come it is returning this error when searching for username and password?
More info
in my easy tables I made them authenticated access only, do I have to do something to get around this?
function login(email, password, callback) {
//this example uses the "tedious" library
//more info here: http://pekim.github.io/tedious/index.html
var Connection = require('tedious#1.11.0').Connection;
var Request = require('tedious#1.11.0').Request;
var TYPES = require('tedious#1.11.0').TYPES;
var connection = new Connection({
userName: 'username',
password: 'pass',
server: 'server',
options: {
database: 'db',
encrypt: true,
rowCollectionOnRequestCompletion: true
}
});
var query = "SELECT Id, Email, Password " +
"FROM user WHERE Email = #Email";
connection.on('debug', function (text) {
// Uncomment next line in order to enable debugging messages
// console.log(text);
}).on('errorMessage', function (text) {
console.log(JSON.stringify(text, null, 2));
return callback(text);
}).on('infoMessage', function (text) {
// Uncomment next line in order to enable information messages
// console.log(JSON.stringify(text, null, 2));
});
connection.on('connect', function (err) {
if (err) { return callback(err); }
var request = new Request(query, function (err, rowCount, rows) {
if (err) {
callback(new Error(err));
} else if (rowCount < 1) {
callback(new WrongUsernameOrPasswordError(email));
} else {
bcrypt.compare(password, rows[0][2].value, function (err, isValid) {
if (err) { callback(new Error(err)); }
else if (!isValid) { callback(new WrongUsernameOrPasswordError(email)); }
else {
callback(null, {
user_id: rows[0][0].value,
email: rows[0][1].value
});
}
});
}
});
request.addParameter('Email', TYPES.VarChar, email);
connection.execSql(request);
});
}
Since you are using Azure Mobile App, which included the Node.js server SDK for your app. Then you don't need to install tedious to work with Azure SQL database. The SDK has already wrapped up mssql to do this. So basically you can use this code sample to connect your database.
var api = {
// an example of executing a SQL statement directly
get: (request, response, next) => {
var query = {
sql: 'UPDATE TodoItem SET complete = #completed',
parameters: [
{ name: 'completed', value: request.query.completed }
]
};
request.azureMobile.data.execute(query)
.then(function (results) {
response.json(results);
});
},
// an example of executing a stored procedure
post: (request, response, next) => {
var query = {
sql: 'EXEC completeAllStoredProcedure #completed',
parameters: [
{ name: 'completed', value: request.query.completed }
]
};
request.azureMobile.data.execute(query)
.then(function (results) {
response.json(results);
});
}
};
module.exports = api;
For more information, please refer to this documentation.

Categories