Firebase Callable Cloud Function returning undefined - javascript

I'm creating a callable cloud function which creates a users profile in the cloud firestore db and also add custom claims...
I call it in the front-end as follows
let setUpUser = functions.httpsCallable('setUpUser');
...other code
.then(async ()=>{
try {
const user = await setUpUser({
displayName: this.state.name,
email: user.user.email,
type: this.state.type,
organization: this.state.organization
});
console.log(user);
}
catch (e) {
console.error(e);
}
})
and the in the cloud function I try call the cloud function and set it up using the code below
exports.handler = ((data,context,admin,db )=>{
let uid = context.auth.uid;
return db.doc('users/'+uid).set({
displayName:data.displayName,
email: data.email,
type:data.type,
organization:data.organization
})
.then(()=>{
if (data.type === "teacher"){
return admin.auth().setCustomUserClaims(uid,{
isTeacher:true,
})
}
return;
})
.then(() => {
return admin.auth().getUser(uid).then((userRecord)=>{
return userRecord
})
})
.catch(error =>{
throw new functions.https.HttpsError(error);
});
})
But the log in the front-end is Cannot read property 'user' of undefined

Related

Firebase Cloud Function Unauthenticated Error After Password Sign-Up

I am receiving the following error when triggering this cloud function: "Error unauthenticated".
I do not wish to allow unauthenticated calls to this cloud function.
The workflow is as follows:
User registers in app via firebase password authentication
Firebase Auth Credentials are created (firebase signs in user upon success)
Once the credentials have been created, the cloud function is triggered in the firebase auth callback.
At this point, the call should be authenticated, given it's being triggered in the firebase auth response.
However, it keeps erroring with
Error: unauthenticated
The user is authenticated at this point.
Any suggestions?
CLIENT CODE ->
const onRegisterPress = () => {
if (password !== confirmPassword) {
alert("Passwords don't match.")
return
}
setLoading(true);
//CREATE'S USER'S AUTH CREDENTIALS
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((response) => {
const data = {
...
}
//console.log(response);
return new Promise(async function(resolve, reject) {
await firebase.functions().httpsCallable('writeAccountUser')({
data
}).then((response) => {
console.log("Write Account User Response: ", response);
resolve(setLoading(false));
}).catch((error) => {
console.error("Cloud Function Error: ", error);
setLoading(false);
reject(error)
})
});
})
.catch((error) => {
alert(error)
});
}
CLOUD FUNCTION ->
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const firestore = admin.firestore();
exports.writeAccountUser = functions.https.onCall((data, context) => {
console.log("Incoming Data: ", data);
console.log("Incoming Context: ", context);
const clientData = data.data;
console.log("Client Data: ", clientData);
console.log("Account ID: ", clientData.accountID);
return new Promise(async function (resolve, reject) {
const accountsRef = firestore.collection('Accounts');
const usersRef = firestore.collection('users');
const now = new Date();
if (clientData.accountExists === true) {
console.log("Account Exists");
await accountsRef
.doc(clientData.accountID)
.update({
users: admin.firestore.FieldValue.arrayUnion(clientData.uid)
}).catch((error) => { console.error(error); reject(false) });
}
else {
console.log("Account Does Not Exist!");
const account_data = clientData.accountData;
const product_lines = clientData.productLines;
await accountsRef
.doc(clientData.accountID)
.set({
account_data,
product_lines,
users: [clientData.uid],
dateCreated: {
date: now,
timestamp: now.getTime()
}
}).catch((error) => { console.error(error); reject(false)});
};
const email = clientData.email;
const fullName = clientData.fullName;
const acceptTerms = clientData.acceptTerms;
const userData = {
id: clientData.uid,
email,
fullName,
accountID: clientData.accountID,
dateCreated: {
date: now,
timestamp: now.getTime()
},
lastUpdateFetch: {
date: now,
timestamp: now.getTime()
},
termsConditionsAccepted: acceptTerms
};
await usersRef
.doc(clientData.uid)
.set(userData)
.catch((error) => { console.error(error); reject(false) });
resolve(true);
});
});
Error ->
[Unhandled promise rejection: Error: unauthenticated]
at node_modules/#firebase/firestore/dist/rn/prebuilt.rn-f9cd27ba.js:12199:33 in
at http:///node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&minify=false:169743:29 in _errorForResponse
at node_modules/#firebase/firestore/dist/rn/prebuilt.rn-f9cd27ba.js:12747:31 in yu
at node_modules/tslib/tslib.js:77:12 in
at http://REDACTED/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&minify=false:120748:21 in
at http://REDACTED/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&minify=false:120702:31 in fulfilled
You can try refactoring the function as shown below:
const onRegisterPress = async () => {
if (password !== confirmPassword) {
alert("Passwords don't match.")
return
}
setLoading(true);
const response = await firebase.auth().createUserWithEmailAndPassword(email, password)
const data = {...}
const fnResponse = await firebase.functions().httpsCallable('writeAccountUser')({data})
console.log("Write Account User Response: ", response);
}
You can also create the account using the Admin SDK in the same function and log the user on your web app after the response. That'll ensure the Cloud function's action has been executed as well (just in case the function is not called after user sign up for any reason).

firebase - Use updateProfile whenever a user signup

I have a problem with firebase, I want when a user creates a user for the first time, add him to updateProfile, personal details.
This is the code I'm trying to do but the code is not running, it does not work for me.
The part with the currentUser does not work, I do not understand why, I also do not get an error.
signupUser = async () => {
const newUser = {
email: 'test#mail.com',
password: '123456'
};
await signup(newUser);
}
call to signup in nodejs
export const signup = (newUser) => (dispatch) => {
axios
.post('/signup', newUser)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
signup - nodejs
//basically call to this function to signup
exports.signup = (req, res) => {
const newUser = {
email: req.body.email,
password: req.body.password
};
firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password)
.then((data) => {
const currentUser = firebase.auth().currentUser;
const name = `${"adding some private information"}`;
currentUser.updateProfile({
displayName: name,
})
.then(() => {
console.log("sign in successfully")
});
return data.user.getIdToken();
})
.then((token) => {
return db.doc(`/users/${newUser.handle}`).set("test");
})
.then(() => {
return res.status(201).json({ token });
})
.catch((err) => {
console.error(err);
});
};
The issue looks to be that you aren't return the promise from currentUser.updateProfile, ensuring it successfully completes. Try the following by returning the Promise from that method:
exports.signup = (req, res) => {
const newUser = {
email: req.body.email,
password: req.body.password,
};
firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password)
.then((data) => {
const currentUser = firebase.auth().currentUser;
const name = `${"adding some private information"}`;
return currentUser
.updateProfile({
displayName: name,
})
.then(() => {
console.log("sign in successfully");
return data.user.getIdToken();
});
})
.then((token) => {
return db.doc(`/users/${newUser.handle}`).set("test");
})
.then(() => {
return res.status(201).json({ token });
})
.catch((err) => {
// probably send an error back?
// return res.status(500).json({ message: 'error' });
console.error(err);
});
};

React Native - How to deal with asynchronism with AsyncStorage

I am facing to asynchronism problem :
I create a user in firebase, generating a unique ID for it.
I get this unique ID.
I call an async function to persist this ID with AsyncStorage method.
Problem : The asyncStorage method is called before I get back the generated ID from my user creation. How to deal with this ?
This is my code :
class Subscription extends Component {
constructor() {
super();
this.state = {
email: '',
password: ''
}
}
persistUserId = (userID) => {
try {
AsyncStorage.setItem('userId', userID); // Here, user ID is undefined
} catch (error) {
console.log(error.message);
}
};
updateInputValue = (value, prop) => {
const state = this.state;
state[prop] = value;
this.setState(state);
}
registerUser = () => {
var generatedUserId = '';
firebase
.auth()
.createUserWithEmailAndPassword(this.state.email, this.state.password) // Authentication
.then((res) => {
var user = { // Set Javascript Object to insert
email: this.state.email
}
database.collection("users").add({ // Create the new user generating an ID
'email': user.email,
}).then(function(docRef) {
generatedUserId = docRef.id; // Get the generated ID (The one to persist witch asyncstorage)
}).then(function() {
this.persistUserId(generatedUserId) // Call the AsyncStorage to persist the ID
})
this.props.navigation.navigate('AppPage') // Go to next page.
})
.catch(error => {
alert(error.message)
})
}
For persisting data. According to react-native doc. You need to use async await keyword:
_storeData = async () => {
try {
await AsyncStorage.setItem(
'#MySuperStore:key',
'I like to save it.'
);
} catch (error) {
// Error saving data
}
}
for your case:
persistUserId = async (userID) => {
try {
await AsyncStorage.setItem('userId', userID); // Here, user ID is undefined
} catch (error) {
console.log(error.message);
}
};
Note: Persisting data is async process. That's why you need to use async await
You need to update your firebase then catch as well. Either use bind or use arrow function. Here is updated version:
firebase
.auth()
.createUserWithEmailAndPassword(this.state.email, this.state.password) // Authentication
.then((res) => {
var user = {
// Set Javascript Object to insert
email: this.state.email,
};
database
.collection("users")
.add({
// Create the new user generating an ID
email: user.email,
})
.then( (docRef) => {
generatedUserId = docRef.id; // Get the generated ID (The one to persist witch asyncstorage)
})
.then( () => {
this.persistUserId(generatedUserId); // Call the AsyncStorage to persist the ID
});
this.props.navigation.navigate("AppPage"); // Go to next page.
})
.catch((error) => {
alert(error.message);
});

How to debug a mongoose action within node.js

I am using mongoose to connect mongoDB and my node.js app. However, when I create or update a model instance, it won't change the Database, how can I go inside to debug what happens in the create or update action? I do check the MongoDB interface, delete and find and list action works just fine:
Here are those two docs that I have:
// index.js
const mongoose = require('mongoose')
const User = require('../model/user')
mongoose.Promise = global.Promise;
// connect to DB
const db = mongoose.connect('mongodb://localhost:27017/myImportantDates', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// create a user
const addUser = (user) =>{
let newUser = new User(user)
if (newUser.save()){
console.log(newUser) // it will console.log the newly created user, but it is not in the database
mongoose.disconnect()
}else{
console.log(newUser.errors)
}
}
// list all users
const listAllUsers = () =>{
User.find().then((users)=>{
console.log("Totally there are " + users.length + " users.");
console.log(users);
mongoose.disconnect();
}).then(()=>process.exit())
}
// find one user
const findUserByEmail = (email) => {
User.find({email},(err,docs)=>{
if(err){
console.log(err)
}else{
console.log(`Already found ${docs.length} matches.` )
console.log(docs)
}
mongoose.disconnect()
})
}
// update a user and make sure pass {new:true} option so that the doc in callback return the doc after updated
const updateUser = (email,user) => {
User.findOneAndUpdate( { email }, user, { new: true }, (err,doc) =>{
if(err){
console.log(err);
return
}else{
console.log(doc)
}
mongoose.disconnect()
})
}
// remove a user
const deleteUser = email => {
User.deleteOne( { email },(err,res) =>{
if(err){
console.log(err);
return
}
console.log("Deleted Successfully.");
mongoose.disconnect()
})
}
module.exports = {
addUser,
listAllUsers,
findUserByEmail,
updateUser,
deleteUser
}
//user_methods.js
const { program } = require('commander');
const {addUser,listAllUsers,findUserByEmail,updateUser,deleteUser} = require('./model_methods/user_methods')
const inquirer = require('inquirer')
const questions = [
{
type: 'input',
name: 'name',
message: 'user name'
},
{
type: 'input',
name: 'email',
message: 'user email'
},
{
type: 'input',
name: 'password',
message: 'user password'
},
];
program
.version('0.0.1')
.description("testing");
program
.command('list')
.alias('l')
.description('List all users')
.action(()=>listAllUsers())
program
.command('add')
.alias('a')
.description('Add a user')
.action(()=>{
inquirer.prompt(questions)
.then( answers => {
addUser(answers)
}).then(() => {
process.exit()
})
.catch(err =>{
console.log(error)
})
})
program
.command('find <email>')
.alias('f')
.description('find a user through email')
.action((email)=>{
findUserByEmail(email)
})
program
.command('update <email>')
.alias('u')
.description('update a user through email')
.action((email)=>{
inquirer.prompt(questions)
.then( ( email,answers ) => {
updateUser(email, answers)
}).then(() => {
process.exit()
})
.catch(err =>{
console.log(error)
})
})
program
.command('delete <email>')
.alias('d')
.description('delete a user through email')
.action((email)=>{
deleteUser(email)
})
program.parse(process.argv)
I will run node index.js <command> to reach those methods.
Currently, the process is getting exited before the save happens. According to your current code, it looks like you don't need to call the process.exit() explicitly. The application will exist on its own when the addUser operation is completed.
Also, you need to update the addUser method. You should only close the connection after successfully saving the record
// create a user
// create a user
const addUser = (user) =>{
let newUser = new User(user)
newUser.save((err, result) => {
console.log("inside save method")
if (err) console.log(err);
else {
console.log(result);
mongoose.disconnect();
}
})
}

How To Fix "Unknown error status: Error: The uid must be a non-empty string with at most 128 characters." in Firebase Functions

I'm trying to create a user in my firebase app by passing the data from the UI to a callable function where I
create a user account using email password then
add a display name then create a profile in a user collection then
send a user confirmation email but I get the error
Unknown error status: Error: The uid must be a non-empty string with at most 128 characters.
at new HttpsError (/srv/node_modules/firebase-functions/lib/providers/https.js:102:19)
at admin.auth.createUser.then.then.then.catch.error (/srv/index.js:41:12)
at <anonymous>
const db = admin.firestore();
exports.createUser = functions.https.onCall((data,context)=>{
return admin.auth().createUser({
email: data.email,
password: data.password,
displayName: data.displayName,
}).then(user =>{
return db.doc('users/'+user.uid).set({
email: data.email,
displayName:data.displayName,
type:data.type,
organization:data.organization
});
})
.then(user=>{
let uid = user.uid;
if (data.type === "admin"){
return admin.auth().setCustomUserClaims(uid,{
isAdmin: true,
})
}else{
return admin.auth().setCustomUserClaims(uid,{
isAdmin: false,
})
}
})
.then(user =>{
return user.sendEmailVerification();
})
.catch(error =>{
new functions.https.HttpsError(error);
});
})
and this is my code on my React JS front end
let createUser = functions.httpsCallable('createUser')
createUser({
email: this.state.email,
password: this.state.password,
displayName:this.state.name,
type:this.state.type,
organization:this.state.organization
})
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
When you do
return db.doc('users/'+user.uid).set({
email: ....});
})
.then(user => { // here, user is undefined})
the value of user (i.e. the fulfillment value, or, in other words, the argument you pass to the first callback function you pass to the then method) is undefined since the set() method returns a "non-null Promise containing void".
You need to save the value of uid in a variable in the previous then(), as shown in the code below.
Note also that, by doing,
.then(user =>{
return user.sendEmailVerification();
})
firstly you get the same problem than above (the value of user is undefined), but, in addition, in the Admin SDK there isn't a sendEmailVerification() method, which is a method of the client JavaScript SDK.
You can use the generateEmailVerificationLink() method of the Admin SDK and send the link by email (from the Cloud Function) to the user, through Sendgrid for example.
const db = admin.firestore();
exports.createUser = functions.https.onCall((data,context)=>{
let userUid;
return admin.auth().createUser({
email: data.email,
password: data.password,
displayName: data.displayName,
}).then(user =>{
userUid = user.uid;
return db.doc('users/'+userUid).set({
email: data.email,
displayName:data.displayName,
type:data.type,
organization:data.organization
});
})
.then(()=>{
if (data.type === "admin"){
return admin.auth().setCustomUserClaims(userUid,{
isAdmin: true,
})
}else{
return admin.auth().setCustomUserClaims(userUid,{
isAdmin: false,
})
}
})
.then(() =>{
//You may use the generateEmailVerificationLink() method, see
//https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth#generateEmailVerificationLink
const actionCodeSettings = ....
return admin.auth()
.generateEmailVerificationLink(data.email, actionCodeSettings)
})
.then(link => {
//The link was successfully generated.
//Send an email to the user through an email service
//See https://github.com/firebase/functions-samples/tree/master/email-confirmation
//or https://stackoverflow.com/questions/50205390/send-transactional-email-with-sendgrid-in-cloud-functions-firebase/50248871
})
.catch(error =>{
throw new functions.https.HttpsError('unknown', error.message);
});
})

Categories

Resources