Custom logging from firebase function - firebase

I'm trying to follow this guide to put some custom logging into a firebase function. The function itself is running, and I can see the data being passed in (it's an https 'callable' function). But as soon as it hits the line where it tries to actually write that log entry, I get "Error: 7 PERMISSION_DENIED"
Since the console.log() calls write to the cloud logs, I'd assumed the firebase function has access to Cloud Logging. But perhaps it needs additional permission? I can't find any reference to where this should be set on that page though.
// Logging, logName, region, functions are provided by the surrounding app
const logging = new Logging()
const log = logging.log(logName)
const METADATA = {
resource: {
type: 'cloud_function',
labels: {
function_name: 'CustomLog',
region
}
}
};
exports = module.exports = functions.https.onCall(async data => {
const exVersion = 6
const exId = data.exId
console.log('***** exVersion:', exVersion, 'exId:', exId) // exId from caller
const entry = log.entry(METADATA, data.error) // data.error from caller
console.log('METADATA:', METADATA) // Shows in Logs Explorer
console.log('entry:', entry) // Shows in Logs Explorer
log.write(entry) // Results in Error: 7 PERMISSION_DENIED
return {
exVersion,
exId,
}
})
If I run it from the CLI using firebase function:shell, the log entry is created correctly, so I'm pretty confident the code is correct.

OK, I finally tracked it down. According to this answer, the service account used by firebase functions is {project-id}#appspot.gserviceaccount.com, and in my project, that account did not have the 'Logs Writer' role. Adding that role solves the problem.
I find it odd that the firebase functions don't need that role to log messages using console.log(), but perhaps that call is intercepted by the functions environment, and the logs are written as a different service account. It also explains why the functions running locally were able to write the logs, as they run using the 'owner' service account, which has full access.

According to the Firebase documentation page you have linked:
The recommended solution for logging from a function is to use the
logger SDK. You can instead use standard JavaScript logging calls such
as console.log and console.error, but you first need to require a
special module to patch the standard methods to work correctly:
require("firebase-functions/lib/logger/compat");
Once you have required the logger compatibility module, you can use console.log() methods as normal in your code.
Thus you might to require this library, however I am not sure this is producing your "Error: 7 PERMISSION_DENIED error, but you might also try some solutions that have worked for some members of the community.

Perhaps the logging API is not enabled in your project. You'll get a permission denied error when attempting to use it in that case.
It's a couple levels in, but the guide you linked points to
https://github.com/googleapis/nodejs-logging#before-you-begin, which includes a step to "Enable the Cloud Logging API"

Related

#google-cloud/firestore returns "Missing or insufficient permissions." [duplicate]

This question already has answers here:
Firebase Cloud Functions Firestore Trigger produces: Error: 7 PERMISSION_DENIED: Missing or insufficient permissions
(9 answers)
Closed 10 months ago.
I'm attempting to use Google Cloud Functions to interact with a Google Firestore collection. Going through the documentation, it seems like the suggested library to use is: https://www.npmjs.com/package/#google-cloud/firestore
the documentation on the library indicates it should only be used in a secure environment (like Cloud Functions) and that it would take advantage of ADC ( Application Default Credentials ). However when I implement it in a cloud function I'm getting the following response from the library call to firestore.get()
{
"code": 7,
"details": "Missing or insufficient permissions.",
"metadata": {}
}
For the sake of this article lets say the project id is "mobile-site"
I made a firestore collection:
"site"
which has a single document:
{
"hi": "there"
}
and a cloud function to call which attempts to get the document:
const firestore_1 = require("#google-cloud/firestore");
const projectId = 'mobile-site';
const firestore = new firestore_1.Firestore({ projectId });
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
return TestStuff().then(v => {
return res.status(200).send(v);
}).catch(reason => {
return res.status(500).send(reason)
})
};
//
function TestStuff() {
const collection = firestore.collection('site');
return collection.get().then(query => {
// this code is never invoked
return {query, version: '1.0.1'}
});
}
As a troubleshooting step, I did the exact same thing above on a personal account, and everything works swimmingly. So its just my company's enterprise solution that causes an issue.
Before you say "check the Firestore Security Rules", this sdk circumvents the firestore rules since its in network and attached to a service account. Speaking of which, here is the service account role/permissions, I've added quite a few in an effort to resolve the issue, so some may be completely unrelated, will use an example name:
IAM & Admin
example-service-account#appspot.gserviceaccount.com
Editor
Service Account User
Viewer
Cloud Functions
example-service-account#appspot.gserviceaccount.com
Cloud Functions Admin
Cloud Functions Invoker
Cloud Functions Service Agent
Editor
Firebase Admin
Any help or suggestions to resolve or troubleshoot further are appreciated.
Turns out the issue is there are multiple IAM "principal" role assignment areas.
Previously I had verified the service account settings here:
https://console.cloud.google.com/
Menu > IAM & ADMIN > Service Accounts > ( Clicking on the default service account ) > Permissions
However the location that resolved the issue for me was found here:
https://console.cloud.google.com/
Menu > IAM & ADMIN > IAM
In my case the service account in question wasn't even present, so it had to be added. My administrators attached the following roles:
Firebase Admin SDK Administrator Service Agent
Service Account Token Creator
And now the functions work
User "Marc Anthony B" pointed me to
Firebase Cloud Functions Firestore Trigger produces: Error: 7 PERMISSION_DENIED: Missing or insufficient permissions
which was extremely close but not exactly the same since I am using a different library and the Role assignment is different.

Firebase and LinkedIn Auth integration unknown route

I've been examining this code base as an example of how to implement LinkedIn authorization to my project with a Firebase Backend. One thing I'm confused about is these lines:
var code = getURLParameter("code");
var state = getURLParameter("state");
var error = getURLParameter("error");
if (error) {
document.body.innerText = "Error back from the LinkedIn auth page: " + error;
} else if (!code) {
// Start the auth flow.
window.location.href = "/redirect";
}
at window.location.href = '/redirect', I believe it is meant to invoke the cloud function called "redirect". In my code base, it simply goes to an unknown route and triggers my fallback. Am I wrong about the purpose of this line of code? Does anyone know any possible reasons it's not triggering the cloud function (console says 0 invocations)? What other information should I look into to try to debug this?
To provide a bit fuller of an answer:
The example you provided relies on a Firebase.json file. This file provides configuration if (and only if) your application is hosted with Firebase hosting (see docs).
If you expect to host your app elsewhere, you'll need to make sure your /redirect path points to the Firebase function URL itself (probably something like https://us-central1-project-name.cloudfunctions.net/redirect). In the authorization flow, the LinkedIn module in the example repo then will redirect to either a default or a configured callback url.

firebase callable functions returning CORS error and not being called from client

I have been using firebase functions for quite some time and all deployments of functions have been going quite smoothly. All of a sudden any new functions deployed have stopped working and any calls from the client return a CORS error. If I check the functions list in the firebase dashboard I can't see the functions being called which is similar to what I would expect if the functions simply didn't exist at all.
I am now just trying a simple basic function like below:
exports.createSession = functions.region('europe-west2').https.onCall(async (data, context) => {
return ({status: 200})
});
On the frontend I am doing a simple call like below:
const createSessionFunction = functions.httpsCallable('createSession');
const response = await createSessionFunction({});
All the other functions that were create and deployed prior to this week are working fine. Any new functions are not.
The error I get is below:
Access to fetch at 'https://europe-west2-xxxxxxx.cloudfunctions.net/createSession' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
index.cjs.js:614 POST https://europe-west2-xxxxxxxz.cloudfunctions.net/createSession net::ERR_FAILED
My function list on the firebase GUI show this function does exist:
createSession - Request - https://europe-west2-xxxxxxxx.cloudfunctions.net/createSession-europe-west2-Node.js 8 -256 MB - 60s
However the logs show that it is never called from the client when I'm trying to test it which means the client might not be detecting this function at all.
I have tried the following steps with no luck:
Delete and redeploy the functions
Rename the function and redeploy
Deploy the same new function on different applications (dev/test etc)
Any ideas?
This was resolved on the google cloud dashboard by granting all my functions public access. The default permissions has changed from public to private.
https://cloud.google.com/functions/docs/securing/managing-access-iam#allowing_unauthenticated_function_invocation

Why do I get auth'ed when I test hitting my Firebase Cloud Function in a browser?

I have a Firebase Cloud Function that handles HTTP requests, using:
export const foo = functions.https.onRequest((req, res) => {
// etc.
}
When I hit the URL for it in a browser, I see a Google sign in page, listing my Google accounts. If I sign in, I then get a 403:
Error: Forbidden
Your client does not have permission to get URL /foo from this server.
Why? There's nothing about this in the docs that I can find. I'm on the free plan ("Spark"), if that makes any difference.
[edit]
I'm accessing the function using the URL:
https://us-central1-[project name].cloudfunctions.net/[function name]
There's no vanity URL.
The 403 message is originating from the main url https://us-central1-[project-name].cloudfunctions.net/ which is fully managed by Google.
It seems cloud functions does not have an error handling for non-existing functions name. Thus everything that is not created are treated the same way as a forbidden link. I don't know if this is an intended behavior but since the functions are running on a managed environment, there's not much handling of not existing functions against your project cloud function url.
The following statement from the link above explains it all:
"Cloud Functions run in a fully-managed, serverless environment where Google handles infrastructure, operating systems, and runtime environments completely on your behalf. Each Cloud Function runs in its own isolated secure execution context, scales automatically, and has a lifecycle independent from other functions. "
Hope this helps.

meteor-shopify authenticator getPermanentAccessToken with code

I'm using the froatsnook:shopify atmosphere package to create an embedded public app on Shopify. I currently have a couple issues:
1) Getting the access token from the "code" query parameter after a user authenticates. As it mentions in the docs here, I'm supposed to use authenticator.getPermanentAccessToken(code) but what I don't understand is how to get call authenticator if the "code" parameter appears on the callback route (at that point, the authenticator I instantiated on the client pre-auth route is out of scope).
2) The "oAuth" function callback is never called for some reason, even when assigning it to Shopify.onAuth on the server.
3) The difference between post_auth_uri and redirect_uri ?
// I call this during 'onBeforeAction' for iron-router
function beforeAuth (query) {
// is this necessary..?
console.assert(Meteor.isClient);
// get shop name like 'myshop' from 'myshop.shopify.com';
const shop = query.shop.substring(0, query.shop.indexOf('.'));
// use api_key stored in settings
var api_key = Meteor.settings.public.shopify.api_key;
// Prepare to authenticate
var authenticator = new Shopify.PublicAppOAuthAuthenticator({
shop: shop,
api_key: api_key,
keyset: 'default',
embedded_app_sdk: true,
redirect_uri: 'https://45a04f23.ngrok.com/testContent',
//post_auth_uri: ???
// This is doesn't seem to be getting
// called after clicking through the OAuth dialog
onAuth: function(access_token) {
ShopifyCredentials.insert({
shop: shop,
api_key: api_key,
access_token: access_token
});
}
});
// Should i use something different with iron-router?
location.href = authenticator.auth_uri;
// how do i get code in this scope???
// authenticator.getPermanentAccessToken(code);
}
There are a few issues with the way you are trying to set up the authenticator, although it's not really your fault because the way Scenario 3 works in the docs is not an 'out of the box' solution and requires a bunch of custom code, including your own handler (I can provide a gist if you REALLY want to build your own handler, but I suggest using the new server-side onAuth callback instead)
1. Specifying a redirect_uri overrides the package's default redirect_uri handler which is Meteor.absoluteUrl("/__shopify-auth").
So instead, completely remove redirect_uri and put your testContent url in post_auth_uri instead.
2. ShopifyCredentials does not exist in this package. If you want to use it that way, make sure you actually have defined a collection called 'ShopifyCredentials' and insert the record from the server, not the client. Note that you will still need to add a keyset on the server for the API methods to work. If you are using user accounts and would like to permanently store credentials, I suggest saving the credentials to the database and adding the keyset via a server-side onAuth callback.
3. authenticator.getPermanentAccessToken(code) isn't useful unless you are using your own handler. Instead, you can just get access_token from the onAuth callback.
Also keep in mind that if you ever need to reauthenticate from inside the embedded app, you need to use window.top.location.href to break out of the iframe.
If you want a complete, working boilerplate example with user accounts see my gist here:
Authentication with Accounts and Persistent Keysets
If you aren't using accounts, you can use this gist instead, but please note that you really need to come up with some way to check that the current client has permission to request the keyset for a given shop before going to production:
Authentication with Persistent Keysets

Resources