I have a Google Cloud Function which I am calling from my RN app but it is returning
[Error: Internal]
I have set the permission to Unauthenticated users so anyone can call it - for testing purposes only. When I set to Authenticated users permission, it throws another error [Error: Unauthenticated] eventhough I am authenticated and I can get the currentUser id in my app.
Tried searching for this error but it didnt send me to any possible solutions so decided to post here and hopefully recieve responses that will help me fix it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
//Checking that the user calling the Cloud Function is authenticated
if (!context.auth) {
throw new UnauthenticatedError('The user is not authenticated. Only authenticated Admin users can create new users.');
}
const newUser = {
email: data.email,
emailVerified: false,
password: data.password,
disabled: false
}
const role = data.role;
const userRecord = await admin
.auth()
.createUser(newUser);
const userId = userRecord.uid;
const claims = {};
claims[role] = true;
await admin.auth().setCustomUserClaims(userId, claims);
return { result: 'The new user has been successfully created.' };
} catch (error) {
if (error.type === 'UnauthenticatedError') {
throw new functions.https.HttpsError('unauthenticated', error.message);
} else if (error.type === 'NotAnAdminError' || error.type === 'InvalidRoleError') {
throw new functions.https.HttpsError('failed-precondition', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
}
});
in my RN app I am calling it like this:
var user = {
role: role
}
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
const createUser = await functionsForRegion.httpsCallable('createUser');
createUser(user)
.then((resp) => {
//Display success
});
console.log(resp.data.result);
})
.catch((error) => {
console.log("Error on register patient: ", error)
});
I think the way I am calling it in my RN app is correct because I have tested it with a testFunction and I returned a simple string. So, I believe the problem is somewhere in the function itself.
EDIT: I just tested by simply calling the function and returning the context and it always returns Internal error:
exports.registerNewPatient = functions.region('europe-west3').https.onCall((data, context) => {
return context; //this is returned as INTERNAL error.
}
I just cant get to understand whats going on here, why does it return Internal error when I am authenticated as a user and it should return the authenticated user data, isn't that right?
Try some console.log(context) ; console.log(data) statements in your registerNewPatient function and take a look at the logs. What do they say?
Some other things to consider might include that in your client code you use europe-west1 while your function code has europe-west3. Try to have those line up and see if it works? From my experience, if a specified function isn't found to exist, the client receives an INTERNAL error.
Related
I have no idea why, but for some reason the jwt.verify function is complaining about an invalid signature in only part of my application. To give you some background, I am trying to set up auth headers using the context function with Apollo Server (which I guess is irrelevant anyway as the jwt.verify function should work in any javascript code snippet the same way) as follows:
context: ({ req }) => {
const token = req.get('Authorization') || '';
if (!token) {
console.log('no token detected. returning null...');
return { user: null };
}
return {
user: verifyUser(token.split(' ')[1]),
};
},
The verifyUser function:
const verifyUser = (token: string) => {
try {
return jwt.verify(token, process.env.JWT_SECRET as Secret);
} catch (error: any) {
console.error('error: ', error.message);
return null;
}
};
Note that I’m following this guide and have renamed getUser to verifyUser.
In each graphql request, I am providing a Bearer token, like so:
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2F3ZXNvbWVhcGkuY29tL2dyYXBocWwiOnsicm9sZXMiOlsiYWRtaW4iXSwicGVybWlzc2lvbnMiOlsicmVhZDphbnlfYWNjb3VudCIsInJlYWQ6b3duX2FjY291bnQiXX0sImlhdCI6MTU4NjkwMDI1MSwiZXhwIjoxNTg2OTg2NjUxLCJzdWIiOiIxMjM0NSJ9.31EOrcKYTsg4ro8511bV5nVEyztOBF_4Hqe0_P5lPps"
}
But every time I make a graphql request (a query or mutation) in the graphql playground, I am getting an invalid token message in the catch block, so presumably the jwt.verify function is failing. I wondered whether the JWT I provided to the Bearer code above is wrong, but I do not think it is. I am getting it from an authenticateUser resolver:
export const authenticateUser = async (
_: undefined,
{ input: { email, password } }: any
): Promise<any> => {
try {
const user = await User.findByEmail(email);
if (!user) {
throw new HttpError(404, 'User not found. Please create an account.');
}
const correctPassword = await bcrypt.compare(password, user.hashedPassword);
if (!correctPassword) {
throw new HttpError(403, 'Wrong password for this account.');
}
// assign object methods to the user instance as objects retrieved from db don't have methods
Object.setPrototypeOf(user, User.prototype);
const token = user.signToken(); // see below
return { token, id: user._id };
} catch (error: any) {
throw new HttpError(error.statusCode, error.message);
}
}
The user.signToken() function comes from a User class:
signToken(expiry: number | string = 60 * 24): string {
const secret = process.env.JWT_SECRET + this.hashedPassword;
// { ...this } overcomes error `Expected "payload" to be a plain object`
const token = jwt.sign({ ...this }, secret, {
expiresIn: expiry,
});
return token;
}
The only difference is that I am passing in a hashed password with the secret argument. I’ve also noticed that the error does not occur when using jwt.sign instead of jwt.verify (like in this post), but I assume I have to use the verify function.
Does anyone know why my code may not be working? The guide I am following does not pass a hashed password into the secret argument.
I have the following code in a google cloud function. When the user account is created it returns status 200 and the name of the registered user which I can access from the return of the successful promise. All works as expected. However, when there is an error creating the new user the status changes to 400 but no matter what on the client side I get an error of "Error: invalid-argument". I want to pass the error message from the google cloud function. When I check the cloud function logs, I can see the error code and error message.
What I tried: I did try to use the throw new functions.https.HttpsError() but I get a CORS error message.
Any advice on getting the cloud function to pass the error message properly?
const { initializeApp, applicationDefault, cert } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');
const { getAuth } = require('firebase-admin/auth')
const functions = require('firebase-functions')
const app = initializeApp();
const db = getFirestore();
exports.registerUser = (req, res) => {
let registerDetails = req.body.data;
res.set('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') {
// Send response to OPTIONS requests
res.set('Access-Control-Allow-Methods', 'GET');
res.set('Access-Control-Allow-Headers', 'Content-Type');
res.set('Access-Control-Max-Age', '3600');
res.status(204).send('');
} else {
console.log(registerDetails)
if(registerDetails.FirstName === undefined || registerDetails.FirstName === ""){
res.status(400).json({data: 'Invalid Request'})
} else {
console.log(registerDetails);
getAuth()
.createUser({
email: registerDetails.Email,
emailVerified: false,
password: registerDetails.Password,
displayName: registerDetails.DisplayName,
disabled: false,
})
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
let message = 'Registered user '+registerDetails.DisplayName+".";
res.status(200).json({data: message});
console.log('Successfully created new user:', userRecord.uid);
},(error)=>{
res.status(400).json({code:error.errorInfo.code,message:error.errorInfo.message})
console.log('Error creating new user:', error);
})
}
}
};
To anyone who views this post, the solution is that I was confusing HTTP and HTTPS Callable functions, which have different syntax.
See this post:
Google Cloud Function Cors Error Only When Error is Thrown
I've created a simple createUser function which is executed on call. I have one problem though. The function is crashing when the user is trying to register with an already existing email. I mean, it's ok, since no one wants to have 2 users with the same email address but I want to prevent crushing function, instead, I want to send an error message as a response.
export const createUserTest = functions.https.onCall((data, context) => {
const {email, password} = data;
return new Promise((resolve, reject)=>{
try{
admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
resolve({
result: 'success',
user: user,
}) ;
})
.catch((error) => {
reject(error) ;
});
}catch(error) {
reject (error)
}
})
});
I tried to put the function in to try/catch block but it didn't help. Do you have an idea of how I can achieve my goal?
As explained in the doc for Callable Cloud Functions, "to ensure the client gets useful error details, return errors from a callable by throwing (or returning a Promise rejected with) an instance of functions.https.HttpsError".
The error has a code attribute that can be one of the values listed here. In your case, the most appropriate seems to be already-exists.
On, the other hand, you'll find here the Admin SDK Authentication errors list and you'll see that in case the provided email is already in use by an existing user the error code is auth/email-already-exists.
So you can adapt your code as follows:
export const createUserTest = functions.https.onCall((data, context) => {
const { email, password } = data;
return admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
return {
result: 'success',
user: user,
}
})
.catch((error) => {
if (error.code === 'auth/email-already-exists') {
throw new functions.https.HttpsError('already-exists', 'The provided email is already in use by an existing user');
} else {
throw new functions.https.HttpsError('...other code....', '...');
// If an error other than HttpsError is thrown, your client instead receives an error with the message INTERNAL and the code internal.
}
});
});
See here in the doc, how to handle errors on the client side. If error.code == 'already-exists' you know that it's because the email is already in use.
I am trying to update the email of the current user, I went to documentation and I copied the example code that they are given there,
But nothing worked, this from doc didn't work
var user = firebase.auth().currentUser;
user.updateEmail("newemail#example.com").then(function() {
console.log('success')
}).catch(function(error) {
console.log('failed')
});
I have also tried :
try {
await user.updateEmail('test#test.com').then(function() {
console.log('success')
}).catch(function(error) {
handleErrors(dispatch, error.message);
});
} catch(e) {
handleErrors(dispatch, e.message);
}
could you help to solve this issue?
update :
sorry i didn't include the exact error that come out in the console , here is the error :
This operation is sensitive and requires recent authentication. Log in
again before retrying this request.
2)
I should add that I am using asyncstorage of react native , and I store that Item( profile of user => email , name , ect..) in reduxPersist
that how my login code is :
export const login = ( email, password ) => {
return async (dispatch) => {
dispatch({ type: ATTEMPTING });
try {
await firebase.auth().signInWithEmailAndPassword(email, password)
.then(resp => handleLoginSuccess(dispatch, resp.uid,resp.name,email))
.catch(error => handleErrorLogin(dispatch, error.message));
}catch(e){
handleErrorLogin(dispatch, e.message);
}
};
};
const handleLoginSuccess = async(dispatch , userId,name,email) => {
try{
const profile = { userId, name, email };
await AsyncStorage.setItem('userProfile', JSON.stringify(profile));
dispatch({ type: LOGIN_SUCCESS, payload: profile });
}catch(e){
alert(e.message);
}
}
The error message is telling you that there is no user logged in at the time you call updateEmail. The SDK doesn't know which user you're trying to modify. You'll need to wait until the login is fully complete before calling that method.
Note that all of the Firebase APIs are asynchronous, so you'll need to make use of their returned promises to make sure the order of calls is valid.
I am using the FacebookAuthProvider by firebase to login my users from my platform.
I'm using react native in expo with firestore and it was working fine till I tried to add in some checks to redirect users to the correct screens after login. There are two different roles (administrators and users) which have to be separate right after the login.
if (/* user is administrator */) {
this.props.navigation.navigate('Admin');
} else {
this.props.navigation.navigate('Main');
}
After adding this method to separate users by there roles, I got this error:
react native TypeError: Cannot read property 'navigation' of undefined
Later I will add some more details (log files etc. as soon as I've learned how to grep them from my locale machine).
For better understanding I put my whole code here (sorry for the bad indentations which lesses the readability):
const auth = firebase.auth();
const firebaseUser = '';
const usersRef = firebase.firestore().collection('users');
async handleFacebookButton() {
const { type, token, } = await Facebook.logInWithReadPermissionsAsync(FACEBOOK_APP_ID, {
permissions: ['public_profile', 'email']
});
if (type === 'success') {
//Firebase credential is created with the Facebook access token.
const credential = firebase.auth.FacebookAuthProvider.credential(token);
auth.signInAndRetrieveDataWithCredential(credential)
.then(function(userCredential) {
newUserCheck = userCredential.additionalUserInfo.isNewUser;
console.log('newUserCheck = ', newUserCheck)
});
this.setState({loggedIn: "You are signed in"})
this.setState({signedIn: true})
console.log('you are signed in');
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
firebaseUser = {name: user.displayName, uid: user.uid, email: user.email}
console.log(firebaseUser.name, ' and ', firebaseUser.uid);
var existingRef = usersRef.doc(firebaseUser.uid);
existingRef.get().then(function(documentSnapshot) {
// check if user is registered
if(documentSnapshot) {
data = documentSnapshot.data();
console.log('existing user exists!!');
// check if user is an administrator
if (data.administrator == true) {
console.log('existing administrator exists!!');
this.props.navigation.navigate('Admin');
} else { this.props.navigation.navigate('Main');
}
}
});
(error => {
console.log('user not accessed: ', error);
});
//User is not yet in firebase database and needs to be saved
// double check that user is a new user
if (newUserCheck == true) {
this.ref
.doc(uid)
.set({
id: firebaseUser.uid,
username: firebaseUser.name,
email: firebaseUser.email,
})
this.props.navigation.navigate('ChooseRoute')
}
}
})
}
// If login type is not success:
(error => {
this.setState({loggedIn: "Login failed: log in again"})
this.setState({ errorMessage: error.message });
});
}
I fixed it!! 3 days later - it was a binding issue - after several unsuccessful attempts to work out which were the right parts of the functions to bind I converted both 'auth().onAuthStateChanged' and 'documentSnapshot' into fat arrow functions and the errors are gone!! Thank goodness for ES6...! Hope this helps someone else down the line...