Azure communication services - azure-communication-services

I am using azure communication services in my react app to send email.
But It is giving CORS error
import { EmailClient } from "#azure/communication-email";
function App() {
const connectionString =
"**************************************************************************************";
const client = new EmailClient(connectionString);
const sender = "1000055393#hexaware.com";
const emailContent = {
subject: "Send email quick start test- JS sample",
plainText:
"Test Email from JS Send Email Sample Application\n\n This email is part of testing of email communication service. \\n Best wishes",
html: "<html><head><title>ACS Email as a Service</title></head><body><h1>ACS Email as a Service - Html body</h1><h2>This email is part of testing of email communication service</h2></body></html>",
};
const toRecipients = {
to: [{ email: "krnsda04#gmail.com", displayName: "Karan S" }],
};
async function main() {
try {
const emailMessage = {
sender: sender,
content: emailContent,
recipients: toRecipients,
};
console.log(sender,"sender");
const sendResult = await client.send(emailMessage);
console.log(sendResult,"result");
if (sendResult && sendResult.messageId) {
// check mail status, wait for 5 seconds, check for 60 seconds.
const messageId = sendResult.messageId;
if (messageId === null) {
console.log("Message Id not found.");
return;
}
console.log("Send email success, MessageId :", messageId);
let counter = 0;
const statusInterval = setInterval(async function () {
counter++;
try {
const response = await client.getSendStatus(messageId);
if (response) {
console.log(
`Email status for {${messageId}} : [${response.status}]`
);
if (response.status.toLowerCase() !== "queued" || counter > 12) {
clearInterval(statusInterval);
}
}
return;
} catch (e) {
console.log("Error in checking send mail status: ", e);
}
}, 5000);
} else {
console.error(
"Something went wrong when trying to send this email: ",
sendResult
);
}
return;
} catch (e) {
console.log(
"################### Exception occoured while sending email #####################",
e
);
}
}
main();
return <h3>hello ${connectionString}</h3>;
}
export default App;
But when running this code, I m getting this error.
Access to XMLHttpRequest at 'https://************************/emails:send?' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
In Azure docs, they have given the code in Node.js.
Since Iam using that code in react and accessing from front end, is it showing CORS error?
How to solve this issue?

As you can see in the SDKs and REST APIs docs, ACS Email library is meant to be used in a trusted service environment. This is because there is no reasonable way to protect your connection string in the browser.
In your case, building a Node.js backend and letting your React app talk to it would be a good and preferred solution.

Related

TypeError: [function] is not a function in Passport local strategy

I'm trying to authenticate users locally with Passport.js while not keeping session and using my own JWTokens.
I was following this tutorial:
Learn using JWT with Passport authentication
While also reading Passport.js documentation. I don't know what went wrong, but passport doesn't seem to notice that some functions are indeed functions.
I've got a Load function to select a User from the DB(mongo) given certain criteria(a user might logIn with e-mail or phone number).
load: function(options, cb) {
options.select = options.select || 'email phone';
return this.findOne(options.criteria)
.select(options.select)
.exec(cb);
}
I'm calling passport.authenticate in my routes:
// Controllers //
const Users = require('../../app/controllers/users');
...
...
app.post('/api/login', passport.authenticate('local', { failureRedirect: '/api/login' }), Users.login);
And here's my local strategy:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
{
usernameField: 'email',
phoneField: 'phone',
passwordField: 'password',
session: false
},
function(email, phone, password) {//cb == callback
const options = {
criteria: { email: email, phone: phone },
select: 'name username email hashed_password salt'
};
User.load(options, function(err, user) {
if (err || !user){
return res.status(400).json({
type: 'failure',
message: "User creation failed",
data: [err]
});
};
if (!user.authenticate(password)) {
return res.status(400).json({
type: 'failure',
message: "User creation failed",
data: [err]
});
};
req.login(user, {session: false}, (err) => {
if (err) {
res.send(err);
}
// generate a signed son web token with the contents of user object and return it in the response
const token = jwt.sign(user.id, 'your_jwt_secret');
return res.json({user, token});
});
});
}
));
I'm getting the following error:
TypeError: res.status is not a function
Before trying to get stuff back from passport with responde. I was trying to do it with cb(callback), as done in the tutorial, but I keep getting the same error.
Thanks in advance for any help!
There are a few issues with how you are implementing Passport's local strategy that are causing problems.
You are trying to use two fields as the username when Passport's local startegy only accepts one. (see: http://www.passportjs.org/packages/passport-local/)
function(username, password, done){}
If you want to use both as a username, you might want to consider creating your own custom strategy. This is going to be a little more in-depth, but you can start learning on the Passport Github page (https://github.com/jaredhanson/passport-strategy)
The second issue is that you are trying to get Passport to send a response in the local strategy which is not what it is intended to do. Instead, you should be passing the errors and return values to Passport's done() function, which will process them accordingly.
Here is an example of what your local strategy should look like:
passport.use(
new LocalStrategy(async (email, phone, password, done) => {
const options = {
criteria: { email, phone },
select: 'name username email hashed_password salt',
};
try {
const user = await User.load(options);
/**
* If null is returned meaning there was no user found, send the done call
* with the false flag. This tells passport to redirect to the failure URL.
*/
if (!user) {
return done(null, false);
}
/**
* If the user's password is incorrect, also return the done function with the false
* flag. This tells passport to redirect to the failure URL.
*/
if (!user.authenticate(password)) {
return done(null, false);
}
/**
* If a user is found and their password is verified, send the user object to
* the done function. This will tell Passport to call the next middelware attaching
* the user object.
*/
return done(null, user);
} catch (err) {
/**
* If there is an error with the DB call, return generic message
* for security purposes.
*/
return done('There was an internal server error.');
}
})
);
and an example of what your load function should look like:
load: options => {
return new Promise(async (resolve, reject) => {
options.select = options.select || 'email phone';
try {
const user = await this.findOne(options.criteria)
.select(options.select)
.exec();
resolve(user);
} catch (err) {
reject(err);
}
});
};
As a general best practice, I changed your callbacks to the newer method of promises (https://developers.google.com/web/fundamentals/primers/promises).
This should work in the way you are intending to use Passport.

Problem with using nodemailer to send email to user on Actions on Google, Dialogflow

I am using nodemailer to send emails to users, when an intent is triggered.
I am creating a promise for the function to send emails using nodemailer.
When running with node index.js, the emails are sent, but when deploying to firebase and invoking the intent with the simulator on actions on google console, i get a webhook error
MalformedResponse
Failed to parse Dialogflow response into AppResponse because of empty speech response.
Here is my code:
When the user invokes this intent, they should receive an email from me.
let sendMails = () => {
return new Promise((resolve, reject) => {
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'my email',
pass: 'password'
}
});
let mailOptions = {
from: 'my email',
to: 'user's email',
subject: 'something',
text: 'here is the email'
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
throw new Error("Error has come");
} else {
let answer = info.response;
return resolve(answer);
// console.log('Email sent: ' + info.response);
}
});
}).catch(error => {
console.log(error);
throw new Error("Error is coming")
})
}
And here is the intent:
app.intent('nda - yes', conv => {
return sendMails().then((answer)=> {
return conv.ask(`<speak>Done. Email is sent. Check your inbox.</speak>`)
})
})`

NodeMailer queuing outgoing email, but email never sends

I'm trying to send an email from my own domain without using an external SMTP server. I'm using NodeMailer's SMTP connection:
let options = {
secure: true,
port: consts.portOut,//465
host: consts.host, //mydomain.com
transactionLog: true,
debug: true,
requireTLS: true,
authMethod: 'PLAIN',
};
let connection = new SMTPConnection(options);
connection.connect(function() {
let auth = {
user: 'abc',
pass: 'def'
};
connection.login(auth, function(err) {
if (err) {
console.log("Authentication failed!", err);
}
console.log("Authentication to SMTP server successful.");
let envelope = {
from: 'fee#mydomain.com',
to: 'myemail#gmail.com'
};
let message = 'message hello world';
connection.send(envelope, message, function(err, info) {
if (err) {
console.log("err:::", err);
} else {
console.log('info?', info);
//connection.quit();
}
});
connection.quit();
});
});
connection.on("error", function(err) {
console.log(err);
});
My server code using NodeMailer's SMTP Server:
const options = {
secure: true,
size: 25000000, //25MB
authMethods: ['PLAIN'],
key: hskey,
cert: hscert,
ca: [hschain],
onAuth(auth, session, callback) {
if(auth.username !== 'abc' || auth.password !== 'def') {
return callback(new Error('Invalid username or password'));
}
callback(null, {user: 123}); // where 123 is the user id or similar property
},
onConnect(session, callback) {
console.log("the address is:", session.remoteAddress)
if (session.remoteAddress === consts.ip) {
return callback(); // Accept the address
} else {
return callback(new Error('Only connections from %s allowed', consts.ip));
}
},
onData(stream, session, callback) {
simpleParser(stream, (err, parsed) => {
if(err) {
console.error(err);
} else {
console.log(parsed);
}
});
stream.on('end', function () {
let err;
if(stream.sizeExceeded){
err = new Error('Message exceeds fixed maximum message size');
err.responseCode = 552;
return callback(err);
}
callback(null, 'Message queued as abcdef');
});
}
};
const emailServer = new SMTPServer(options);
emailServer.listen(consts.portOut, function () {
processSMTPConnection(consts, hskey);
});
emailServer.on("error", function (err) {
console.error("Error %s", err.message);
});
So after my client connects to my local SMTP server, the last message I get is 'Message queued as abcdef' and nothing ever sends (nothing ever arrives in my gmail inbox or any other email testing services)...
No incorrect ports are blocked, so I must be missing something(?).
Is this not how to correctly use NodeMailer?
Should I be able to send emails from my local domain using NodeMailer?
Documentation here has a note that states:
This module does not make any email deliveries by itself. smtp-server
allows you to listen on ports 25/24/465/587 etc. using SMTP or LMTP
protocol and that’s it. Your own application is responsible of
accepting and delivering the message to destination.
(emphasis mine)
Your server seems to accept the email (that's why it's showing that the message has been queued) but it doesn't delivers to destination.
To expand a little bit on how to send the message once it arrives to your SMTP server. If the TO address is local, just put the message in their mailbox. But if you want to "remail" the message, you need to contact the TO mail exchange with the message.
Something like:
const toExchange = getMX(parsed.to);
const outMessage = createMessageFromParsed(parsed);
const transporter = createTransport({
port: 25,
host: toExchange,
name: os.hostname(),
});
transporter.sendMail(outMessage);

AWS Lambda - MongoDB resource optimization

I'm building facebook chatbot using AWS Lambda and MongoDB. At the moment, my application is pretty simple but I'm trying to nail down the basics before I move onto the complex stuff.
I understand AWS Lambda is stateless but I've read adding below line in handler along with variables initialized outside handler, I don't have to establish DB connection on every request.
context.callbackWaitsForEmptyEventLoop = false;
(I've read this from this article; https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs)
I'm adding my entire code below
'use strict'
const
axios = require('axios'),
mongo = require('mongodb'),
MongoClient = mongo.MongoClient,
assert = require('assert');
var VERIFY_TOKEN = process.env.VERIFY_TOKEN;
var PAGE_ACCESS_TOKEN = process.env.PAGE_ACCESS_TOKEN;
var MONGO_DB_URI = process.env.MONGO_DB_URI;
let cachedDb = null;
let test = null;
exports.handler = (event, context, callback) => {
var method = event.context["http-method"];
context.callbackWaitsForEmptyEventLoop = false;
console.log("test :: " + test);
if (!test) {
test = "1";
}
// process GET request --> verify facebook webhook
if (method === "GET") {
var queryParams = event.params.querystring;
var rVerifyToken = queryParams['hub.verify_token']
if (rVerifyToken === VERIFY_TOKEN) {
var challenge = queryParams['hub.challenge'];
callback(null, parseInt(challenge))
} else {
var response = {
'body': 'Error, wrong validation token',
'statusCode': 403
};
callback(null, response);
}
// process POST request --> handle message
} else if (method === "POST") {
let body = event['body-json'];
body.entry.map((entry) => {
entry.messaging.map((event) => {
if (event.message) {
if (!event.message.is_echo && event.message.text) {
console.log("BODY\n" + JSON.stringify(body));
console.log("<<MESSAGE EVENT>>");
// retrieve message
let response = {
"text": "This is from webhook response for \'" + event.message.text + "\'"
}
// facebook call
callSendAPI(event.sender.id, response);
// store in DB
console.time("dbsave");
storeInMongoDB(event, callback);
}
} else if (event.postback) {
console.log("<<POSTBACK EVENT>>");
} else {
console.log("UNHANDLED EVENT; " + JSON.stringify(event));
}
})
})
}
}
function callSendAPI(senderPsid, response) {
console.log("call to FB");
let payload = {
recipient: {
id: senderPsid
},
message: response
};
let url = `https://graph.facebook.com/v2.6/me/messages?access_token=${PAGE_ACCESS_TOKEN}`;
axios.post(url, payload)
.then((response) => {
console.log("response ::: " + response);
}).catch(function(error) {
console.log(error);
});
}
function storeInMongoDB(messageEnvelope, callback) {
console.log("cachedDB :: " + cachedDb);
if (cachedDb && cachedDb.serverConfig.isConnected()) {
sendToAtlas(cachedDb.db("test"), messageEnvelope, callback);
} else {
console.log(`=> connecting to database ${MONGO_DB_URI}`);
MongoClient.connect(MONGO_DB_URI, function(err, db) {
assert.equal(null, err);
cachedDb = db;
sendToAtlas(db.db("test"), messageEnvelope, callback);
});
}
}
function sendToAtlas(db, message, callback) {
console.log("send to Mongo");
db.collection("chat_records").insertOne({
facebook: {
messageEnvelope: message
}
}, function(err, result) {
if (err != null) {
console.error("an error occurred in sendToAtlas", err);
callback(null, JSON.stringify(err));
} else {
console.timeEnd("dbsave");
var message = `Inserted a message into Atlas with id: ${result.insertedId}`;
console.log(message);
callback(null, message);
}
});
}
I did everything as instructed and referenced a few more similar cases but somehow on every request, "cachedDb" value is not saved from previous request and the app is establishing the connection all over again.
Then I also read that there is no guarantee the Lambda function is using the same container on multiple requests so I made another global variable "test". "test" variable value is logged "1" from the second request which means it's using the same container but again, "cachedDb" value is not saved.
What am I missing here?
Thanks in advance!
In short AWS Lambda function is not a permanently running service of any kind.
So, far I know AWS Lambda works on idea - "one container processes one request at a time".
It means when request comes and there is available running container for the Lambda function AWS uses it, else it starts new container.
If second request comes when first container executes Lambda function for first request AWS starts new container.
and so on...
Then there is no guarantee in what container (already running or new one) Lambda function will be executed, so... new container opens new DB connection.
Of course, there is an inactivity period and no running containers will be there after that. All will start over again by next request.

Facebook messenger api - Metadata on the message is not coming back in the response

I have following code to send text message with metadata. When user responds with text, metadata field on the message is empty. Is it a bug or Messenger api does not support this functionality?
function sendTextMessage(recipientId, messageText, metadata) {
var messageData = {
recipient: {
id: recipientId
},
message: {
text: messageText,
metadata: metadata,
}
};
callSendAPI(messageData);
}
function callSendAPI(messageData) {
request({
uri: 'https://graph.facebook.com/v2.6/me/messages',
qs: { access_token: PAGE_ACCESS_TOKEN },
method: 'POST',
json: messageData
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
var recipientId = body.recipient_id;
var messageId = body.message_id;
if (messageId) {
console.log("FBHook Successfully sent message with id %s to recipient %s",
messageId, recipientId);
} else {
console.log("FBHook Successfully called Send API for recipient %s",
recipientId);
}
} else {
console.error(response.error);
}
});
}
This is not how it is expected to behave. The metadata field will be returned to webhook immediately if subscribed to the "message_echoes" field. This is meant for co-ordination between multiple apps linked to the page.
From the changelog - https://developers.facebook.com/docs/messenger-platform/changelog
"New field: metadata, passed from the Send API and sent to the message_echoes callback, to help interoperability betwen multiple bots."