unable to get data sent from function call to firebase cloud functions - javascript

I have a firebase function called sendMail that is used to send emails. I am trying to pass the email address of the receiver and another parameter to the function. In my vue app I call the function as follows:
sendEmail(){
console.log(this.email)
let sendMail = firebase.functions().httpsCallable('sendMail');
sendMail(
{
"email": this.email,
"superu": this.superu
}
).then(
result => {
console.log(result)
}
)
}
And my function index.js looks like:
const functions = require('firebase-functions');
const admin = require("firebase-admin")
const nodemailer = require('nodemailer');
admin.initializeApp()
//google account credentials used to send email
var transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: '*****#****.com',
pass: '***********'
}
});
exports.sendMail = functions.https.onRequest((req, res) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Content-Type");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
console.log(req.body['data']);
const mailOptions = {
from: `•••••••••#gmail.com`,
to: req.body['data'].email,
subject: 'contact form message',
html: `<h2 style="color: teal">Order Confirmation</h2>
<a href="https://track-acquintances.firebaseapp.com/signup/${req.body.superu}">
<b> Register </b>"<br>
</a>`
};
return transporter.sendMail(mailOptions, (error, data) => {
if (error) {
return res.status(200).json({data: error.message});
}
data = JSON.stringify(data)
return res.status(200).json({data: data});
});
});
The problem is I can't access the passed email data and the function fails. I logged req.body['data'] to the functions logs and I see { email: 'xxx#xx.xxx.x', superu: true }. But I tried both req.body['data'].email and req.body['data']['email'] and they both doesn't work. And in my browsers console I get {data: "No recipients defined"}. Any help would be appreciated. Thank you

You're confusing two types of Cloud Functions:
Your Cloud Function is defined as an HTTPS triggered function, which means that you can invoke it by accessing its URL in a browser, by calling fetch, or by using XMLHTTPRequest.
Your client code, tries to invoke a so-called Callable Cloud Function, which is a different type. While Callable Cloud Functions are also invoked directly over HTTPS, they have a specific wire protocol for being invoked.
Since the two types of function don't match, your client code is passing the parameters in a different format than what the server is handling.
You'll need to either call the HTTPS function, or convert the Cloud Function to be Callable. The latter would look something like:
exports.sendMail = functions.https.onCall((data, context) => {
const email = data.email;
const superu = data.superu;
...
});

Related

Node express references error response is not defined

var _expressPackage = require("express");
var _bodyParserPackage = require("body-parser");
var _sqlPackage = require("mssql");
//Initilize app with express web framework
var app = _expressPackage();
//To parse result in json format
app.use(_bodyParserPackage.json());
***//Here we will enable CORS, so that we can access api on cross domain.***
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, contentType,Content-
Type,
Accept, Authorization");
next();
});
***//Lets set up our local server now.***
var server = app.listen(process.env.PORT || 4000, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
***//Set up your sql connection string, i am using here my own, you have to replace it with your
own.***
var dbConfig = {
user: "sa1",
password: "12345",
server: "localhost",
database: "test123"
};
***//Function to connect to database and execute query***
var QueryToExecuteInDatabase = function (response, strQuery) {
***//close sql connection before creating an connection otherwise you will get an error if
connection already exists.***
_sqlPackage.close();
//Now connect your sql connection
_sqlPackage.connect(dbConfig, function (error) {
if (error) {
console.log("Error while connecting to database :- " + error);
response.send(error);
}
else {
***//let's create a request for sql object***
var request = new _sqlPackage.Request();
//Query to run in our database
request.query(strQuery, function (error, responseResult) {
if (error) {
console.log("Error while connecting to database:- " + error);
response.send(error);
}
else {
response.send(responseResult);
}
});
}
});
}
***//GET API***
app.get("/StudentList", function(_req ,_res){
var Sqlquery = "select * from student1"; ***//tbl_studentdetails***
QueryToExecuteInDatabase(_res, Sqlquery);
});
***//call a stored procedure***
var request = new _sqlPackage.Request();
***//calling a stored procedure***
request.input('Username', _sqlPackage.VarChar(50), 'admin');
request.input('Password', _sqlPackage.VarChar(50), 'admin#123');
request.execute('sp_CheckLogin', function (err, recordsets, returnValue) {
response.send(recordsets);
});
> (D:\performalytic\9999.practice\angularpra\NodeApiWithSql\node_modules\mssql\lib\tedious\request.js:701:23)
at processImmediate (internal/timers.js:463:21)
- end snippet -->
This question could use a bit more clarity... but with the limited information provided it appears the issue you are running into here has to do with lexical scope.
Lexical scope most simply has to do with what variables the current execution context has access to. Inside of a function, you can either access variables declared within the function... or in the surrounding code. The last line of your code snipped shows a top level variable request and a method on that object called execute.
The callback you are passing the execute method has three variables (function parameters) you're naming err, recordsets, and returnValue. Yet inside that function body you're attempting to access a variable named response. If we look in the surrounding code... there is no response variable declared. (The only variable named response I see is within the QueryToExecuteInDatabase, and therefore only accessible within that function body.
Where are you getting this templated code from?

Nodemailer google cloud function error code: 304

I have a firebase google cloud function that sends an email via javascript with cors and nodemailer.
I am getting an error code: 304 in some occasions with different destination emails.
Why would this be happening occasionally and only when I change the email destination. Sometimes its works and sometimes it doesnt.
How can the condition be sometimes false by changing the email send to destination? Do i need to set the cache somehow in the function?
Allow Un-secure apps is enabled followed by Captcha Unlocked
error code: 304
notModified - The conditional request would have been successful, but
the condition was false, so no body was sent.
const nodemailer = require('nodemailer');
const cors = require('cors')({origin: true});
// Gmail configuration to Send eMail
let transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: 'email',
pass: 'passwors'
}
});
exports.sendMailPasswordReset = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const mailOptions = {
from: 'team <team#example.app>', // Something like: Jane Doe <janedoe#gmail.com>
to: 'example#gmail.com',
subject: 'Password Reset', // email subject
html: `html body` // email content in HTML
};
// returning result
return transporter.sendMail(mailOptions, (erro, info) => {
if(erro){
return res.send(erro.toString());
}
return res.send('true');
});
});
});
You cannot send back a constant value.
return res.send('true');
Error code 304: Is returning false because it is always returning the same constant.
To fix the problem the send value needs to be different every time.
For Example:
let random = Math.random().toString(36).substring(7);
return res.send('true_' + random);
I also added for cors
res.setHeader('Cache-Control', 'no-cache');
Here is a detailed understanding of Error code 304

Cloud Firebase Function works locally but doesnt work after deployment

I am writing my first Cloud Function which works great locally but doesn't work the moment I deployed it to Firebase. I am trying to invoke a Cloud function which will send an email via a HTTP request using Flutter Web. I read online and suspect it might be be because I cant return the promise. How do I ensure the asynchronous calls to be completed?
const nodemailer = require('nodemailer');
const cors = require('cors')({origin: true});
/**
* Here we're using Gmail to send
*/
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'dummyaccount#gmail.com',
pass: 'dummypassword'
}
});
exports.sendMail = functions.https.onRequest((req, res) => {
cors(req, res, () => {
return transporter.sendMail(req.body, (erro, info) => {
if(erro){
return res.send(erro.toString());
}
return res.send('Message send');
});
});
});
const admin = require('firebase-admin');
admin.initializeApp();
This is my Http Request using Dart for Flutter Web
void httpCallFunction() async {
var url =
'https://us-central1-fire-station-ope-blablala.cloudfunctions.net/sendMail';
var response = await http.post(
url,
body: json.encode({
"from": "Dummy Guy <Do-Not-Reply#gmail.com>",
"to": "$_email",
"subject": "Your Booking has been approved!!",
"html": approvedMessageTosent,
}),
headers: {
'Content-type': 'application/json',
},
);
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
}
I went through a rollercoaster to solve this issue. So apparently the cloud function log came back with status code 204 and said that my 'Request body is missing data' but I could see that my data was being sent.
So I found another guy faced the exact same issue as I did in this link: Dart json.encode is not encoding as needed by Firebase Function
Essentially I had to wrap my data in a key called 'data'.
{
"data":{
"to":"john#doe.com",
"displayName":"Johnny",
"from":"JaneDoe",
}
}
https://firebase.google.com/docs/functions/callable-reference#request_body

Using ably.io JWT with Angular

I'm trying to use ably.io with Angular and Azure Functions using the JWT way of authenticating since it's secure, but I'm having issues with configuring the angular side of it. The use case is for a live auction site to update bids in realtime. There isn't a specific angular tutorial for this so I'm trying to piece it together. Also this code
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
never throws the alert so I don't think it's working right. I used to have it working where I wasn't using ably JWT, but had the API key on the client-side in a component like this
let api = "<api key>";
let options: Ably.Types.ClientOptions = { key: api };
let client = new Ably.Realtime(options); /* inferred type Ably.Realtime */
let channel = client.channels.get(
"auctions"
);
and I could subscribe to that channel and update auctions accordingly by their id inside ngOnInit()
channel.subscribe(message => {
const auction = this.posts.find(action => {
return action.id === message.data.auctionId;
});
if (auction) {
auction.currentBid = message.data.lastBid;
}
});
but I need to switch this logic for JWT and somehow feed that JWT token into different components as well.
Ably.io JWT tutorial reference
I put the following in my angular login service
login(email: string, password: string) {
const authData: AuthDataLogin = { email: email, password: password };
return this.http
.post<{
token: string;
expiresIn: number;
userId: string;
}>(environment.azure_function_url + "/POST-Login", authData)
.pipe(takeUntil(this.destroy)).subscribe(response => {
//JWT login token. Not Ably JWT Token
const token = response.token;
this.token = token;
if (token) {
console.log('Fetching JWT token from auth server')
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
...
}
With my azure function already configured, When I login, the browser console outputs
GET wss://realtime.ably.io/?access_token=<token was here>&format=json&heartbeats=true&v=1.1&lib=js-web-1.1.22
SO it returns my token, but
the alert never happens
I'm not sure how to grab that JWT token that's returned to the browser. I was thinking I could store it in localStorage to share between components and clear out localStorage when user logs out, but I need to be able to subscribe to response and assign the token to a variable, but I didn't see in ably javascript tutorial how to get variable assigned to JWT Token response since it's being called with this syntax.
I appreciate any help with this!
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
My azure function looks like
const checkAuth = require('../middleware/check-auth');
var jwt = require("jsonwebtoken")
var appId = '<APP ID>'
var keyId = '<key ID>'
var keySecret = '<key secret>'
var ttlSeconds = 60
var jwtPayload =
{
'x-ably-capability': JSON.stringify({ '*': ['publish', 'subscribe'] })
}
var jwtOptions =
{
expiresIn: ttlSeconds,
keyid: `${appId}.${keyId}`
}
console.log("JwtPayload");
console.log(jwtPayload);
console.log("jwtOptions");
console.log(jwtOptions);
module.exports = function (context, req) {
console.log("INSIDE ABLY AUTH")
// checkAuth(context, req);
console.log('Sucessfully connected to the server auth endpoint')
jwt.sign(jwtPayload, keySecret, jwtOptions, function (err, tokenId) {
if (err) {
console.log("ERR")
console.log(err)
console.trace()
return
}
context.res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate')
context.res.setHeader('Content-Type', 'application/json')
console.log('Sending signed JWT token back to client')
console.log(tokenId)
context.res = {
status: 200,
body: JSON.stringify(tokenId),
headers: {
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Set-Cookie",
"Access-Control-Max-Age": "86400",
"Vary": "Accept-Encoding, Origin",
"Content-Type": "application/json"
}
};
context.done();
})
}
I'd recommend if you're wanting to intercept the JWT prior to passing it to Ably (so as to verify the contents, and also use the JWT for other components), you make use of authCallback instead of authUrl. You can use a function instead of a direct URL, within which you can call the endpoint, and do anything you like with the response, prior to passing the JWT back to the Ably constructor. I've made a JavaScript example of using the authCallback for normal Token Authentication, but the same principle applies.
As to why you're not seeing the alert, it looks like you're sending an invalid JWT for what Ably is expecting, and thus you're not successfully connecting to Ably. For example, you're specifying 'expiresIn' rather than 'exp'. For a token to be considered valid, it expected certain elements in a very specific structure, see the documentation. I'd recommend for this sort of situation where you're not certain what's breaking that you make use of verbose logging, which you can enable in the connection constructor as "log": 4.

.put method for updating password in mocha chai requests not working

My code:
const model = require('../db/models/user');
const describe = require('mocha').describe;
const assert = require('chai').assert;
const chaiHttp = require('chai-http');
let chai = require('chai');
let server = require('../server');
chai.use(chaiHttp);
describe('Test user registration, login, update password', () => {
beforeEach((done) => {
// Reset user mode before each test
model.User.remove({}, (err) => {
console.log(err);
done();
})
});
Now, I get the error
UnhandledPromiseRejectionWarning: TypeError: Cannot read property
'_id' of null
in the route itself, specifically:
router.put('/me/update-password', async (req, res, next) => {
const {body} = req;
const auth = req;
const userId = auth._id; // problem on this line!
// rest of code...
});
So, after registration and logging in (which works fine, as it should!), I am having a lot of problems to update the password. In the params I am sending generated token and in the body is the password field with new password. On live example (for example Postman) it works as it should, but in tests it simply does not.
I really have no idea and have lost a lot of my time over this already (3 days).
Can someone please take a look suggest solution?
Much appreciated.
Updated with auth.js:
const jwt = require('jsonwebtoken');
const isAu = function(req) {
return jwt.verify(req.headers.authorization.split(' ')[1], 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};
module.exports = isAu;
EDIT:
Since OP changed the original question after it has been answered here is the link to original: https://stackoverflow.com/revisions/55064109/1
=======================================
JWT verify method accepts Authorization token - you are fetching that correctly by splitting Authorization header string in order to fetch token.
HTTP Authorization header string hold Authentication scheme type (Bearer, Basic, Digest, etc) and the token value
Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9...TJVA95OrM7E20RMHrHDcEfxjoYZgeFONFh7HgQ
but your Authorization header in the Chai request only holds the value of the token and not the Authentication scheme type.
Assumin your Authentication scheme is Bearer you need to set that in your Chai request Authorization header:
...
chai.request(server)
.put('/api/me/update-password')
.set('Authorization', `Bearer ${token}`)
.send(`${updatedPassword}`)
.end((error, response) => {
assert.equal(response.status, 200);
done();
});
...
On the other hand, in case you do not specify Authentication type in the request authorization header than you should send it like that to JWT to veirfy:
const isAuthenticated = function(req) {
return jwt.verify(req.headers.authorization, 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};

Categories