Securing Cloud Functions auth onDelete trigger - firebase

How do we secure the auth().onDelete handler so it's not exposed as a function (so user code cannot call it)?
I define the handler as a Firebase Cloud function:
exports.deleteUser = functions.auth.user().onDelete((user, context) => { ... }
It seems like this function is executable in client code by calling:
var f = config.getFunctions().httpsCallable('deleteUser')
const rc = await f({user: 'abc'})
I want that code to fail as if the function does not exist, but it doesn't. Running "firebase serve" I see the function being executed when I call it directly using the httpsCallable approach.
I want to ensure it's automatically called when a user is deleted but never otherwise.

Related

How to get the current https functions endpoint for firebase when using/not using emulator

I have a graphql api running on a firebase function and I develop locally for the most part.
Before when I called the firebase functions directly I would use the connectFunctionsEmulator function to conditionally connect to my local emulator and when I called the functions using getFunctions it would know to call my localhost endpoint.
However I'm trying to use Apollo client and I'm not sure how to determine when the local emulators have been connected and when they have not as well as how to determine the absolute url when calling the function like the way firebase does when I call it using the firebase functions helpers.
I went snooping through the code and when I call the connectFunctionsEmulator function it updates a variable called functionsInstance.emulatorOrigin.
This is not present in the functions typing but I'm still able to access the value by doing getFunctions().emulatorOrigin.
If the value is not set to null that means firebase will be talking to my emulators and not to the live endpoint.
function getGraphQLUri(): string {
const functions = getFunctions();
const app = getApp();
const { projectId } = app.options;
const { region } = functions;
// emulatorOrigin is not defined in the typings but it exists
// #ts-ignore
const emulator = functions.emulatorOrigin;
let url: string = "";
if (emulator) {
url = `${emulator}/${projectId}/${region}/graphql`;
} else {
url = `https://${region}-${projectId}.cloudfunctions.net/graphql`;
}
return url;
}
I would appreciate if someone at Google can confirm whether this approach might continue to be supported in the future.

How to parse and validate Cloud Task token in Firebase HTTP function

I have a node.js application that creates Cloud HTTP tasks with authentication. I'd like to handle these tasks viaFirebase HTTP function (also in JS). I understand that I need to use oidcToken when creating a task, but I don't understand how to validate such a token on the Firebase HTTP function end. Docs are not very helpful. I was expecting to find some utility in #google-cloud/tasks or in googleapis/google-auth-library-nodejs, but nothing jump out at me.
All you have to do is assosiacte your Cloud Function with a service account. This is called Function Identity. Please note that whenever you deploy a Cloud Function either in GCP or Firebase, the same function appears in both platforms.
You can create a new service account for this purpose with:
gcloud iam service-accounts create [YOUR_NEW_SERVICE_ACCOUNT_NAME] \
--display-name "Service Account Test"
And assign the required IAM role with:
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member serviceAccount:[YOUR_NEW_SERVICE_ACCOUNT_NAME]#${PROJECT_ID}.iam.gserviceaccount.com \
--role roles/cloudfunctions.invoker
Your function deployment should look like something like this:
gcloud functions deploy hello_world \
--trigger-http \
--region us-central1 \
--runtime nodejs14 \
--service-account [YOUR_NEW_SERVICE_ACCOUNT_NAME]#${PROJECT_ID}.iam.gserviceaccount.com \
--no-allow-unauthenticated
Once you have all that, your Cloud HTTP task should use this service account as the OICD token, but I believe you already know how to do so.
You can find more information in this guide (although it uses the Cloud Scheduler instead of Cloud Tasks, the idea is pretty much the same).
You don't need to validate the token in the function, when you create the function so only authenticated users can call it this verification will automatically be done on Google's side.
So, to make what you want you need to:
1. Create the Task Queue
Detailed steps in this how-to.
2. Create the service account
You will need to create a SA with these IAM permissions:
Cloud Functions Invoker
Cloud Tasks Enqueuer
Service Account User
"Cloud Functions Invoker" is necessary to be able to call the function and "Cloud Tasks Enqueuer" to add tasks to the queue. If you want to use a different SA for each of those steps you can separate these permissions.
3. Create the Firebase Functions function
When creating the function, make sure that it required authentication.
4. Create the task using the SA
There's a section for this in the documentation, which you can find here. The code below is copied below:
// Imports the Google Cloud Tasks library.
const {CloudTasksClient} = require('#google-cloud/tasks');
// Instantiates a client.
const client = new CloudTasksClient();
async function createHttpTaskWithToken() {
const project = '[PROJECT_NAME]';
const queue = '[QUEUE_NAME]';
const location = '[LOCATION]';
const url = '[FUNCTION_TREIGGER_URL]';
const serviceAccountEmail = '[SA]';
const payload = 'Hello, World!';
// Construct the fully qualified queue name.
const parent = client.queuePath(project, location, queue);
const task = {
httpRequest: {
httpMethod: 'POST',
url,
oidcToken: {
serviceAccountEmail,
},
},
};
if (payload) {
task.httpRequest.body = Buffer.from(payload).toString('base64');
}
console.log('Sending task:');
console.log(task);
// Send create task request.
const request = {parent: parent, task: task};
const [response] = await client.createTask(request);
const name = response.name;
console.log(`Created task ${name}`);
}
createHttpTaskWithToken();

Is it possible to stub meteor methods and publications in cypress tests?

Is it possible to stub meteor methods and publications in cypress tests?
In the docs (https://docs.cypress.io/guides/getting-started/testing-your-app#Stubbing-the-server) it says:
This means that instead of resetting the database, or seeding it with
the state we want, you can force the server to respond with whatever
you want it to. (...) and even test all of the edge cases, without needing a server.
But I do not find more details about that. All I can find is, that when not using the virtual user in the tests to fill the database, it is possible to call API calls on the app, like so:
cy.request('POST', '/test/seed/user', { username: 'jane.lane' })
.its('body')
.as('currentUser')
In my opinion that is not a "stub". It is a method to "seed" the database.
Is it possible to tell cypress to answer a meteor method in the client code like
Meteor.call('getUser', { username: 'jane.lane' }, callbackFunction);
with some data, it would give back in production?
I can only show an example using sinon to stub Meteor method calls:
const stub = sinon.stub(Meteor, 'call')
stub.callsFake(function (name, obj, callback) {
if (name === 'getUser' && obj.username === 'jane.lane') {
setTimeout(function () {
callback(/* your fake data here */)
})
}
})
That would be of corse a client-side stub. You could also simply override your Meteor method for this one test.

How to test an onCall function as an authenticated user in the functions:shell

I want to test an onCall function via firebase functions:shell as an authenticated usr
I’ve tried many of the combinations of calls from https://firebase.google.com/docs/functions/local-emulator#invoke_https_functions
As well as downloading and setting the GOOGLE_APPLICATION_CREDENTIALS env var
My function looks like this:
exports.sendMessage = functions.https.onCall((data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}
// Other logic
}
It works fine once deployed and hit from my ios app, but i can’t get it running in the shell.
The following command will actually hit the function:
sendMessage.post({headers: {'content-type' : 'application/json'},body: JSON.stringify({data: {'messageId':'test'} }) })
and returns
RESPONSE RECEIVED FROM FUNCTION: 400, {“error”:{“status”:“FAILED_PRECONDITION”,“message”:“The function must be called while authenticated.“}}
Which is correct but I want an authenticated user now. When I try to add auth like the docs recommend:
sendMessage('data', {auth:{uid:'USERUID'}}).post({headers: {'content-type' : 'application/json'},body: JSON.stringify({data: {'messageId':'test'} }) })
I end up getting ERROR SENDING REQUEST: Error: no auth mechanism defined
If I try following the Authorization headers on this page https://firebase.google.com/docs/functions/callable-reference like so:
sendMessage.post({headers: {'Authorization': 'Bearer USERUID', 'content-type' : 'application/json'},body: JSON.stringify({data: {'messageId':'test'} }) })
I get back:
RESPONSE RECEIVED FROM FUNCTION: 401, {"error":{"status":"UNAUTHENTICATED","message":"Unauthenticated"}}
How do you send the request as an authenticated user?
Related Links
How to test `functions.https.onCall` firebase cloud functions locally?
According to the documentation, the Authorization header requires an authentication id token, not a user id. You'll have to generate a token somehow and pass that to the function. The callable function will then validate that token and provide the user id to the function via context.auth.
It looks like you can use the Firebase Auth REST API to get one of these tokens. Or you can generate one in a client app that uses Firebase Auth client libraries, log it and copy its value, and use it until it expires (1 hour).

firebase serve: From a locally served app, call locally served functions

How can I properly simulate a cloud function locally so that it has all data as when being invoked on firebase servers? (e.g. the context.auth)
I am serving my project with firebase serve, it runs ok on http://localhost:5000/, however, my cloud functions are being called from https://us-central1-<my-app>.cloudfunctions.net/getUser. (The function is not even deployed.)
To avoid XY problem, I am trying to debug my function, but calling it from firebase shell results in context.auth being undefined, same when calling via postman from http://localhost:5000/<my-app>/us-central1/getUser.
This is my ./functions/src/index.ts file
import * as functions from 'firebase-functions'
import admin from 'firebase-admin'
import { inspect } from 'util'
admin.initializeApp()
export const getUser = functions.https.onCall((data, context) => {
console.debug('== getUser called =========================================')
console.log('getUser', inspect(data), inspect(context.auth))
return admin.database().ref('userRights/admin').child(context.auth.uid).once('value', snapshot => {
console.log(snapshot.val())
if (snapshot.val() === true) {
return 'OK'
// return {status: 'OK'}
} else {
return 'NOK'
// return {status: 'error', code: 401, message: 'Unauthorized'}
}
})
})
file ./firebase.functions.ts
import { functions } from '~/firebase'
export const getUser = functions.httpsCallable('getUser')
Consumer ./src/pages/AdminPanel/index.tsx
import { getUser } from '~/firebase.functions'
//...
getUser({myDataX: 'asd'}).then(response => console.debug('response', response))
UPDATE - April/2021
As of April/2021, method useFunctionsEmulator has been deprecated. It is suggested to use method useEmulator(host, port) instead.
Original post:
By default, firebase serve sends queries to CLOUD function instead of localhost, but it is possible to change it to to point to localhost.
#gregbkr found a workaround for that at this github thread.
You basically add this after firebase initialization script (firebase/init.js) in html head.
<script>
firebase.functions().useFunctionsEmulator("http://localhost:5001");
</script>
Make sure to REMOVE it when deploying to SERVER
There is currently no support for local testing of callable functions like this. The team is working on a way for you to specify the URL endpoint of a callable function so that you can redirect it to a different location for testing.
Just found a workaround.
using fiddlers AutoResponder to redirect the function call to the local served function.
step 1
copy the target url of the function from the client
step 2
copy the local served function url
step 3
active the auto respoder and use the following rules
(the second rule is also importent to allow all outher requests
That worked for me, thank you #GorvGoyl!
script src="/__/firebase/init.js?useEmulator=true"></script

Resources