Use of Service account credentials when using Cloud Functions Shell - firebase

I've just migrated to Cloud Functions 1.0 and am trying out Cloud Functions shell/emulator to run functions locally (using instructions at https://firebase.google.com/docs/functions/local-emulator)
One of the functions is using code below to upload a file to cloud storage and then then generate url for it....but am getting following error:
SigningError: Cannot sign data without client_email.
const bucket = gcs.bucket(bucketName);
bucket.upload(localFilePath, {destination: destinationPath})
.then(data => {
const file = data[0];
return file.getSignedUrl({
action: 'read',
expires: '01-01-2099'
});
I can work around this locally by explicitly setting keyFileName as shown below but seems like this should not be necessary
const gcs = require('#google-cloud/storage')({keyFilename: 'service-account.json'});
The link above mentions that "Cloud Firestore and Realtime Database triggers already have sufficient credentials, and do not require additional setup" (I'm triggering this code from db write). I'm setting GOOGLE_APPLICATION_CREDENTIALS env variable in any case but doesn't look like it's picking it up.

Related

What is the difference between mediaLink and SignedUrl in firebase storage

I have a firebase function to upload files to firebase storage, after upload I have to return the url (as Reset response) so that user can view the file
const bucket = admin.storage().bucket();
const [file, meta] = await bucket.upload(tempLocalFile, {
destination: uploadPath,
resumable: false,
public: true,
});
I have two options
1- const signedUrl = await file.getSignedUrl({ action: 'read', expires: '03-09-2491' });
2- meta.mediaLink
SignedUrl will be like https://storage.googleapis.com/web-scanner-dev.appspot.com/pwc%2Fwww.x.com%2F2019-11-17%2Fdesktop%2Fscreenshot-2019-11-17-1125.png?GoogleAccessId=firebase-gcloud%40scanner-dev.iam.gserviceaccount.com&Expires=16447035600&Signature=w49DJpGU9%2BnT7nlpCiJRgfAc98x4i2I%2FiP5UjQipZQGweXmTCl9n%2FnGWmPivkYHJNvkC7Ilgxfxc558%2F%2BuWWJ2pflsDY9HJ%2Bnm6TbwCrsmoVH56nuGZHJ7ggp9c3jSiGmQj3lOvxXfwMHXcWBtvcBaVj%2BH2H8uhxOtJoJOXj%2BOq3EC7XH8hamLY8dUbUkTRtaWPB9mlLUZ78soZ1mwI%2FY8DqLFwb75iob4zwwnDZe16yNnr4nApMDS7BYPxh4cAPSiokq30hPR8RUSNTn2GxpRom5ZiiI8dV4w%2BxYZ0DvdJxn%2FW83kqnjx6RSdZ%2B9S3P9yuND3qieAQ%3D%3D
and mediaLink will be like https://storage.googleapis.com/download/storage/v1/b/web-scanner-dev.appspot.com/o/pwc%2Fwww.x.com%2F2019-11-17%2Fdesktop%2Fscreenshot-2019-11-17-1125.png?generation=1574007908157173&alt=media
What is the pros and cons of each?
The mediaLink does not convey any access permissions on its own -- thus, the object itself will need to be publicly readable in order for end uers to make use of the link (or you will need to be authenticated as an account with read access to that bucket when you execute the link).
On the other hand, a URL returned by getSignedUrl will have a signature that allows access for as long as the URL hasn't expired. Thus, the link alone is sufficient (if temporary) permission to access the blob. Additionally, the URL that is generated maintains the permissions of the user who created it -- if that user loses access to the blob before the link would otherwise expire, the link will no longer function.

How to listen for realtime updates in Firebase Cloud Firestore?

In the official documentation of Firebase, we can do it with https://firebase.google.com/docs/firestore/query-data/listen
let doc = db.collection('cities').doc('SF');
let observer = doc.onSnapshot(docSnapshot => {
console.log(`Received doc snapshot: ${docSnapshot}`);
// ...
}, err => {
console.log(`Encountered error: ${err}`);
});
But how can we do it with Google Apps Script?
To run an Apps Script on a event (like a data update) implies using triggers. Unfortunately, at the moment there are no Apps Script triggers supporting Firebase.
However, you can include JavaScript code in the HTML file attached to your Apps Script file and deploy it as a Web App.

Google Cloud Cloud Function signed url yields 403 SignatureDoesNotMatch

I'm getting a 403 SignatureDoesNotMatch error when trying to load a url generated through:
file.getSignedUrl({
expires: moment()
.add(10, 'minutes')
.format()
})
I've done all the steps outlined in the example, including adding a service account token creator to the App Engine default service account to allow the creation of signed urls:
As an alternative approach to using admin via firebase-functions I tried downloading service account credentials service-account-credentials.json and creating a gcs storage object as suggested here as such:
const { Storage } = require('#google-cloud/storage');
const storage = new Storage({
keyFilename: 'service-account-credentials.json',
projectId: 'project-id',
});
storage.bucket('bucket-id').getFiles({prefix: 'path/to/dir'}).then(files => files.map(file => [same code as above]));
However this still generates SignatureDoesNotMatch urls.
I've followed the github issue related to the matter but I have not been able to find a viable solution. The solution listed by Firebase dev owner #mcdonamp in the issue references using iam.signBlobRequest but I don't know where iam is defined, I only see it here as a property of bucket, with no signBlobRequest method, and here as an HTTP API endpoint.
Seems that despite the doc's claim of Content-Type headers being optional, they are not. As suggested by this SO post and this github issue, adding contentType to the getSignedUrl options argument fixes the issue:
file.getSignedUrl({
action: 'read',
contentType: 'audio/wav',
expires: moment()
.add(10, 'minutes')
.format()
})
Make sure to also include the header when requesting the resource as well.

Firebase cloud function to use sendgrid for sending emails when a contact form gets submitted to firestore

I want my firebase backend to send an email to me when a document is created in a firestore collection based on a form submission in my vue app..
I found sendgrid to be the easiest to get the job done, the example mentioned in the package page suggests that I store the API key in an Environment variable.
Since this will run from a cloud function, I used the following command firebase functions:config:set sendGrid.key="THE API GOES HERE" as mentioned in Firebase docs here
cloud function
I initialized the firebase cloud functions locally, then I called the admin module so i can listen to onCreate() when a document is created in firestore,
I used sendGrid inside the callback function of onCreate()..
I tested the code and checked the functions logs in my firebase project and it gets invoked and finished successfully with a status ok, which means that everything should be working fine.
here is my index.js code inside the /functions folder in my project root
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// sendGrid
const sgMail = require('#sendgrid/mail');
// the cloud function
exports.formSubmitted = functions.firestore.document('message/{messageId}').onCreate(doc => {
// referencing the form data
const formData = doc.data();
// the following should be logged in the function logs in my firebase project
console.log(formData);
// retrieving the environment variable
sgMail.setApiKey(functions.config().sendgrid.key);
// the message to be sent
const msg = {
to: 'MY-EMAIL#gmail.com',
from: formData.email,
subject: 'new user submitted our contact form',
text: formData.message,
html: '<h3> test email from sendGrid </h3>'
}
return sgMail.send(msg);
})
result:
everything worked fine except I didn't receive the email.
If further code/explanation is needed, please leave a comment below.
any help or hints is highly appreciated, thanks in advance.

Firebase cloud functions -#google-cloud/storage initialization

First question here.
I am trying to write a firebase cloud function to compress a file. I went through a lot of examples on the web but my code keeps getting stuck at two points.
1.const {gcs} =require('#google-cloud/storage')();
When I use this construct in require , I get the following error
TypeError: require(...) is not a function
If I change this to
const {gcs} =require('#google-cloud/storage'); the error goes away but apparently the object isn't initialized because I get this error when I try to access it like so
TypeError: Cannot read property 'bucket' of undefined
at exports.onfilechangecompressor.functions.storage.object.onFinalize.object
(/user_code/index.js:21:27)
at cloudFunctionNewSignature (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:105:23)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:135:20)
at /var/tmp/worker/worker.js:733:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
This is the line where I use gcs like so
const destBucket = gcs.bucket(bucket); where bucket is the object.bucket returned ( object is returned by onFinalize ).
Can someone please tell me how to initialize the storage so it works and returns a valid object.
My node.js version is 8.12.0
firebase version is 5.1.1
The documentation for 2.x shows this:
const {Storage} = require('#google-cloud/storage');
You're adding () after the require, which you're discovered is not correct.
You then go on to initialize it like this:
// Your Google Cloud Platform project ID
const projectId = 'YOUR_PROJECT_ID';
// Creates a client
const storage = new Storage({
projectId: projectId,
});
You may not need to specify the project id if you're running in Cloud Functions or other Google environments.
After that, you can get a reference to your default bucket:
storage.bucket()
You can also use the Admin SDK to invoke the same cloud storage APIs:
const admin = require('firebase-admin');
admin.initializeApp();
const storage = admin.storage();
storage.bucket();

Resources