firebase cloud functions failed to access google sheet api - firebase

I want to build an api service that use google sheet api to create, read and write the spreadsheets. The api service is deployed on firebase cloud functions with express.js, I have created the service account, and use JWT for authentication. The api works (i.e. can create, read and write spreadsheets) when test locally, but fails when I deployed to firebase cloud functions.
const functions = require('firebase-functions');
// firebase admin
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// google sheet api
const google = require('googleapis');
const key = require('./key/serviceKey.json');
const scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
];
const authClient = new google.auth.JWT(
key.client_email, null, key.private_key, scopes, null
);
authClient.authorize((err, tokens) => {
if (err) {
console.log('error occurred when authorising with JWT');
} else {
console.log('auth success, tokens', tokens);
google.options({ auth: authClient });
}
});
const drive = google.drive('v2');
const sheets = google.sheets('v4');
The error message I get when I test it on firebase cloud functions:
Request is missing required authentication credential. Expected OAuth
2 access token, login cookie or other valid authentication credential.
Ultimately, I will have an ionic app that calls the api to perform certain actions (e.g. create and share spreadsheets with other user). So am I doing the right approach or I should use other types of authentication?
My function definitions:
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors')({ origin: true });
const bodyParser = require('body-parser');
const app = express();
app.use(cors);
app.use(bodyParser.json({ type: 'application/json' }));
// some routing setup
exports.api = functions.https.onRequest(app);
Router setup:
const express = require('express');
const router = express.Router();
router.post('/createSheets', (req, res) => {
const request = { // }
sheets.spreadsheets.create(request, (err, sheetsResponse) => {
// some code
}
});

Related

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 CLI: execute arbitrary Firebase Authentication requests to Firebase Emulator

Using the Firebase CLI, I'm executing arbitrary JavaScript that's stored in a file. In that script, I'm trying to make calls to admin.auth().something(), but it doesn't work against the Firebase Emulator. This is in contrast to making calls to Firestore, which works perfectly fine with the Emulator.
Firestore (everything works)
GCP
This makes calls to Firestore on GCP and it succeeds:
const admin = require('firebase-admin');
admin.initializeApp({ projectId: 'my-project' });
const db = admin.firestore();
(async () => {
const widget = await db.doc('/widgets/123456789').get();
console.log(widget.data().name);
})();
Emulator
This also succeeds:
const admin = require('#firebase/testing');
const db = admin
.initializeAdminApp({ projectId: 'my-project' })
.firestore();
(async () => {
const widget = await db.doc('/widgets/123456789').get();
console.log(widget.data().name);
})();
Firebase Auth (GCP works but Emulator does not)
GCP
This makes calls to Firebase Auth on GCP and it succeeds:
const admin = require('firebase-admin');
admin.initializeApp({ projectId: 'my-project' });
(async () => {
const user = await admin
.auth()
.getUser('user123456789');
console.log(user.email);
})();
Emulator
This fails:
const admin = require('#firebase/testing');
const auth = admin
.initializeAdminApp({ projectId: 'my-project' })
.auth();
(async () => {
const user = await auth.getUser('user123456789');
console.log(user.email);
})();
The error message is:
C:\Users\...\node_modules\#firebase\testing\node_modules\#firebase\component\dist\index.cjs.js:134
throw e;
^
[t [Error]: Your API key is invalid, please check you have copied it correctly.] {
code: 'auth/invalid-api-key',
a: null
}
I'm not sure what API key they're referring to, as the request is against the Emulator. How can I execute Firebase Auth requests against the Emulator using Firebase CLI?
To send requests against Firebase Emulator Auth, set the following environment variables and use the standard Firebase Admin SDK (firebase-admin) instead of using #firebase/testing:
process.env.FIREBASE_AUTH_EMULATOR_HOST = 'localhost:9099';
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';
const admin = require('firebase-admin');
admin.initializeApp({ projectId: 'emulator projectId' });
Now this works:
(async () => {
const user = await auth.getUser('user123456789');
console.log(user.email);
})();

No permission error in Firebase Cloud Function

I want to implement a Slack authentication with Passport.js + Firebase Cloud Function. But when I redirected URL, the forbidden error occurs.
The error:
Your client does not have permission to get URL /api/auth/slack?uid=XXXXXXXXXXX&redirectTo=http://localhost:3000 from this server.
The React code:
const slackAuthorizeURL = (uid) =>
`https://us-central1-xxxxxxxxx.cloudfunctions.net/api/auth/slack?uid=${uid}&redirectTo=${window.location.href}`
<a href={slackAuthorizeURL}>Sign in with Slack</a>
The Server code:
const express = require('express')
const session = require('express-session')
const app = express()
const allowedOrigins = [
'http://localhost:3000',
]
const allowCrossDomain = (req, res, next) => {
const origin = req.headers.origin
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin)
}
res.header('Access-Control-Allow-Methods', 'GET,POST')
res.header('Access-Control-Allow-Headers', 'Content-Type')
next()
}
app.use(allowCrossDomain)
app.use(session({ secret: config.session.secret }))
const passport = require('passport')
app.use(passport.initialize())
app.use(passport.session())
app.get('/auth/slack', (req, res, next) => {
req.session.uid = req.query.uid
req.session.redirectTo = req.query.redirectTo
passport.authenticate('slack')(req, res, next)
})
I have already set up allUsers to Cloud Functions Admin on api in Google Cloud Platform.
I missed to set up allUsers to Cloud Functions Admin on api in Google Cloud Platform correctly.
https://cloud.google.com/functions/docs/securing/managing-access#allowing_unauthenticated_function_invocation

Firestore Cloud Function Times Out When called

I have a custom endpoint setup for my FireStore database.
For now, all I want is to print all values to console, but when I call it from a client, the request times out and the console only says:
#firebase/database: FIREBASE WARNING: The Firebase database
'project-name' has been disabled by a database owner.
(https://project-name-de56eb8.firebaseio.com)
Here's my code. Can anyone tell me what is (what thins are) wrong with it?
const util = require('util');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const language = require('#google-cloud/language');
const client = new language.LanguageServiceClient();
const express = require('express');
const app = express();
app.post('/calculateAverage', async (request, response) => {
const bodyUserId = request.body.id
let query = admin.database().ref(`/user_info/`);
try {
const snapshot = await query.once('value');
snapshot.forEach((childSnapshot) => {
console.log("key: " + childSnapshot.key + " value: " + childSnapshot.val())
});
response.send({"snapshot await": "ok"});
} catch(error) {
console.log('Error getting messages', error.message);
response.send({"snapshot await error": error.message});
}
});
exports.api = functions.https.onRequest(app);
The problem is that you no use firebase realtime data.
in the options of firebase you have database and next *Cloud Firestore and
*Realtime Database, select Realtime Database and after, active this option and with this the solution

Dialogflow sign in user payload

I have implemented Account linking with Google Sign-In into dialogflow but I can't retrieve the user datas.
Into my webhook "actions_intent_SIGN_IN", conv.user.profile.payload is always empty.
However signing in seems to have worked as SIGN_IN status is "OK".
Here is the documentation:
https://developers.google.com/actions/identity/google-sign-in
Here is my fulfillment webhook:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {WebhookClient, Suggestion} = require('dialogflow-fulfillment');
const {dialogflow, Permission, Image, SignIn, BasicCard} = require('actions-on-google');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
db.settings({timestampsInSnapshots: true});
const {ssml} = require('./util');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({request, response});
let conv = agent.conv();
function ask_for_sign_in(agent) {
let conv = agent.conv();
conv.ask(new SignIn('Per personalizzare'));
agent.add(conv);
}
function actions_intent_SIGN_IN(agent) {
let conv = agent.conv();
const granted = conv.arguments.get('SIGN_IN').status === 'OK';
console.log('name', conv.user.profile.payload);
if(granted){
agent.add('granted');
}else{
agent.add('not granted');
}
agent.add('test');
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('ask_for_sign_in', ask_for_sign_in);
intentMap.set('actions_intent_SIGN_IN', actions_intent_SIGN_IN);
agent.handleRequest(intentMap);
});
conv.user.profile.payload is only populated if you use "actions-on-google" library. since you are using "dialogflow-fulfillment" as your webhook client, you have to do the token verification yourself. you can access the JWT token from conv.request.user.idToken
here is document explaining how to do the verification
https://developers.google.com/identity/sign-in/web/backend-auth

Resources