Passport.js -- how to use render email asynchronously - javascript

I'm running Express with Sequelize/MariaDB and Passport.js for user authentication.
I'm in the signup part (which works) but I can't seem to render and return an activation email asking them to confirm their email.
passport.js (containing authentication strategies)
passport.use('user-signup-email', new LocalStrategy({
//Process/validate input, check database for existing emails, create user, add to database...
...
if (newUser) {
var token = jwt.sign( { data: newUser.id }, newUser.login_pass + "-" + newUser.account_created);
var URLdata = {
id: newUser.id,
expiration_time: Date.now() + 86400000, //24 hours
url: token,
email_info: mail.createActivationEmail(req.app, newUser.login_key, newUser.user_alias, token)
};
console.log("info: " + URLdata.email_info);
...
//Store dynamic URL and email contents (in case of resending) in database with expiration time
//And then send the email that was just rendered
}
mail.js
exports.createActivationEmail = (app, recipientAddress, userName, url) => {
app.render('emails/activation_email', {
layout: false,
page_title: 'Please confirm your account!',
dynamic_url: url
}, function(err, rendered) {
if (err) {
console.log("Q [" + err + "]");
}
console.log("R " + rendered.toString());
return {
from: adminEmailAddress,
to: recipientAddress,
cc: false,
bcc: false,
subject: 'Welcome to example.com ' + userName + '!',
html: rendered.toString(),
text: "TO DO"
};
});
};
The last console.log in passport.js displays "info: undefined."
But if I print the output in the mail.js module before returning it, it is fine.
I'm guessing it's an async problem? How would I fix it?
I'm still a little unclear on promises and async-await blocks in this context.
Thanks in advance for any help you can offer!

You misunderstood callback functions.
callbacks are (should, when you write them) asynchron:
https://nemethgergely.com/async-function-best-practices/
How to write asynchronous functions for Node.js
I changed your createActivationEmail function.
The last argument is now a callback, thats get invoked when your code app.redner is done.
passport.use('user-signup-email', new LocalStrategy({
//Process/validate input, check database for existing emails, create user, add to database...
// ...
if(newUser) {
var token = jwt.sign({ data: newUser.id }, newUser.login_pass + "-" + newUser.account_created);
mail.createActivationEmail(req.app, newUser.login_key, newUser.user_alias, token, (err, email_info) => {
var URLdata = {
id: newUser.id,
expiration_time: Date.now() + 86400000, //24 hours
url: token,
email_info
};
console.log("info: " + URLdata.email_info);
//...
//Store dynamic URL and email contents (in case of resending) in database with expiration time
//And then send the email that was just rendered
});
}
}));
exports.createActivationEmail = (app, recipientAddress, userName, url, done) => {
app.render('emails/activation_email', {
layout: false,
page_title: 'Please confirm your account!',
dynamic_url: url
}, function(err, rendered) {
if (err) {
console.log("Q [" + err + "]");
cb(err);
return;
}
console.log("R " + rendered.toString());
done(null, {
from: adminEmailAddress,
to: recipientAddress,
cc: false,
bcc: false,
subject: 'Welcome to example.com ' + userName + '!',
html: rendered.toString(),
text: "TO DO"
});
});
};

Related

Google Cloud Function frozen for over minute

have a strange thing happening running a Google cloud function. The function starts and logs the user id and job id as expected. Then it calls firestore db and basically sits there for 1 minute, sometimes 2 before it executes the first call... It was even timing out on 240 seconds.
const AWS = require('aws-sdk');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.run = functions.https.onCall((data, context) => {
var id = data.id;
var userid = data.uid;
var retry = data.retry;
var project;
var db = admin.firestore();
var storage = admin.storage();
console.log("Starting Collect");
console.log("UID: " + userid);
console.log("id ID: " + id);
// Times out on this call
db.collection("users").doc(userid).collection("ids").doc(id).get().then(function(doc) {
console.log("Loaded DB");
project = doc.data();
createexport();
}).catch(function(err) {
console.log(err);
error('Loading DB Error, ' + err, false);
});
function createexport() {
db.collection("exports").doc(id).set({
status: 'Collecting',
stage: 'Export Checker',
percent: 0,
id: id,
}).then(function() {
console.log("Creating Export");
setdb();
}).catch(function(err) {
error("Error creating export in database :" + err, true)
});
}
function setdb() {
db.collection("users").doc(userid).collection("ids").doc(id).update({
status: 'Analyzing Files',
stage: 'Collecting'
}).then(function() {
getaudio();
}).catch(function(err) {
error("Error updating users id in database :" + err, true)
});
}
function getaudio() {
const from = userid + '/projects/' + project.originalproject.id + '/audio.' + project.originalproject.extension;
const to = userid + '/' + id + '/audio.' + project.originalproject.extension;
storage.bucket('---------').file(from).copy(storage.bucket('---------').file(to)).then(function() {
console.log("Collecting files");
copy2();
}).catch(function(err) {
error('Collecting Audio Error, ' + err, true);
});
}
function copy2() {
const from = userid + '/projects/' + project.originalproject.id + '/overlay.png';
const to = userid + '/' + id + '/overlay.png';
storage.bucket('--------.appspot.com').file(from).copy(storage.bucket('---------').file(to)).then(function() {
updateexport();
}).catch(function(err) {
error('Collecting Overlay Error, ' + err, true);
});
}
function updateexport() {
db.collection("exports").doc(id).update({ status: "Waiting" }).then(function() {
console.log("All files collected");
return { status: 'Success' };
}).catch(function(err) {
error("Error creating export entry in database :" + err, true)
});
}
function error(evt, evt2) {
AWS.config.update({ region: "us-east-1" });
var html;
var sub = 'Error with id ' + id;
console.log(evt);
if (evt2) {
db.collection('users').doc(userid).collection('ids').doc(id).update({
status: 'Error'
}).catch(function(err) {
console.log(err);
});
db.collection("exports").doc(id).update({
status: 'Error',
stage: 'Collecting',
error: evt,
}).catch(function(err) {
console.log(err);
});
html = `
Username: ${project.username} <br>
UserID: ${userid} <br>
Email: ${project.email} <br>
id: ${id}
`
} else {
html = `id: ${id}<br>
UserID: ${userid} <br>
Message: Error logged was: ${evt}
`
}
var params = {
Destination: {
ToAddresses: [
'errors#mail.com'
]
},
Message: {
Body: {
Html: {
Charset: "UTF-8",
Data: html
},
},
Subject: {
Charset: 'UTF-8',
Data: sub
}
},
Source: 'errors#mail.com',
ReplyToAddresses: [
project.email
],
};
var sendPromise = new AWS.SES({
apiVersion: "2010-12-01",
"accessKeyId": "-----------",
"secretAccessKey": "------------------------",
"region": "--------",
}).sendEmail(params).promise();
sendPromise.then(function(data) {
return { data: data };
}).catch(function(err) {
return { err: err };
});
}
});
Seems to me to be way too long for a database call of only a few kb. I will attach the cloud log to show time difference. After this initial slump it then performs as expected.
Cloud log image
Anyone got any ideas as to why this could be happening? Many thanks...
Your function is appearing to hang because it isn't handling promises correctly. Also, it doesn't appear to be sending a specific response to the client app. The main point of callable functions is to send a response.
I suggest reviewing the documentation, where you will learn that callable functions are required to return a promise that resolves with an object to send to the client app, after all the async work is complete.
Minimally, it will take a form like this:
return db.collection("users").doc(userid).collection("files").doc(id).get().then(function(doc) {
console.log("Loaded DB");
project = doc.data();
return { "data": "to send to the client" };
}
Note that the promise chain is being returned, and the promise itself resolves to an object to send to the client.

Javascript Tokens, JWT and numbers when decoded

I am totally new on using JWT and tokens; I just tried to do this:
console.log("logged " + data);
//prints { _id: 5a82ee98e918b22e83d6c3e0,
//username: 'test2',
//name: 'test',
//surname: '2',
//password: '...'
//[etc]
//}
console.log("logged _id " + data._id);
//prints 5a82ee98e918b22e83d6c3e0
var token = jwt.sign(data._id, secret, {
expiresIn: "24h" // expires in 24 hours
});
console.log("saved token " + token);
//prints the token in style eyJhbGciOi[...].eyJfYnNvbn[...].usoLRz-[...]
console.log("decoded: " + JSON.stringify( jwt.decode(token) ) )
//prints the token in this way:
//{"_bsontype":"ObjectID","id":{"type":"Buffer","data":[90,130,238,152,233,24,178,46,131,214,195,224]},"iat":1519502719,"exp":1519589119}
why it does not prints the id number in plain text?
how can i get the id number i put in the token?
UPDATE:
I am in a login function; data is the answer after login, and it contains the user logged in; i have
var login = function(req, res) {
passport.authenticate('local', function(err, data, info) {
if (err) { console.log(err);
}
console.log("data in auth " + data);
if (!data) {
return res.status(404);
}
req.logIn(data, function(err) {
if (err) {
console.log("err " + err);
}
console.log("logged " + data);
console.log("logged _id " + data._id);
var token = jwt.sign[continues on top]
[...]
}
You are not decoding your token correctly
let decodedData = JWT.decode(token,secret);
console.log(decodedData._id);
No need to stringify your data. It will work fine. If you get any error, contact me anytime.
Solved.
Problem was that i put in the token data._id directly as string; as this link says, token payload must be built this way:
{
"_id" : data._id
}
so I do NOT have to do this:
console.log("logged _id " + data._id);
//WRONG
var token = jwt.sign( data._id, secret, {
expiresIn: "24h"
});
but I do have to do THIS WAY:
console.log("logged _id " + data._id);
var myId = {
"_id" : data._id
}
var token = jwt.sign( myId, secret, {
expiresIn: "24h"
});
so now if I use
let decodeddata = jwt.decode(token,secret);
console.log("decoded: " + JSON.stringify(decodeddata,null,4) )
then THIS WORKS.
Thanks to all for helping me finding the issue!

aws node.js pass contents of xml as JSON stringify

Node.js 6.10
Process is as follows...
1) Dropped an XML file into an S3 bucket.
2) Triggered a lambda,
3) Called a step function, which calls another lambda
4) This lambda captures the XML from the bucket with
var s3Bucket = new aws.S3( { params: {Bucket: 'bucketName'} } );
var xmlFile = s3Bucket.getObject('fileName.xml');
5) send an email with the contents of the XML as a string
let index = function index(event, context, callback) {
var fileName = event.fileName;
var bucketName = event.bucketName;
var todaysDate = event.todaysDate;
var eParams = {
Destination: {
ToAddresses: ["emailAddress"]
},
Message: {
Body: {
//Text: { Data: 'file: ' + fileName + ' bucketName: ' + bucketName + ' todaysDate: ' + todaysDate}
Text: { Data: 'file: ' + JSON.stringify(xmlFile)}
},
Subject: {
Data: "Email Subject!!!"
}
},
Source: "emailAddress"
};
console.log('===SENDING EMAIL===');
var email = ses.sendEmail(eParams, function(err, data){
if(err) console.log(err);
else {
console.log("===EMAIL SENT===");
console.log(data);
console.log("EMAIL CODE END");
console.log('EMAIL: ', email);
context.succeed(event);
}
});
};
module.exports.init = (event, context, callback) => {
};
exports.handler = index;
I know the sending an email works because if I uncomment the line
Text: { Data: 'fileName: ' + fileName + ' bucketName: ' + bucketName + ' todaysDate: ' + todaysDate}
and comment out Text: { Data: 'file: ' + JSON.stringify(xmlFile)}
it sends the email with the correct filename, bucketName, and date
So when I try to include Text: { Data: 'file: ' + JSON.stringify(xmlFile)}
the logs show the error
ypeError: Converting circular structure to JSON at Object.stringify (native) at index (/var/task/index.js:39:51)
UPDATE
Thanks #Michael & #Khand for the replies. I have tried what you suggested
var params = {
Bucket: "bucketName",
Key: "fileName.xml"
};
s3.getObject(params, function(err, data)
{
if (err)
{
console.log(err, err.stack);
}// an error occurred
else
{
console.log("Returned data object " + data); // successful response
console.log("Returned xml " + data.body);
}
console is returning
Returned data object [object Object]
Returned xml undefined
and yes the bucket does contain the named file. The [object, object] is populated but the body tag is undefined

send email with gmail api not working

I am trying to send an email, by using the google api in node.js
var sendmsg = function(auth) {
var to = 'foo#gmail.com',
subject = 'Hello World',
content = 'send a Gmail.'
var email = "To: "+ to +"\r\n"+
"Subject: "+subject+"\r\n"+
content;
var base64EncodedEmail = new Buffer(email).toString('base64');
var gmail = google.gmail('v1');
var request = gmail.users.messages.send({
'userId': auth,
'message': {
'raw': base64EncodedEmail
}
}, function (err, result) {
console.log('result'+result);
});
};
I took this example from the quick start sample in google's documentation, that reads the labels in my email account(which worked fine). And I just changed the scopes to:
var SCOPES = ['https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.send'];
And created that var = email
var to = 'foo#gmail.com',
subject = 'Hello World',
content = 'send a Gmail.'
var email = "To: "+ to +"\r\n"+
"Subject: "+subject+"\r\n"+
content;
Then I am just trying to use the gmail.users.messages.send method.. But when running the result is returning the following:
<HTML>
<HEAD>
<TITLE>Bad Request</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<H1>Bad Request</H1>
<H2>Error 400</H2>
</BODY>
</HTML>
Any idea what I am missing? I think the way I am creating my var 'email' is wrong, but I am not sure how it should be
The value of the userId-field has to be the senders email address (or me for short), the auth-object has to be passed in the auth field, and the message should be passed in the resource-field. Your message lacks a From header and an extra new line before the content to be valid. The message also has to be base64url-encoded:
function sendMessage(auth, from, to, subject, content) {
// The Gmail API requires url safe Base64
// (replace '+' with '-', and '/' with '_')
var encodedEmail = new Buffer(
'From: ' + from + '\r\n' +
'To: ' + to + '\r\n' +
'Subject: ' + subject + '\r\n\r\n' +
content
).toString('base64').replace(/\+/g, '-').replace(/\//g, '_');
var gmail = google.gmail('v1');
var request = gmail.users.messages.send({
auth: auth,
userId: 'me',
resource: {
raw: encodedEmail
}
}, function (err, result) {
console.log('result:', result);
});
};
Instead of constructing the body yourself I'd highly reccomend using Nodemailers system:
const sendMail = async () => {
const mail = await new MailComposer({
to: ...,
from: ...,
subject: ...,
html: ...,
});
const message = await mail.compile().build();
const encodedMessage = message
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
await gmail.users.messages.send({
userId: 'me',
requestBody: { raw: encodedMessage },
});
}

Redirecting after all functions finished

I am using the Parse hosting and Cloud Code functions.
I have a button that runs a function and then redirects to the same page to refresh it after the function has been called. The cloud function that is being called by the button then calls a number of different functions from there, including a httpRequest. From what I can see, the page is refreshing after the first function has been called, not the subsequent functions and httpRequests being called later. The data on the loaded page is still displaying old data, and has to be refreshed manually to see the updated data.
Here is the code the button is triggering:
// User Control Panel -- Logic
app.post('/panel', function(req, res) {
var currentUser = Parse.User.current();
if (currentUser) {
currentUser.fetch().then(function(fetchedUser){
var username = fetchedUser.getUsername();
if (fetchedUser.get("timeRemaining") < 10) {
res.redirect("/panel");
} else if (fetchedUser.get("isRunning") == false){
Parse.Cloud.run("dockerManager", {username: username}) // Ignoring the rest of the code, this is where the cloud function is called.
res.redirect("/panel");
} else {
res.redirect("/panel");
}
}, function(error){
});
} else {
res.redirect('/panel');
}
});
This is the cloud function that is running:
Parse.Cloud.define("dockerManager", function(request, response) {
var username = request.params.username;
var override = request.params.override;
var containerID = request.params.containerID;
//other irrelevant code here
} else {
Parse.Cloud.useMasterKey();
var query = new Parse.Query(Parse.User);
query.equalTo("username", username);
query.first(function(user) {
if (user.get("dockerID") == undefined) {
Parse.Cloud.run("createDockerContainer", {username: username});
response.success("[Docker Manager] Created Docker Container for username: " + username + " with Docker ID: " + user.get("dockerID"));
} else if (user.get("isRunning") == true) {
Parse.Cloud.run("stopDockerContainer", {username: username});
response.success("[Docker Manager] Stopped Docker Container for username: " + username + " with Docker ID: " + user.get("dockerID"));
} else if (user.get("isRunning") == false) {
if (user.get("timeRemaining") >= 10){
Parse.Cloud.run("startDockerContainer", {username: username});
response.success("[Docker Manager] Started Docker Container for username: " + username + " with Docker ID: " + user.get("dockerID"));
} else {
response.error("noTime");
}
}
});
}
});
Each of the functions this is calling send a httpReqest to another server, as shown below:
Parse.Cloud.define("stopDockerContainer", function(request, response) {
var username = request.params.username;
//irrelevant code
containerID = user.get("dockerID");
Parse.Cloud.httpRequest({
method: "POST",
url: "http://[redacted address]/containers/" + containerID + "/stop",
headers: {
"Content-Type": "application/json"
},
success: function(httpResponse) {
console.log("[Docker Stopper] Stopped Docker container for user: " + username + " with ID: " + containerID);
user.set("isRunning", false);
user.save();
response.success(true);
},
error: function(httpResponse) {
console.log("[Docker Stopper][CRITICAL] Error stopping docker container for username:" + username);
console.log("Request failed with response code " + httpResponse.status);
response.error(false);
}
});
});
});
Any ideas?

Categories

Resources