Why does my Firebase/Postmark email despatch function fail to deploy? - node.js

Firebase deployment of my function fails with the following error:
"Function failed on loading user code. This is likely due to a bug in the user code."
Here's the function code:
const postmark = require("postmark");
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp()
exports.sendPostmarkEmailFunction = functions.firestore.
document('/postmarklogs/{documentId}').
onCreate((snapShot, context) => {
var serverToken = "_my_client_key_";
var client = new postmark.ServerClient(serverToken);
try {
client.sendEmail({
"From": "_my_depatch_address_",
"To": "_my_receipt_address_",
"Subject": snapShot.data().subject,
"HtmlBody": snapShot.data().message
});
return true;
} catch (error) {
console.log("Error : " + error.ErrorCode + " : " + error.Message);
return false;
}
});
This code works just fine in the Firebase emulator. As far as I can see, the deployment issue is triggered specifically by the const postmark = require("postmark"); line. If I comment this out, the function deploys - but then of course it doesn't work!
Advice would be greatly appreciated.

Postmark needs to be installed in the project's 'functions' folder. I'd installed it into the body of the project and so Postmark was missing from the 'functions/package.json' file that guides deployment's build stage. The 'functions' folder created by firebase init functions is like a project within a project and needs to be treated as such.
I got onto the problem from the deployment "build" logs
Once Postmark had been installed in the 'functions' folder my sendPostmarkEmailFunction function worked perfectly.
In passing, unless you already know this, the Postmark API token really needs to be squirreled securely away in the Firebase environment variable store. Also, while you might be tempted to use an https.onRequest trigger rather than the onCreate() used here, you might like to know that this is likely to land you in endless CORS issues when used with Postmark.

Related

XERO-NODE SDK => How to choose a specific email template

I am using the Xero-node SDK to automatically create client invoices which works well.
At the end of the process, I would like to automatically email the client the invoice.
In the documentation it has the following example:
const xeroTenantId = 'YOUR_XERO_TENANT_ID';
const invoiceID = '00000000-0000-0000-0000-000000000000';
const requestEmpty: RequestEmpty = { };
try {
const response = await xero.accountingApi.emailInvoice(xeroTenantId, invoiceID, requestEmpty);
console.log(response.body || response.response.statusCode)
} catch (err) {
const error = JSON.stringify(err.response.body, null, 2)
console.log(`Status Code: ${err.response.statusCode} => ${error}`);
}
I have 2 questions:
The requestEmpty method does not work in javascript. Does anyone know the correct structure of requestEmpty?
I have used requestEmpty = { } but this throws an error => even though the system does actually send an email (probably a bug)
AND....
Is there a way for me to specify the email template that I would like the invoice to use (if I have specific templates setup in the web version)? Currently it seems to use the default Xero email template.
If you don't get an answer to your first query here, please can you raise it on the SDK page in Github and the Xero SDK team will look into this for you.
With regards to point 2, it is not possible to choose the email template when sending through the API, a basic template is used.

Can I schedule a Google Cloud Task on the client side to call a cloud function with a payload?

I want to make sure I'm thinking about Cloud Tasks right conceptually, and not sure that I am.
The examples I've been looking at seem to trigger a cloud function first that then schedules a task, that then calls a cloud function again.
(Or at least this is what I'm understanding, I could be wrong).
I'd like to set up something so that when a user clicks a button, it schedules a cloud task for some time in the future (anywhere from 1 minute to an hour and half). The cloud task then triggers the cloud function to upload the payload to the db.
I tried to set this up client side but I've been getting the error "You need to pass auth instance to use gRPC-fallback client in browser or other non-Node.js environments."
I don't want the user to have to authenticate if that's what this is saying (not sure why I'd have to do that for my use case).
This is the code that gives that error.
const {CloudTasksClient} = require('#google-cloud/tasks');
const client = new CloudTasksClient();
// import { Plugins } from '#capacitor/core';
// const { RemotePlugin } = Plugins;
const scheduleTask = async(seconds) => {
async function createHttpTask() {
const project = 'spiral-productivity';
const queue = 'spiral';
const location = 'us-west2';
const url = 'https://example.com/taskhandler';
const payload = 'Hello, World!';
const inSeconds = 5;
// Construct the fully qualified queue name.
const parent = client.queuePath(project, location, queue);
const task = {
httpRequest: {
httpMethod: 'POST',
url,
},
};
if (payload) {
task.httpRequest.body = Buffer.from(payload).toString('base64');
}
if (inSeconds) {
// The time when the task is scheduled to be attempted.
task.scheduleTime = {
seconds: inSeconds + Date.now() / 1000,
};
}
// Send create task request.
console.log('Sending task:');
console.log(task);
const request = {parent: parent, task: task};
const [response] = await client.createTask(request);
console.log(`Created task ${response.name}`);
}
createHttpTask();
// [END cloud_tasks_create_http_task]
}
More recently I set up a service account and download a .json file and all of that. But doesn't this mean my users will have to authenticate?
That's why I stopped. Maybe I'm on the wrong track, but if anyone wants to answer what I need to do to schedule a cloud task from the client side without making the user authenticate, it would be a big help.
As always, I'm happy to improve the question if anything isn't clear. Just let me know, thanks!
Yes.
Your understanding is mostly accurate. Cloud Tasks is a way to queue "tasks". The examples are likely using Cloud Functions as an analog for "some app" (a web app) that would be analogous to your Node.js (web) app, i.e. your Node.js app can submit tasks to Cloud Tasks. To access Google Cloud Platform services (e.g. Cloud Tasks), you need to authenticate and authorize.
Since your app is the "user" of the GCP services, you're correct in using a Service Account.
See Application Default Credentials to understand authenticating (code) as a service account.
Additionally, see Controlling access to webapps.

Identity platform auth blocking function deploy in typescript

I am trying to write an auth blocking function to prevent users creating accounts from the client side in google identity platform. I have written it in typescript but I am struggling to deploy it
Here is a the website about the blocking functions https://cloud.google.com/identity-platform/docs/blocking-functions
My actual function looks like this
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as gcipCloudFunctions from 'gcip-cloud-functions';
const authClient = new gcipCloudFunctions.Auth();
exports.beforeCreateBlockingFunction = authClient.functions().beforeCreateHandler((user, context) => {
throw new gcipCloudFunctions.https.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
});
Have I got the method signature correct?
I am trying to deploy it using the following command. I am not sure whether this works with typescript. Is this the correct way?
// Http trigger with Cloud Functions.
// gcloud functions deploy beforeCreateBlockingFunction --runtime nodejs10 --region europe-west1 --trigger-http --allow-unauthenticated
Any help is appreciated.
Kind regards
Declaration of method for typescript is not correct.. name of function don't have to be beforeCreate.. you can name it as per your convention. Refer below TS code for beforeCreateHandler event
import * as gcipCloudFunctions from 'gcip-cloud-functions';
const authClient = new gcipCloudFunctions.Auth();
export const beforeRegister = authClient.functions().beforeCreateHandler((user: gcipCloudFunctions.UserRecord,
context: gcipCloudFunctions.AuthEventContext) => {
console.log("User Object: " + JSON.stringify(user));
console.log("Context Object: " + JSON.stringify(context));
return {
customClaims: {
verified: true
},
};
});
I’m not really sure if this is how you are deploying your code, but I want to underline that you may be changing the name of the beforeCreate function to beforeCreateBlockingFunction. I thought you could choose any name you like for the function, but I realized the name should be as the documentation you are following says.

google-cloud/resource' cloud function not listing all projects in response

I am using a cloud function written in node.js to list projects this is the index.js file containing the method, When I trigger this function I am getting only 1 project printed. ProjectA -> the cloud function also resides in ProjectA, I have another ProjectB which is not getting printed which is also in ACTIVE mode. I have owner permission for both the projects.
const {Resource} = require('#google-cloud/resource');
const resource = new Resource();
async function getProjects() {
try {
// Lists all current projects
const [projects] = await resource.getProjects();
console.log(`success in getProjects() call`);
// Set a uniform endTime for all the resulting messages
const endTime = new Date();
const endTimeStr = endTime.toISOString();
// sample 2019-11-12T17:58:26.068483Z
for (var i=0; i<projects.length;i++) {
console.log("Total Projects ",projects.length) //Printing as 1 instead of correct 2
// Only publish messages for active projects
if (projects[i]["metadata"]["lifecycleState"] === config.ACTIVE) {
// Construct a Pub/Sub message
console.log(`About to send Pub/Sub message ${projects[i]}}`);
const pubsubMessage = {
"token": config.METRIC_EXPORT_PUBSUB_VERIFICATION_TOKEN,
"project_id": projects[i]["id"],
"end_time": endTimeStr
}
}
}
} catch(err) {
console.error("Error in getProjects()");
console.error(err);
throw err;
}
}
However if i try the google api link
https://cloud.google.com/resource-manager/reference/rest/v1/projects/list#try-it
I am getting 2 projects as response which i have access to.
When you execute a Cloud Function you choose a service account that will execute it, normally it's the "App Engine default service account (project-id#appspot.gserviceaccount.com), that service account should have the "Project Owner" role.
The API call from the API explorer uses an API key that it's tied to your user account no the service account used to execute the Cloud Functions, that's why it shows you all your projects.
To fix your issue just add the service account, that you're using to execute the Cloud Function, to all your Projects with the Project Owner role, although other roles (like Project Viewer) are enough to list it.

Using Firebase parameters with Google Cloud Storage under node.js

There is no node.js Firebase Storage client at the moment (too bad...), so I'm turning to gcloud-node with the parameters found in Firebase's console.
I'm trying :
var firebase = require('firebase');
var gcloud = require('gcloud')({
keyFilename: process.env.FB_JSON_PATH,
projectId: process.env.FB_PROJECT_ID
});
firebase.initializeApp({
serviceAccount: process.env.FB_JSON_PATH,
databaseURL: process.env.FB_DATABASE_URL
});
var fb = firebase.database().ref();
var gcs = gcloud.storage();
var bucket = gcs.bucket(process.env.FB_PROJECT_ID);
bucket.exists(function(err, exists) {
console.log('err', err);
console.log('exists', exists);
});
Where :
FB_JSON_PATH is the path to the JSON file generated in order to use the Firebase Server SDK
FB_DATABASE_URL is something like https://app-a36e5.firebaseio.com/
FB_PROJECT_ID is the name of the firebase project in Google's console : "app-a36e5"
The id of the bucket is FB_PROJECT_ID (in Firebase's console the storage tab displays gs://app-a36e5.appspot.com)
When I run this code I get :
err null
exists false
But no other errors.
I'm expecting exists true at least.
Some additional info : I can query the database (so I imagine the JSON file is correct), and I have set the storage rules as follow :
service firebase.storage {
match /b/app-a36e5.appspot.com/o {
match /{allPaths=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
So that everything on the storage is readable.
Any ideas how to get it to work ? Thank you.
The issue here is that you aren't naming your storage bucket correctly. The bucket initialization should be:
var bucket = gcs.bucket('app-a36e5.appspot.com'); // full name of the bucket includes the .appspot.com
I would assume that process.env.FB_PROJECT_ID is just the your-bucket part, and you'd need to get the full bucket name, not just the project id (though the bucket name may be process.env.FB_PROJECT_ID + '.appspot.com').
Also, sorry about not providing Storage integrated with Firebase--GCS has a high quality library that you've already found (gcloud-node), and we figured that this provides the best story for developers (Firebase for mobile, Google Cloud Platform for server side development), and didn't want to muddy the waters further.

Resources