How to use Firebase admin SDK with google cloud functions - node.js

I'm trying to generate an email sign in link using the admin.auth().generateSignInWithEmailLink method provided by the Firebase Admin SDK. I'm attempting to do this within a Google cloud function.
Here is my index.js file (All packaged are installed):
const cors = require('cors')({ origin: true });
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.sendLoginLink = function(req, res) {
cors(req, res, () => {
const actionCodeSettings = {
url: 'http://localhost:8083/account/dashboard/?email=' + req.body.email,
handleCodeInApp: true
};
admin.auth().generateSignInWithEmailLink(req.body.email, actionCodeSettings)
.then((link) => {
console.log(link)
})
.catch((error) => {
res.status(500)
});
});
};
I've passed an email from my front-end app to the cloud function which works, the actionCodeSettings also output correctly, but i'm not able to make it past the first line in the generateSignInWithEmailLink() when debugging.
Anyone know why?

Related

Authentication using Node.js OauthClient "auth-code" flow

I'm building a SaaS application which require read-access to a user's google calendar. After the user gives consent to access their calendar during the first sign-in, I want the application to be able to authorize itself to access any of the user's calendars at any time without having to prompt the user for authorization again.
Currently I'm trying to create an authentication flow following the '#react-oauth/google' node library (specifically the "authorization code flow" here: https://react-oauth.vercel.app/). In my frontend, I get a code from the user, which is sent and successfully received by my backend. The backend is then supposed to use the code to get an access token and a refresh token for that user, but the request to exchange the code for the access token (oAuth2Client.getToken(req.body.code);) is failing with error 401 (unauthorized client.)
My ultimate goal is to store the access token and refresh token in a database somewhere so that I can access that user's calendar later at any time.
If I treat the backend as an Oath Client on google cloud and pass in the credentials for that, I get error 401 - unauthorized client, but I've given it access to the calendar api on google console, as you can see in the image:
How can I resolve the issue that I'm facing?
I started reading about service accounts that can do this for you but I'm unsure how to proceed. I saw that they can do domain wide delegation but my users will be signing in from their personal gmail accounts so that option is not applicable for me.
Frontend Code:
import { useGoogleLogin } from '#react-oauth/google';
import axios from 'axios';
export const LoginModal = () => {
const googleLogin = useGoogleLogin({
flow: "auth-code",
onSuccess: async codeResponse => {
console.log(codeResponse);
const tokens = await axios.post("http://localhost:3001/auth/google/", {
code: codeResponse.code
});
console.log(tokens);
}
})
return (<>
...some html code
<button onClick={() => { googleLogin() }}>
..some more html code
</>)
}
Backend Code:
require('dotenv').config();
const express = require('express');
const {
OAuth2Client,
} = require('google-auth-library');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const CLIENT_ID = "xxx";
const CLIENT_SECRET = "xxx";
// initialize oathclient
const oAuth2Client = new OAuth2Client(
CLIENT_ID,
CLIENT_SECRET,
'postmessage',
);
// get token from code given from frontend
app.post('/auth/google', async (req, res) => {
console.log(req.body.code)
const { tokens } = await oAuth2Client.getToken(req.body.code); // exchange code for tokens
res.json(tokens);
});
app.post('/auth/google/refresh-token', async (req, res) => {
const user = new UserRefreshClient(
CLIENT_ID,
CLIENT_SECRET,
req.body.refreshToken,
);
const { credentials } = await user.refreshAccessToken(); // obtain new tokens
res.json(credentials);
})
app.listen(3001, () => {
console.log(`server is running`)
});
I figured it out. Basically, I was putting in the wrong clientid/client secret in my google console because I thought the frontend and backend needed different oauth client IDs. I used the same oauth client secret/id for frontend and backend and made sure to follow the answers here:
https://github.com/MomenSherif/react-oauth/issues/12
MAKE SURE TO PUT "postmessage" AS YOUR REDIRECT_URI! It will not work without that.
Working code:
frontend is the same
backend:
require('dotenv').config();
const express = require('express');
const {
OAuth2Client,
} = require('google-auth-library');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const CLIENT_ID = XXX
const CLIENT_SECRET = XXX
// initialize oathclient
const oAuth2Client = new OAuth2Client(
CLIENT_ID,
CLIENT_SECRET,
'postmessage',
);
// get token from code given from frontend
app.post('/auth/google', async (req, res) => {
console.log("got request!")
console.log(req.body.code)
const { tokens } = await oAuth2Client.getToken(req.body.code); // exchange code for token
res.json(tokens);
});
app.listen(3001, () => {
console.log(`server is running`)
});

Internal Server Error 500 from API when deploying front-end React.js backend Express app

For my senior capstone, my group and I have developed a web-based application to simulate Bitcoin - using react.js for the front-end and node.js/express for the back-end. Up until recently, we've had all of simulation-creating-code (javascript files) inside the src directory, meaning it was being built client-side. Due to high waiting times to create a simulation from all the hashing necessary in transactions, we decided that our simulation-creating-code would be better suited for the back-end rather than the front end. Taking the load off the client and putting it on the server drastically improved the speed of creating a simulation, so 'Great success!'.
When we made this change, we ended up having some issues with require and import statements. Reactjs only supports import statements and Express uses require statements. We had to use some js functions that we developed in our API's so we imported them with require statements, and we thought we thought it was resolved because on our development environment, everything runs as smooth as butter, but once it's deployed, our login page is unable to make an API call. The error is: Failed to load resource: the server responded with a status of 500 (Internal Server Error).
It's interesting because this route in the API worked prior to making this big switch from require to import, and those changes were in other files/routes. The login API remains completely unchanged.
Either way, I'll drop some code in case it's helpful in troubleshooting.
server.js
const express = require("express");
const app = express();
const router = express.Router();
const path = require("path");
var cors = require("cors");
require("dotenv").config();
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb" }));
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
// List of routes
router.use("/api/users", require("./api/users"));
router.use("/api/data", require("./api/data"));
router.use("/api/share", require("./api/share"));
router.use("/api/addresses", require("./api/addresses"));
const root = path.join(__dirname, "client/build");
app.use(express.static(root));
app.use(router);
app.use(cors({ origin: true, credentials: true }));
app.listen(
process.env.PORT,
() => `Server running on port ${process.env.PORT}`
);
api/users.js login route
const express = require("express");
const app = express();
const db = require("../dbConn");
const bcrypt = require("bcrypt-nodejs");
const cors = require("cors");
const router = express.Router();
const jwt = require("jwt-simple");
const config = require("../configuration/config.json");
// to parse JSON
app.use(express.json());
router.post("/login", (req, res) => {
//check if email and password are sent
if (!req.body.email || !req.body.password) {
return res.status(401).json({ error: "Missing username and/or password" });
}
// go into mysql and get info
let qry = `select * from user where email = "${req.body.email}"`;
db.query(qry, (err, rows) => {
if (err) {
return res.status(500).json({ error: err });
}
// assert: no error - process the result set
if (rows.length == 0) {
// no users found
res.status(400).json({ msg: "No users found" });
} else {
// process the user records
let users = [];
rows.forEach((row) => {
let user = {
uid: row.uid,
email: row.email,
role: row.role,
dateCreated: row.created_date,
password: row.password,
};
users.push(user);
});
if (users[0]) {
// Does given password hash match the database password hash?
bcrypt.compare(req.body.password, users[0].password, (err, result) => {
// Send back a token that contains the user's username
const token = jwt.encode({ email: req.body.email }, config.secret);
if (result == true) {
res.status(200).json({
msg: "user authenticated",
fname: users[0].fname,
lname: users[0].lname,
role: users[0].role,
token: token,
});
} else {
res.sendStatus(401);
}
});
}
}
});
});
router.post("/auth", cors(), (req, res) => {
try {
let user = jwt.decode(req.body.token, config.secret);
res.status(200).send(user);
} catch (err) {
res.sendStatus(401);
}
});
SignIn.js client/src/components. This is wrapped in a react.useEffect() arrow function, but again I don't believe the issue is here because this page remains unchanged from a working version.
const handleSubmit = (e) => {
e.preventDefault();
const credentials = { email, password };
// API call to login to account
// if successful, redirect to landing page
// if not, display error message
fetch(`http://${process.env.REACT_APP_API_URL}/api/users/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
})
.then(async (res) => {
if (res.status == 200) {
return res.json();
} else {
throw new Error("Failed to Login!");
}
})
.then(async (res) => {
// Store token in cookie
setCookie("token", res.token, { path: "/${path}", maxAge: 3600 * 24 });
// Toggle state of sign in
toggleSignIn();
// Feedback
setFeedback(true);
setFeedbackObj({ message: "Signed in!", severity: "success" });
//redirect
history.push(`${process.env.PUBLIC_URL}/simulation`);
})
.catch(async (err) => {
// Feedback
setFeedback(true);
setFeedbackObj({ message: "Sign In Error", severity: "error" });
console.error(err);
});
};
If there are any other files that are of interest please let me know.
I've tried to mess with the proxy in package.json, but I don't think thats the answer because it was working previously. I've had a really difficult time finding others with similar issues or resources other than how to build a simple app with Express backend and React.js front end. This is not our issue because our application was working perfectly before this big switch. I believe the issue is stemming from require statements in our API and the running of JS functions in the API. I have no way to confirm this because in production (deployment), the errors are super uninformative, and in development, it runs perfectly fine.
I have been trying to solve this issue for a couple of weeks now, and I've made very little progress. If anyone has suggestions or tips on troubleshooting deployment, I would greatly appreciate it.
Thanks!

Jest not executing express middleware

I am trying to write tests for a simple endpoint which is protected. I have an auth() middleware in place which verifies the Authorization header and upon successful verification adds a key token to the req object.
The problem is that jest is directly calling the getCustomerProfile controller and skips executing auth() middleware because of which the controller says req.token is undefined. This is how my Jest setup looks like:
const express = require('express');
const mapRoutes = require('express-routes-mapper');
const privateRoutes = ('../routes/private_routes')
const auth = require('auth_middleware');
const setupAction = async () => {
const testapp = express();
const mappedPrivateRoutes = mapRoutes(privateRoutes, 'api/controllers/');
testapp.use(express.urlencoded({ extended: false }));
testapp.use(express.json());
testapp.use('/private', mappedPrivateRoutes);
testapp.all('/private/*', (req, res, next) => auth(req, res, next));
return testapp;
};
module.exports = { setupAction };
Below is how I am trying to test the protected endpoint:
const request = require('supertest');
const {
setupAction
} = require('./setup_jest');
const Customer = require('models/customer_model');
let test_api;
beforeAll(async () => {
test_api = await setupAction();
});
....
...
...
test('Customer | get profile', async () => {
let token = 'TestToken123'
res = await request(api)
.get('/private/customer/get-profile')
.set('Accept', /json/)
.set('Authorization', `Bearer ${token}`)
.send()
.expect(200);
expect(res.body.customer_id).toBeTruthy();
});
In auth.js I have tried adding console.log() statements but it has no effect. Nothing gets printed and the request goes straight to getCustomerProfile() controller without validating the authorization headers.
What's more interesting is that when the express app is running and I hit the private endpoint with the Authorization header using Postman it works completely fine and the request goes through auth() middleware.
It is only while running through jest, the middleware is getting skipped!
I am stuck here for hours now. Can anyone please help me out with this? Thank you in Advance :)

Write streaming testcase in jest for download from remote url

I have the following API /api/v1/download/:id
Usage: http://localhost:3000/api/v1/download/client_app.zip
Implementation:
const express = require("express");
const axios = require("axios");
const { InternalServerError } = require("common-lib");
const router = express.Router();
router.get("/api/v1/download/:id",
async (req, res, next) => {
const { id } = req.params;
try {
const response = await axios.get(
`${process.env.DOWNLOAD_URL}/artifacts/client_app/${id}`, {
auth: {
username: process.env.USERNAME,
password: process.env.PASSWORD
},
responseType: "stream"
});
response.data.pipe(res);
} catch (error) {
return next(new InternalServerError(error.message));
}
});
module.exports = {
downloadByIdRouter: router
}
A user will use the /api/v1/download/:id to download the .zip file from another remote server, the actual credentials and remote URL are hidden from the end-user.
The API is working as expected and I am able to download the file into my system.
The question is how to write the test for this particular streaming scenario in Jest.
describe("download by id test", () => {
test("downloads the app, if requested properly", () => {
// to do
}, )
});
I checked this reference https://dev.to/cdanielsen/testing-streams-a-primer-3n6e. However, I am not exactly getting the approach.
I need guidance in writing the test for this particular scenario, comparatively new to Jest.
Route import
const express = require("express");
// Import routes
const { downloadByIdRouter } = require("./routes/download-byid");
const app = express();
// Use routes
app.use(downloadByIdRouter);
module.exports = {
app
}
Then I import app in the test file and using the supertest library I make the API calls
download.test.js
const request = require("supertest");
const { app } = require("../../app");
describe("download by id test", () => {
test.todo("starts the app download, if requested properly")
});
Thanks

When i run the firebase function in browser i am getting error : Cannot GET /testingtest

i am new in firebase function, i need help, when i run the function i am getting error : Cannot GET /testingtest, can anyone please tell me why i am getting this error, Here i have added my full code
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors({ origin: true }));
app.get('/widgets/testingtest', (req, res) => {
res.send({ 'status': 0});
});
exports.widgets = functions.https.onRequest(app);
Firebase functions need to be emulated locally to test.
Please see the docs on firebase for this:
https://firebase.google.com/docs/functions/local-emulator
Install firebase cli:
npm install -g firebase-tools
Since you are currently testing https functions in your example these commands will work:
firebase emulators:start --only functions
and you will see the url to each https function which you can call from browser or postman etc.
Based on the error message, it looks like you're trying to access /testingtest, but in your code, try:
app.get('/testingtest', (req, res) => {
res.send({ 'status': 0});
});
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors({ origin: true }));
app.get('/testingtest', (req, res) => {
res.send({ 'status': 0});
});
exports.widgets = functions.https.onRequest(app);
The get URL is /widgets/testingtest. Not /testingtest.
See unable to access invoke firebase http end point
For those coming here in recent times, firebase emulators:start is default way to start if you use firebase init to create your firebase functions.
The reason for Cannot /GET /* could be the way firebase exports the api.
After creating the api,
app.get('/widgets/testingtest', (req, res) => {
res.send({ 'status': 0});
});
when it is exported as exports.widgets = functions.https.onRequest(app);
the actual api becomes widgets/widgets/testingtest as firebase exports the api on widgets, (export.widgets).
You can remove the extra widgets from here:
app.get('/widgets/testingtest', (req, res) => {
res.send({ 'status': 0});
});
which then becomes :
app.get('/testingtest', (req, res) => {
res.send({ 'status': 0});
});
This will be available in widgets/testingtest/.

Resources