Permissions Error with Cloud Function Adding Data to Firestore - firebase

I'm trying to setup a Cloud Function that, when ran by Cloud Scheduler, will insert certain data into my Firestore Database. I am doing this in Node.js using the Inline editor provided when creating my Cloud Function.
I keep getting the error:
"Error: function crashed. Details:
7 PERMISSION_DENIED: Missing or insufficient permissions."
On my Firebase dashboard, the logs show my function and the error that I get when I test my Cloud Function, so I'm assuming my function is hitting the database, just not adding the dummy data I was testing with.
index.js:
const Firestore = require('#google-cloud/firestore');
const PROJECTID = 'MY_PROJECT_ID';
const firestore = new Firestore({
projectId: PROJECTID,
timestampsInSnapshots: true,
});
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
return firestore.collection("users").add({
first: "Ada",
last: "Lovelace",
born: 1815
});
};
Package.json:
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"#google-cloud/firestore": "0.17.0",
"semver": "^5.5.1"
}
}
I also have my rules set for my database as:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}

Thanks to #andresmijares I was able to fix my problem. I looked more into the quickstart and changed my index.js as follows (specifically everything before the helloWorld function).
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
const db = admin.firestore();
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
/* let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(message);
*/
return db.collection("users").add({
first: "Ada",
last: "Lovelace",
born: 1815
});
};
And I got the error
"Code in file index.js can't be loaded. Did you list all required modules in the package.json dependencies? Detailed stack trace: Error: Cannot find module 'firebase-admin'"
Which I was able to fix by adding the 'firebase-admin' dependency into my package.json, as follows:
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"semver": "^5.5.1",
"#google-cloud/firestore": "^1.3.0",
"firebase-admin": "^7.1.1"
}
}
This was also all done in the inline editor provided when creating my Cloud Function, so no installation of anything was needed.

you need to download the sdk key, this is a json file that you can export from your firebase console Project Overview -> Project Settings -> Services Accounts
Then you can instantiate it like this:
var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
When using the firebase admin sdk, the firestore security rules do not apply (they are only for client-side operations)

Related

Error when setting up Firebase automated backup with Google Cloud Functions

I'm trying to setup automatic backup of my Firestore using instructions here: https://firebase.google.com/docs/firestore/solutions/schedule-export
I get error:
firestoreExpert
g2o6pmdwatdp
TypeError: Cannot read properties of undefined (reading 'charCodeAt')
at peg$parsetemplate (/workspace/node_modules/google-gax/build/src/pathTemplateParser.js:304:17)
at Object.peg$parse [as parse] (/workspace/node_modules/google-gax/build/src/pathTemplateParser.js:633:18)
at new PathTemplate (/workspace/node_modules/google-gax/build/src/pathTemplate.js:55:54)
Any suggestions to debug this?
I've tried looking for errors in my permissions. E.g. I don't know how to check if the service has access to the specific bucket, although the GCL ran OK.
I've also tried looking for errors in the script.
index.js
const firestore = require('#google-cloud/firestore');
const client = new firestore.v1.FirestoreAdminClient();
// Replace BUCKET_NAME
const bucket = 'gs://EDITEDHERE.appspot.com'
exports.scheduledFirestoreExport = (event, context) => {
const databaseName = client.databasePath(
process.env.GCLOUD_PROJECT,
'(default)'
);
return client
.exportDocuments({
name: databaseName,
outputUriPrefix: bucket,
// Leave collectionIds empty to export all collections
// or define a list of collection IDs:
// collectionIds: ['users', 'posts']
collectionIds: [],
})
.then(responses => {
const response = responses[0];
console.log(`Operation Name: ${response['name']}`);
return response;
})
.catch(err => {
console.error(err);
});
};
and package.json
{
"dependencies": {
"#google-cloud/firestore": "^1.3.0"
}
}
I found these great video tutorials
How to schedule firestorm backups and
How To Transfer Firestore Data From One Project To Another

'functions' is assigned a value but never used

I am getting an error in terminal when trying to run 'firebase deploy'. This brings the error
1:7 error 'functions' is assigned a value but never used no-unused-vars
here is my index.js file
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// Take the text parameter passed to this HTTP endpoint and insert it into
// Firestore under the path /messages/:documentId/original
exports.addMessage = functions.https.onRequest(async (req, res) => {
// Grab the text parameter.
const original = req.query.text;
// Push the new message into Firestore using the Firebase Admin SDK.
const writeResult = await admin.firestore().collection('messages').add({original: original});
// Send back a message that we've successfully written the message
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
// Grab the current value of what was written to Firestore.
const original = snap.data().original;
// Access the parameter `{documentId}` with `context.params`
functions.logger.log('Uppercasing', context.params.documentId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to Firestore.
// Setting an 'uppercase' field in Firestore document returns a Promise.
return snap.ref.set({uppercase}, {merge: true});
});
this is directly from the firebase functions documentation
Your index file is outside the functions folder.
In my case i just added warning instead of error in the rules at the
.eslintrc.js file
rules: {
"no-unused-vars": "warn"
},
You can also wrap any block of code with eslint specific comments to disable a rule for that block:
/* eslint-disable no-unused-vars */
(....Your Code....)
/* eslint-enable no-unused-vars */
this way you need not to comment your pre-deploy script checks
and you can disable the enlint properties for the selected block
In firebase.json, delete these 3 lines :
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint"
]
In functions folder/package.json, delete these lines :
"devDependencies": {
"eslint": "^8.9.0", // delete
"eslint-config-google": "^0.14.0", // delete
}
"scripts": {
"lint": "eslint .", // delete
},
Delete node_modules in function folder then reinstall all with npm install (you must be in the function folder)
Then : firebase deploy will work fine

Flutter Firebase Config Variables

I've set some config variables using the command firebase functions:config:set algolia.appid="app_id" algolia.apikey="api_key", but how do I utilize them in my Flutter app? I have firebase_core installed.
In TypeScript you would write the following:
import * as admin from 'firebase-admin';
admin.initializeApp();
const env = functions.config();
console.log(env.algolia.appid);
But what about in Dart and Flutter?
Thanks
The configuration variables you set through firebase functions:config:set are (as the command implies) available only in Cloud Functions. They're not in any way propagated to client-side application by this command. In general that'd be an anti-pattern, as such configuration variables are often used for keeping trusted credentials.
If you have a use-case where the value needs to be available in the client-side application too, you have a few ways to do that:
Create an additional Cloud Functions endpoint where you expose the value of the configuration variable. Typically this would be a HTTPS or Callable function, which you then call from your client-side code.
Push the value into another place where your application code can read it from at the same time that you call firebase functions:config:set. This could be a configuration file of your app, or even a .dart file that you generate.
I also ran into this problem and found myself on this S.O. thread. I tried following Frank van Puffelen's suggestion above.
In functions/.runtimeconfig.json:
{
"algolia": {
"appid": "ID",
"apikey": "KEY"
},
"webmerge": {
"key": "KEY",
"secret": "SECRET",
"stashkey": "STASH_KEY"
},
}
In functions/index.ts:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
. . .
const cors = require('cors')({origin: true})
const envObj = functions.config()
. . .
export const getEnv = functions.https.onRequest((req, resp) => {
cors(req, resp, () => resp.status(200).send(JSON.stringify(envObj)));
});
. . .
NOTE: I used the cors package to get around CORS errors when working locally. I would get these errors when localhost:5000 (Emulator hosting) called localhost:5001 (Emulator functions).
In web_flutter/main.dart:
Future<Map<String, dynamic>> fetchEnv(String functionsURL) async {
var response = await http.get('${functionsURL}/getEnv');
return json.decode(response.body);
}
Future<void> main() async {
try {
var functionsURL = 'FUNCTIONS_URL';
var app = fb.initializeApp(firebase app details);
if (window.location.hostname == 'localhost') {
app.firestore().settings(Settings(
host: 'localhost:8080',
ssl: false,
));
functionsURL = 'http://localhost:5001';
}
var env = await fetchEnv(functionsURL);
var searchClient = Algolia.init(
applicationId: env['algolia']['appid'],
apiKey: env['algolia']['apikey']);
runApp(MyApp(
repository: Repository(app.firestore(), searchClient),
authentication: Authentication(app.auth())));
} on fb.FirebaseJsNotLoadedException catch (e) {
print(e);
}
}
Once I confirmed that this was working locally, I was able to use firebase functions:config:set to set this data in the live Functions environment and deploy my updated hosting and functions with firebase deploy.

Sendgrid & Firebase Functions: Error Sending Transactional Email with Dynamic Template Data

Once a new vendor is registered on my Firebase Realtime Database, I want to send the new vendor a welcome email via Sendgrid. I've constructed a Firebase function newVendorEmail() to do this in my app's functions/src/index.ts folder and configured everything there as per https://app.sendgrid.com/guide/integrate/langs/nodejs/verify. I'm also able to retrieve vendor details from Firebase via onCreate() in newVendorEmail() and pass them to the dynamic_template_data part of the msg object without any problem. But when the newVendorEmail() function was triggered in Firebase Functions the email was not sent and I got this response instead in my Firebase Functions Console: TypeError: Object.values is not a function at Mail.setDynamicTemplateData (/user_code/node_modules/#sendgrid/mail/node_modules/#sendgrid/helpers/classes/mail.js:342:12). Help, please?
I've tried upgrading to the latest #sendgrid/mail npm package v6.4.0, tried switching to a new Sendgrid API key, tried storing this new API key in process.env as per Sendgrid's github example https://github.com/sendgrid/sendgrid-nodejs/blob/master/use-cases/kitchen-sink.md instead of functions.config(), but to no avail.
in node/process.env:
{ SENDGRID_API_KEY:
'SG....E',
...
}
in functions/src/index.ts:
'use strict'
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const sendgrid = require('#sendgrid/mail')
// init function
admin.initializeApp()
//init firebase ref const
const ref = admin.database().ref()
// set sendgrid api from process env
sendgrid.setApiKey(process.env.SENDGRID_API_KEY)
export const newVendorEmail = functions.database
.ref('users/{userId}/profile')
.onCreate((snapshot, context) => {
// call field data using snapshot.val()
let msg
const userData = snapshot.val()
if (userData.type === 'vendor') {
// set email data
msg = {
to: userData.email,
from: {
name: 'Blk. Party',
email: '...#blkparty.com'
},
// custom templates
templateId: '...',
dynamic_template_data: {
subject: 'Welcome to Blk. Party!',
name: userData.name,
regLink: userData.regLink
},
}
}
// send email via sendgrid
return sendgrid.send(msg)
})
in package.json:
...
"dependencies": {
"#sendgrid/mail": "^6.4.0",
"firebase-admin": "~6.0.0",
"firebase-functions": "^2.1.0"
},
"devDependencies": {
"#sendgrid/mail": "^6.4.0",
...
}
...
I expect emails to be sent without any error.
I had the same problem. In my case, the solution was to switch from node6 to node8 in firebase functions.

CORS on firebase storage

I'm gettin the normal cors error on my firebase storage when I do a get call on an html file:
Request header field Access-Control-Allow-Origin is not allowed by Access-Control-Allow-Headers in preflight response.
I'm using axios for the call:
axios.get('https://firebasestorage.googleapis.com/v0/b/xxxxx-xxxxx.appspot.com/o/files%2Fsigning%2F148%2F459.html?alt=media&token=f3be2ef2-a598-4c30-a77b-8077e8b1f7bc',
{
headers: {'Access-Control-Allow-Origin': '*',}
)
I have the access set to public:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}
This same setup works fine when I load images but it's giving me the error for the stored html file. Any thoughts on how to fix it?
Firebase is using the same storage infrastructure as google cloud and even though there is no firebase method to set the cors rules, you can use gc set up.
First you need to install google cloud sdk:
curl https://sdk.cloud.google.com | bash
Restart your shell:
exec -l $SHELL
Initialize gcloud. This will ask you to select your account and authenticate.
gcloud init
Then create a json file with the following content
[
{
"origin": ["http://example.appspot.com"],
"responseHeader": ["Content-Type"],
"method": ["GET", "HEAD", "DELETE"],
"maxAgeSeconds": 3600
}
]
And run this with your firebase storage gc: endpoint
gsutil cors set yourFile.json gs://yourProject
That should fixe the problem for you as well.
After init the application you need to use setCorsConfiguration method
// Imports the Google Cloud client library
const {Storage} = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';
// The origin for this CORS config to allow requests from
// const origin = 'http://example.appspot.com';
// The response header to share across origins
// const responseHeader = 'Content-Type';
// The maximum amount of time the browser can make requests before it must
// repeat preflighted requests
// const maxAgeSeconds = 3600;
// The name of the method
// See the HttpMethod documentation for other HTTP methods available:
// https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/urlfetch/HTTPMethod
// const method = 'GET';
async function configureBucketCors() {
await storage.bucket(bucketName).setCorsConfiguration([
{
maxAgeSeconds,
method: [method],
origin: [origin],
responseHeader: [responseHeader],
},
]);
console.log(`Bucket ${bucketName} was updated with a CORS config
to allow ${method} requests from ${origin} sharing
${responseHeader} responses across origins`);
}
configureBucketCors().catch(console.error);
This is my example with NestJs
import { ConfigService } from '#nestjs/config';
import * as admin from 'firebase-admin';
const configureBucketCors = async (app) => {
const configService: ConfigService = app.get(ConfigService);
return admin
.storage()
.bucket(configService.get<string>('BUCKET_NAME'))
.setCorsConfiguration([
{
origin: ['http://localhost:3000'],
responseHeader: ['Content-Type'],
method: ['GET', 'HEAD', 'DELETE'],
maxAgeSeconds: 3600,
},
]);
};
export default async (app) => {
const configService: ConfigService = app.get(ConfigService);
await admin.initializeApp({
databaseURL: configService.get<string>('FIREBASE_DATABASE_URL'),
storageBucket: configService.get<string>('BUCKET_NAME'),
});
await configureBucketCors(app);
};
Check the docs for more details
https://cloud.google.com/storage/docs/cross-origin
https://cloud.google.com/storage/docs/configuring-cors#storage_cors_configuration-nodejs

Resources