Recurring SSL protocol error with firebase hosting and cloud functions - firebase

I am having an issue with a website I am making for my portfolio. It is hosted on firebase hosting, using the firebase cloud functions for the API calls. my single page react app calls an express router api to query a mongodb database for a list of restaurants. while the firebase emulator is running, I can retrieve the array by going to the api url in my browser, but the web component isn't able to fetch the list and returns an SSL protocol error. Everything I have been able to find so far points to this being a cors issue, but cors() is definitely being use()'d in the server, so I'm a bit lost.
I have tried:
Using the cors npm package in my server, code below:
const functions = require('firebase-functions');
const express = require('express');
const app = express();
const cors = require('cors');
require('dotenv').config({path : './server/config.env'});
const path = require('path');
const publicPath = path.join(__dirname, '..', 'public');
app.use(express.static(publicPath));
app.use(cors());
app.use(express.json());
app.use(require('./server/routes/record'));
exports.api = functions.https.onRequest(app);
Using a mode:cors option in the fetch request: fetch('https://API_URL_HERE', {mode : 'cors'});
Using axios and sending a header with the request: axios.get('https://API_URL', {headers: {'Access-Control-Allow-Origin' : '*'}});
Here is the route I am using as well - I'm using ExpressRouter, not plain Express.js. The problem persisted when I translated it to express.js.
//Returns a list of all restaurants present in the database. The restaurants are objects structured as
// {'name': name,
// 'style': (the type of cuisine),
// 'menu' : (the available items)}
recordRoutes.route('/restaurants').get(async function (req, res) {
console.log('connecting to restaurants db')
await client.connect();
client.db()
.collection('Restaurants')
.find({})
.toArray(function (err, result) {
if (err)
console.log(err);
res.json(result);
});
});
toArray is marked as deprecated in VSC but I can't think of any reason that would be an issue - all the other routes to the database that don't use toArray are nonfunctional.

Related

Getting issue in generating Firebase token in React JS application

importScripts("https://www.gstatic.com/firebasejs/9.8.3/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/9.8.3/firebase-messaging-compat.js");
// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
apiKey: "AIzaSyDk4dYeydr4q3npWx4iqKWejq33r1E",
authDomain: "tengage-33e12.fireeapp.com",
projectId: "tengage-33e12",
storageBucket: "tengage-33e12.appspot.com",
messagingSenderId: "1076165210",
appId: "1:1076165210:web:c9560924940a068ce3",
measurementId: "G-5FMCR8M0G8",
};
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) {
console.log(" message ", payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
};
self.registration.showNotification(notificationTitle, notificationOptions);
});
I have one React js project in which I have to integrate the FCM.
I have added firebase-messaging-sw.js in public folder.
and added the firebbase dependency also.
when I am trying to get token i am getting the error.
SecurityError: Failed to register a ServiceWorker for scope ('http://localhost:3005/') with script ('http://localhost:3005/firebase-messaging-sw.js'): The script has an unsupported MIME type ('text/html').
But with the same firebase credentials when I tried to get the token in the newly created application its giving the token.
The error that is mentioned may occur due to a few reasons , one of which could be an error in the design pattern or syntax. However, it means that any requests for unknown text files initially get redirected to index.html, and therefore return with the MIME type of "text/html", even if it's actually a JavaScript or SVG or some other kind of plaintext file.
Refer below for example:
// Load React App
// Serve HTML file for production
if (env.name === "production") {
app.get("*", function response(req, res) {
res.sendFile(path.join(__dirname, "public""index.html"));
});
}
You may try to add a specific route for service-worker.js before the wildcard route:
app.get("/service-worker.js", (req, res) => {
res.sendFile(path.resolve(__dirname, "public", "service-worker.js"));
});
app.get("*", function response(req, res) {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
Now, when the browser looks for service-worker.js, it will return it with the correct MIME type.
(Note that if you try adding the service-worker.js route after the wildcard, it won't work because the wildcard route will override.)
You also may want to check if the URL http://localhost:3005/firebase-messaging-sw.js, is getting served and its MIME type from the browser interface.
Also , please make sure that the service worker file is generated (Chrome DevTools -> Sources -> Filesystem). You may receive such an error if the above example service-worker.js file is not generated at all (check your deployment script).
You may also refer the links below for more insight:
[1] : https://firebase.google.com/docs/cloud-messaging/js/client#access_the_registration_token
[2] : https://firebase.google.com/docs/cloud-messaging/server
[3] : FCM getToken() Failed to register a ServiceWorker for scope error Flutter web

Google storage permissions error while generating signed url from cloud function

I'm attempting to use a Firebase Cloud Function to create signed download URLs for files stored in a Storage Bucket. Using the snippet below on my local machine, I'm able to access cloud storage and generate these URLs.
/* eslint-disable indent */
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
// eslint-disable-next-line #typescript-eslint/no-var-requires
const serviceAccount = require("./test-serviceAccount.json");
admin.initializeApp();
const storage = admin.storage();
const bucket = storage.bucket();
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
}, "firestore");
export const getFile = functions.https.onRequest(async (request, response) => {
const [files] = await bucket.getFiles();
const fileNames: string[] = [];
files.forEach(async (file) => {
console.log(file.name);
const url = await file.getSignedUrl(
{
version: "v2",
action: "read",
expires: Date.now() + 1000 * 60 * 60 * 24,
}
);
fileNames.push(String(url));
if (files.indexOf(file) === files.length - 1) {
response.send(JSON.stringify(fileNames));
}
});
});
However after deploying to Cloud Functions I get an error when I call the function saying:
Error: could not handle the request
and the following message is logged in the functions console:
Error: The caller does not have permission
at Gaxios._request (/workspace/node_modules/gaxios/build/src/gaxios.js:129:23)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async Compute.requestAsync (/workspace/node_modules/google-auth-library/build/src/auth/oauth2client.js:368:18)
at async GoogleAuth.signBlob (/workspace/node_modules/google-auth-library/build/src/auth/googleauth.js:655:21)
at async sign (/workspace/node_modules/#google-cloud/storage/build/src/signer.js:97:35)
I've tried using and not using a .json service account key and made sure that the service account has permissions (it has Service Account Token Creator, Storage Admin, and Editor roles at the moment).
I also read this issue relating to the python SDK for storage, but it seems to have been resolved. The workaround mentioned in that issue (using a .json service account token) also didn't resolve the permissions errors.
After working with Firebase support - here's what worked for me:
import { initializeApp, applicationDefault } from 'firebase-admin/app';
initializeApp({
credential: applicationDefault(),
projectId: '<FIREBASE_PROJECT_ID>',
});
Specifying the projectId in the init call seems to have resolved the issue.
Signed url means it is signed for (or, accessible to) any particular user for particular span of time, maximum 7 days. If you trying to get it for unauthenticated users, it may show such error(s).
It's better to use getDownloadURL() for unauthenticated users. getSignedUrl() should to used for authenticated users only.

Why am I getting a CORS error in the browser but not in Postman?

I am using the Firebase emulators to serve Firebase functions on localhost. If I test my functions using Postman, this all works using the following request:
http://localhost:5001/project-XXXX/us-central1/api/users
Then, I fire up my Next.js application on port 3000 and try to use Axios to get the same data as follows:
useEffect(() => {
axios
.get(
"http://localhost:5001/project-XXXX/us-central1/api/users"
)
.then((res) => {
console.log(res);
})
.catch(function (error) {
console.log(error);
});
}, []);
However, now I'm getting a CORS error: "Access to XMLHttpRequest at 'http://localhost:5001/project-XXXX/us-central1/api/appointments/availability' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
In response to similar questions I have tried changing region and starting chrome with web-security disabled, which was both unsuccesful in solving the matter.
Does anyone have an idea as to why I am getting this error when requesting from the browser (and not from Postman)?
For full information, my index.js file of the Firebase cloud functions using Express.js:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");
const cors = require("cors");
const authMiddleware = require("./authMiddleware");
const { user } = require("firebase-functions/v1/auth");
admin.initializeApp();
const app = express();
const { users, updateUser } = require("./routes/users");
// user-related routes
app.use("/users", users);
const api = functions.https.onRequest(app);
// export as Firebase functions
module.exports = { api: api, updateUser: updateUser };
Thanks jub0bs! My problem was solved by installing cors (npm install cors) and adding the following to my index.js:
const cors = require("cors");
app.use(cors());
This enables CORS for all origins, so only use for development and change before you go to production.

Firebase functions with nodemailer POST request: Error: Process exited with code 16

I'm running a Firebase cloud function whose purpose is to handle the backend of a "contact me" form. The Express framework is being used for middleware functions. When the submit button is clicked, a POST request is made to the /submit endpoint.
index.js in the functions folder is as below:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const bodyParser = require("body-parser");
const express = require("express");
const emailRoute = require("./routes/email");
// Initialize Firebase in order to access its services.
admin.initializeApp();
const app = express();
// Automatically allow cross-origin requests.
app.use(cors({ origin: true }));
app.use(bodyParser.urlencoded({extended: false}));
app.use(emailRoute);
// Expose Express API as a single Cloud Function.
exports.app = functions.https.onRequest(app);
The imported router from email.js is as follows:
const nodemailer = require("nodemailer");
const express = require("express");
const router = express.Router();
router.post("/submit", (req, res) => {
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "email#gmail.com",
pass: "password",
},
});
const myEmail = {
to: "myemail#gmail.com",
subject: `A new message from ${req.body.name}`,
text: `${req.body.name} sent the following message:
\n\n ${req.body.message}
\n\n Senders email: ${req.body.email}`,
};
const sendersEmail = {
to: req.body.email,
subject: "A copy of your message to me",
text: `You just sent me the following message:\n\n${req.body.message}`,
};
console.log("SUBMIT REQUEST PROCESSING");
transporter.sendMail(myEmail);
transporter.sendMail(sendersEmail);
res.redirect("/#contact");
console.log("PROCESSING COMPLETE");
});
module.exports = router;
There is no issue when running this in the local environment - the email gets sent to both parties. However, when run in the hosted environment as a Firebase function the following error is thrown: Error: Process exited with code 16 (as displayed in the function logs section of the Firebase console). A previous SO answer indicates an uncaughtException or unhandledRejection.
Before the error, both console.log() statements are logged. Then the function finishes with a 302 status code (as it does when run locally and successfully). After that, there is the unhandled rejection followed by Error: Invalid login and a link to my Google account and a statement "Please log in via your web browser and then try again".
Could this be a Firebase security measure against automated mailing that nodemailer is attempting to execute?
I needed to both enable less secure apps and display unlock captcha on the gmail account, only the former of which I had previously done.
I also needed to set the gmail.email and gmail.password Google cloud environment variables. This can be done with the following shell command: firebase functions:config:set gmail.email="myusername#gmail.com" gmail.password="secretpassword"
A great resource I found on this is an official Firebase function sample that utilises Nodemailer, where the above is covered.
The issue is in logging to your Google account. Have you enabled less secure apps in your Google account? Or better I would recommend to use some other authentication, as described here:
https://support.google.com/a/answer/176600?hl=en

why I don't get the URL to access the http cloud function I create?

so I make http trigger function to get all events from my firestore like the image above.
firestore.js
const functions = require('firebase-functions')
const admin = require("firebase-admin")
// initialize database
admin.initializeApp()
const db = admin.firestore();
const settings = {timestampsInSnapshots: true};
db.settings(settings)
const eventRef = db.collection('event')
module.getAllEventsFromFirestore = functions.https.onRequest(async (request,response) => {
try {
const events = await eventRef.get()
response.status(200).send(`number of event is ${event.size}`)
} catch (error) {
response.status(500).send(error)
}
})
and my index.js
const {getAllEventsFromFirestore} = require("./firestore")
after deploying the function, I expect will get the URL to access that http trigger function on my terminal, but I can't find it.
The Firebase CLI will only give you a URL the first time you deploy the function. If you update the function after the first deploy, it won't print the URL. You can get the URL of the function by going to the Firebase console and view your functions there. The URL will be available on the Functions dashboard page.
If you would like to see a change in behavior of the Firebase CLI, file a feature request with Firebase support.

Resources