Cloud Firebase Function works locally but doesnt work after deployment - javascript

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

Related

Getting cors issue while calling google fit rest api (issue is coming while calling authentication url)

I am trying to build a project in which I will fetch the user's step count by using the google fit Rest API. For this, I have created a project on google's developer console and specified a redirection url there. Have a look to the code snippet below :
exports.getUrl = async (req, res, next) => {
try {
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_FIT_CLIENT_ID,
process.env.GOOGLE_FIT_CLIENT_SECRET,
process.env.GOOGLE_FIT_REDIRECTION_URL
);
console.log("oauth2Client", oauth2Client)
// generate a url that asks permissions for fitness activity scopes
const scopes = ["https://www.googleapis.com/auth/fitness.activity.read profile email openid"];
const url = oauth2Client.generateAuthUrl({
access_type: "offline",
scope: scopes,
include_granted_scopes: true,
state: JSON.stringify({
// callbackurl: req.body.callbackurl,
})
});
console.log("url", url);
res.redirect(302, url);
} catch (err) {
console.log("err", err)
next(err);
}
}
exports.getSteps = async (req, res, next) => {
try {
const queryUrl = new urlParse(req.url);
const code = queryParse.parse(queryUrl.query).code;
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_FIT_CLIENT_ID,
process.env.GOOGLE_FIT_CLIENT_SECRET,
process.env.GOOGLE_FIT_REDIRECTION_URL
);
const token = await oauth2Client.getToken(code);
oauth2Client.setCredentials(token);
const result = await axios({
proxy: {
protocol: 'http',
host: 'proxy-url',
port: port
},
method: "POST",
headers: {
authorization: "Bearer " + token.tokens.access_token
},
"Content-Type": "application/json",
url: "https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate",
data: {
"aggregateBy": [{
"dataTypeName": "com.google.step_count.delta",
"dataSourceId": "derived:com.google.step_count.delta:com.google.android.gms:estimated_steps"
}],
"bucketByTime": { "durationMillis": 86400000 }, // This is 24 hours
"startTimeMillis": startTime, // This startTime and endTime I am getting from DB
"endTimeMillis": endTime
}
});
if (result) {
const response = [];
let stepArray = result?.data?.bucket;
for (const dataSet of stepArray) {
for (const points of dataSet.dataset) {
for (const steps of points.point) {
response.push(steps?.value[0]?.intVal);
}
}
}
res.status(200).send(response);
} else {
throw new Error('Data fetching failed!');
}
} catch (err) {
next(err);
}
}
The steps url is what I have mentioned as a redirection url on the google's developer console. I have used proxy because the urls which are getting called are not whitelisted on the server on which I am deploying the code.
Now, everything worked perfectly fine in localhost but on server, I am getting below error :
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline' (redirected from 'https://someexample.com?id=123') from origin 'https://someexample.com' 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.
Also, one thing to note here is that the above error not coming when I am trying to get the data from chrome with --disable-web-security flag.
I have mentioned the uri through which this request is originating ( ex- https://someexample.com) in authorised JavaScript origins on the Google's developer console too.
Please let me know if I am doing something wrong. Any response would be highly appreciated.

how to get cookie in react passed from express js api (MERN stack)

I have an api in express js that stores token in cookie on the client-side (react). The cookie is generated only when the user logins into the site. For example, when I test the login api with the postman, the cookie is generated as expected like this:
But when I log in with react.js then no cookie is found in the browser. Looks like the cookie was not passed to the front end as the screenshot demonstrates below:
As we got an alert message this means express api is working perfectly without any error!!
Here is my index.js file on express js that includes cookie-parser middleware as well
require("dotenv").config();
const port = process.env.PORT || 5050;
const express = require("express");
const app = express();
const cors = require("cors");
const authRouter = require("./routes/auth");
var cookieParser = require('cookie-parser')
connect_db();
app.use(express.json());
app.use(cookieParser())
app.use(cors());
app.use("/" , authRouter);
app.listen(port , () => {
console.log("Server is running!!");
})
Code for setting up the cookie from express api only controller
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require('bcrypt')
const login = async (req, res) => {
const { email, password } = req.body;
try {
const checkDetails = await User.findOne({ email });
if (checkDetails) {
const { password: hashedPassword, token, username } = checkDetails;
bcrypt.compare(password, hashedPassword, function (err, matched) {
if (matched) {
res.cookie("token", token, { expires: new Date(Date.now() + (5 * 60000)) , httpOnly: true }).json({ "message": "You logged in sucessfully!" });
} else {
res.status(500).json({ "message": "Wrong password" });
}
});
} else {
res.status(500).json({ "message": "Wrong email" });
}
} catch (error) {
console.log(error.message);
}
}
Here is the react.js code that I am using to fetch data from api without using a proxy in package.json file
if (errors.length === 0) {
const isLogin = await fetch("http://localhost:5000/api/login", {
method: "POST",
body: JSON.stringify({ email, password }),
headers: {
"Content-Type": "application/json"
}
});
const res = await isLogin.json();
if(res) alert(res.message);
}
I want to get to know what is the reason behind this "getting cookie in postman but not in the browser". Do I need to use any react package?
The network tab screenshot might help you.
If I see in the network tab I get the same cookie, set among the other headers
To my understanding, fetch doesn't send requests with the cookies your browser has stored for that domain, and similarly, it doesn't store any cookies it receives in the response. This seems to be the expected behaviour of fetch.
To override this, try setting the credentials option when making the request, like so:
fetch(url, {
// ...
credentials: 'include'
})
or, alternatively:
fetch(url, {
// ...
credentials: 'same-origin'
})
You can read more about the differences between the two here.
I got my error resolved with two changings in my code
In front end just added credentials: 'include'
fetch(url, {
method : "POST"
body : body,
headers : headers,
credentials: 'include'
})
And in back end just replaced app.use(cors()); to
app.use(cors({ origin: 'http://localhost:3000', credentials: true, exposedHeaders: ['Set-Cookie', 'Date', 'ETag'] }))
That's it got resolved, Now I have cookies stored in my browser!!! Great. Thanks to this article:
https://www.anycodings.com/2022/01/react-app-express-server-set-cookie-not.html
during development i also faced same things, let me help you that how i solve it,
Firstly you use proxy in your react package.json, below private one:-
"private": true,
"proxy":"http://127.0.0.1:5000",
mention the same port on which your node server is running
Like:-
app.listen(5000,'127.0.0.1',()=>{
console.log('Server is Running');
});
above both must be on same , now react will run on port 3000 as usual but now we will create proxy to react So, react and node ports get connected on same with the help of proxy indirectly.
Now, when you will make GET or POST request from react then don't provide full URL, only provide the path on which you wants to get hit in backend and get response,
Example:-
React side on sending request, follow like this:-
const submitHandler=()=>{
axios.post('/api/loginuser',
{mobile:inputField.mobile,password:inputField.password})
.then((res)=>{
console.log(res);
})
.catch((err)=>{
console.log(err);
})
}
Node side where it will hit:-
app.post('/api/loginuser', async(req,res)=>{
//Your Code Stuff Here
res.send()
}
on both side same link should hit, it is very important
it will 100%.
don't forget to mention
on node main main where server is listening

nodeJs basic authentication issue

I'm getting no proper response while make an API request to external API using basic authentication (username and password) in nodejs (javascript)
I used the below code and the response is "undefined", not sure what is missing here.
But I was able to make a request using postman tool without any issues.
const request = require('request')
const user = '*****';
const pass = '*****!';
const url = 'https://servicenow.com/api/table'
var options = {
url: url,
auth: {
username: user,
password: pass
}
};
request.get(options, (err, res, body) => {
if (err) {
return console.log(err);
}
console.log(body.url);
console.log(body.explanation);
});
Response:
undefined
undefined
if your api right with postman you can do like this based on photo
send a request
click on code
select nodejs- Request
copy

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.

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

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;
...
});

Categories