I have written a firebase Http callable cloud function based on the tutorial here: https://www.youtube.com/watch?v=3hj_r_N0qMs from the firebase team. However, my function is unable to verify the custom claims on a user (me) as 'context.auth' is undefined
I've updated firebase, firebase tools, firebase-functions and admin SDK to the latest versions.
My functions/Index.ts file
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp()
export const addAdmin = functions.https.onCall((data, context) => {
if (context.auth.token.admin !== true) {
return {
error: 'Request not authorized'
};
}
const uid = data.uid
return grantAdminRole(uid).then(() => {
return {
result: `Request fulfilled!`
}
})
})
async function grantAdminRole(uid: string): Promise<void> {
const user = await admin.auth().getUser(uid);
if (user.customClaims && (user.customClaims as any).admin === true) {
console.log('already admin')
return;
}
return admin.auth().setCustomUserClaims(user.uid, {
admin: true,
}).then(() => {
console.log('made admin');
})
}
My app.component.ts code
makeAdmin() {
var addAdmin = firebase.functions().httpsCallable('addAdmin');
addAdmin({ uid: '[MY-USER-ID]' }).then(res => {
console.log(res);
})
.catch(error => {
console.log(error)
})
}
The function executes well if I don't try to access 'context' and I can add a custom claim to this user. However if I try to access context.auth I find the error:
Unhandled error TypeError: Cannot read property 'token' of undefined"
The error message is telling you that context.auth doesn't have a value. As you can see from the API documentation, auth will be null if there is no authenticated user making the request. This suggests to me that your client app does not have a signed-in user at the time of the request to the callable function, so make sure that is the case before invoking the function. If you allow the case where a callable function can be invoked without a signed in user, you will need to check for that case in your function code by checking context.auth before doing work on behalf of that user.
Turns out I wasn't properly integrating AngularFire Functions. I found the solution to my problem here: https://github.com/angular/angularfire2/blob/master/docs/functions/functions.md
I changed my client component code to the following:
import { AngularFireFunctions } from '#angular/fire/functions';
//other component code
makeAdmin() {
const callable = this.fns.httpsCallable('addAdmin');
this.data$ = callable({ uid: '[USERID]' })
.subscribe(resp => {
console.log({ resp });
}, err => {
console.error({ err });
});
}
Related
I want to call a payable function in a smart contract I deployed, but it does not work. This is the error I am getting:
Error: Returned error: The method eth_sendTransaction does not exist/is not available
The answer I could find is to just use a private key, because infura does not cater this method, however I want the user to sign the transaction to the smart contract with MetaMask.
This is my code:
export async function helloworld() {
const rpcURL =
"https://ropsten.infura.io/v3/KEY";
const web3 = new Web3(rpcURL);
let provider = window.ethereum;
if (typeof provider !== "undefined") {
provider
.request({ method: "eth_requestAccounts" })
.then((accounts) => {
selectedAccount = accounts[0];
console.log(`Selected account is ${selectedAccount}`);
})
.catch((err) => {
console.log(err);
return;
});
window.ethereum.on("accountsChanged", function (accounts) {
selectedAccount = accounts[0];
console.log(`Selected account changed to ${selectedAccount}`);
});
}
const networkId = await web3.eth.net.getId();
const thecontract = new web3.eth.Contract(
simpleContractAbi,
"0x50A404efF9A057900f87ad0E0dEfA0D485931464"
);
isInitialized = true;
investit(thecontract, selectedAccount);
}
and this is the code that actually throws the error:
export const investit = async (thecontract, selectedAccount) => {
if (!isInitialized) {
await helloworld();
}
thecontract.methods
.invest()
.send({ from: selectedAccount, value: 10000 })
.catch(function (err) {
console.log(err);
});
};
I am completely lost, since if I use the normal window.ethereum.request (https://docs.metamask.io/guide/sending-transactions.html#example) to send a transaction, metamask opens up and I can sign it. With the contract call it simply does not work.
Do you know the reason? How can I fix this?
This must be the issue. you are just passing an url:
const rpcURL ="https://ropsten.infura.io/v3/KEY";
instead:
const web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io/v3/KEY"))
eth_sendTransaction requires you holding the private key to sign the transaction before broadcasting it to the network. Infura doesn’t maintain any private keys. In order to send a transaction, you need to sign the transaction on your end with your private key.
The way you check providers is not correct. window.ethereum is also a provider which is provided by metamask. provider itself is meaningless, it has to be injected into the new Web3().
I'm trying to write a function that deletes a user from the firebase authuntication.
Here is the function:
//Delete user from authuntication and from database
export const deleteUser =
//firest make sure user is connected and active
functions.https.onCall(async (data, context) =>{
const userId: string = data.userId;
UserHelpers.validateConnectedAndActive(context);
if (!userId) {
throw new functions.https.HttpsError(
'invalid-argument',
'Invalid parameters'
);
}
//delete from firebase authuntication
admin.auth().deleteUser(userId);
})
And here is the call to the function from an angular service:
public deleteUser(userId: string): Observable<any> {
return this.fns.httpsCallable('deleteUser')({userId});
}
When I run the emulator and inspect the functions logs, i can see my function is initialized, but when I call it - there is no log indicating the function was triggered.
do you want to try exports.deleteUser = () => { // your code } instead of what you are doing.
I am attempting to send a test webhook as instructed in this tutorial.
But when I go to do it I get the error seen in the first link, and below:
Test webhook error: 400
Here is my index.ts code & functions I have deployed to firebase functions.
import * as functions from 'firebase-functions';
// const functions = require('firebase-functions');
const stripe = require('stripe')(functions.config().keys.webhooks);
const admin = require('firebase-admin');
admin.initializeApp();
const endpointSecret = functions.config().keys.signing;
exports.events = functions.https.onRequest((request, response) => {
let sig = request.headers["stripe-signature"];
try {
let event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret); // Validate the request
return admin.database().ref('/events').push(event) // Add the event to the database
.then((snapshot: { ref: { toString: () => any; }; }) => {
// Return a successful response to acknowledge the event was processed successfully
return response.json({ received: true, ref: snapshot.ref.toString() });
})
.catch((err: any) => {
console.error(err) // Catch any errors saving to the database
return response.status(500).end();
});
}
catch (err) {
return response.status(400).end(); // Signing signature failure, return an error 400
}
});
exports.exampleDatabaseTrigger = functions.database.ref('/events/{eventId}').onCreate((snapshot, context) => {
return console.log({
eventId: context.params.eventId,
data: snapshot.val()
});
});
How do I fix this and successfully run the test?
My current thinking is that the problem may have something to do with:
How I wrote this line: snapshot: { ref: { toString: () => any; };
Update:
From my testing, this does not appear to be the case.
I don't believe that the 'test webhook' properly signs them; you should use Stripe CLI for this instead.
I've deployed this code to my firebase functions project:
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
admin.initializeApp()
export const getEmail = functions.https.onRequest((request, response) => {
var from = request.body.sender;
admin.auth().getUserByEmail(from)
.then(snapshot => {
const data = snapshot.toJSON()
response.send(data)
})
.catch(error => {
//Handle error
console.log(error)
response.status(500).send(error)
})
})
Which takes in a email parameter that it gets from the user's input on my app. My app's code looks like this:
Functions.functions().httpsCallable("https://us-central1-projectname.cloudfunctions.net/getEmail").call(email) { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
//email isnt taken
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
print(code, message, details)
}
// ...
}
if let text = (result?.data as? [String: Any])?["text"] as? String {
// email taken
}
}
When I run the app and when that function is called, it seems to do nothing, no error message is shown and no data has been sent back. What am I missing?
Update: I went to the logs and nothing has happened in there as if the function was never called.
You are actually mixing up HTTP Cloud Functions and Callable Cloud Functions:
You Cloud Function code corresponds to an HTTP one but the code in your front-end seems to call a Callable one.
You should adapt one or the other, most probably adapt your Cloud Function to a Callable one, along the following lines:
exports.getEmail = functions.https.onCall((data, context) => {
const from = data.sender;
return admin.auth().getUserByEmail(from)
.then(userRecord => {
const userData = userRecord.toJSON();
return { userData: userData };
})
});
Have a look at the doc for more details, in particular how to handle errors. The doc is quite detailed and very clear.
I have been trying to implement Cloud Messaging for my app so that every user of the app would receive notification automatically when a new child is added to the Realtime Database.
In my MainActivity I am subscribing each user to a topic with this method.
FirebaseMessaging.getInstance().subscribeToTopic("latest_events").addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Toast.makeText(MainActivity.this, "Successfully subscribed", Toast.LENGTH_SHORT).show();
}
});
I have also installed firebase functions for my backend and deployed my javascript code.
index.js
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref("/Users").onWrite(event => {
var payload = {
notification: {
title: "A new user has been added!",
body: "Click to see"
}
};
if (event.data.previous.exists()) {
if (event.data.previous.numChildren() < event.data.numChildren()) {
return admin.messaging().sendToTopic("latest_events", payload);
} else {
return;
}
}
if (!event.data.exists()) {
return;
}
return admin.messaging().sendToTopic("latest_events", payload);
});
I have not been getting the desired notification when a user gets added. Can't seem to get what am doing wrong.
Firebase Functions Log Cat
sendNotification
TypeError: Cannot read property 'previous' of undefined at exports.sendNotification.functions.database.ref.onWrite.event (/user_code/index.js:15:19) 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:730:24 at process._tickDomainCallback (internal/process/next_tick.js:135:7)
You're using syntax from the beta version of the Cloud Functions for Firebase. Since it was updated to 1.0 the syntax changed, and you will need to update your code to match as described in the upgrade documentation.
Applying that to your code leads to something like this:
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification = functions.database.ref("/Users").onWrite((change, context) => {
var payload = {
notification: {
title: "A new user has been added!",
body: "Click to see"
}
};
if (change.before.exists()) {
if (change.before.numChildren() < change.after.numChildren()) {
return admin.messaging().sendToTopic("latest_events", payload);
} else {
return;
}
}
if (!change.after.exists()) {
return;
}
return admin.messaging().sendToTopic("latest_events", payload);
});
The changes are that:
There are two parameters passed into the function, where previously there was only one.
You no longer need to pass configuration into initializeApp.
The before and after snapshots are now available from change.
Also see these questions (which are easy to find by searching for the error message:
Firebase functions: cannot read property 'user_id' of undefined
Firebase TypeError: Cannot read property 'val' of undefined
Firebase Notifications using node.js