Firebase Admin SDK NodeJS -- "There is no user record corresponding to the provided identifier." Error - firebase

Following the Firebase SDK docs on https://firebase.google.com/docs/auth/admin/email-action-links#generate_email_verification_link and getting the following error, which makes little sense as the function is triggered from the server environment using the authenticated admin.auth().
Might anyone know what is causing the issue?
Error from admin.auth().generateEmailVerificationLink : { Error: There is no user record corresponding to the provided identifier.
at FirebaseAuthError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseAuthError (/srv/node_modules/firebase-admin/lib/utils/error.js:147:16)
at Function.FirebaseAuthError.fromServerError (/srv/node_modules/firebase-admin/lib/utils/error.js:186:16)
at /srv/node_modules/firebase-admin/lib/auth/auth-api-request.js:1201:49
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
errorInfo:
{ code: 'auth/user-not-found',
message: 'There is no user record corresponding to the provided identifier.' },
codePrefix: 'auth' }
The code is just this:
exports = module.exports = functions.firestore
.document("xxx/{docId}")
.onCreate(async (snap, context) => {
let yyy = snap.data();
let { uid, userData } = yyy;
console.log("from sendEmailVerification, uid, userdata: ", userData);
const actionCodeSettings = {
url: `${emailConfirmationUrl}`,
handleCodeInApp: false
};
return admin.auth().generateEmailVerificationLink(userData.email, actionCodeSettings)
.then(async (link) => {
console.log("uid from generateEmailVerificationLink and email: ", uid, userData.email)
await admin.firestore().collection('users').doc(uid).set({
verificationLink: link,
emailVerified: false
}, { merge: true });
return emailfunc.sendCustomVerificationEmail(userData.email, link);
})
.catch((err) => {
console.error("Error from admin.auth().generateEmailVerificationLink :", err);
return Promise.reject(err);
});
});

You read the user's email address from the database (Firestore). That user account, however, doesn't exist in Firebase Auth. It must also exist in Firebase Auth if you wish to use APIs like getUser() and generateEmailVerificationLink(). Having it only in Firestore is not enough.

Related

Sync user with Firebase functions to Hasura GraphQL

I want to use firebase to authenticate users and then firebase functions to insert users into Hasura but having problems with the firebase functions.
When I try to create a user from the app the "registerUser" function, which can be found below, it ends with an error:
Error detected in registerUser:
{"#type":"type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.Insight",
"errorGroup":"CLic1cmw6emOsAE",
"errorEvent":{"message":"Error: The uid must be a non-empty string with at most 128 characters.
at FirebaseAuthError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)\
at new FirebaseAuthError (/srv/node_modules/firebase-admin/lib/utils/error.js:147:16)
at AuthRequestHandler.AbstractAuthRequestHandler.setCustomUserClaims (/srv/node_modules/firebase-admin/lib/auth/auth-api-request.js:996:35)
at Auth.BaseAuth.setCustomUserClaims (/srv/node_modules/firebase-admin/lib/auth/auth.js:342:40)
at exports.registerUser.functions.https.onCall (/srv/index.js:32:18)
at func (/srv/node_modules/firebase-functions/lib/providers/https.js:272:32)
at corsHandler (/srv/node_modules/firebase-functions/lib/providers/https.js:292:44)\n at cors (/srv/node_modules/cors/lib/index.js:188:7)
at /srv/node_modules/cors/lib/index.js:224:17","eventTime":"2020-06-10T08:25:03.017Z","serviceContext":{"service":"registerUser","resourceType":"cloud_function"}}}
If I instead create a user directly via the firebase console my "processSignUp" runs
but ends with another error:
ReferenceError: fetch is not defined
at GraphQLClient.<anonymous> (/srv/node_modules/graphql-request/dist/src/index.js:108:25)
at step (/srv/node_modules/graphql-request/dist/src/index.js:44:23)
at Object.next (/srv/node_modules/graphql-request/dist/src/index.js:25:53)
at /srv/node_modules/graphql-request/dist/src/index.js:19:71
at new Promise (<anonymous>)
at __awaiter (/srv/node_modules/graphql-request/dist/src/index.js:15:12)
at GraphQLClient.request (/srv/node_modules/graphql-request/dist/src/index.js:98:16)
at exports.processSignUp.functions.auth.user.onCreate (/srv/index.js:60:25)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:132:23)
at /worker/worker.js:825:24
I've tried pretty much everything I could think of. I've used https://hasura.io/jwt-config/ to setup the JWT on Heroku. I've triple checked passwords and graphQL endpoint. I have no problems with the mutations or query variables when I play around in hasura console but I'm unable to connect the firebase functions to hasura. Thanks in advance.
functions/index.js
...
const client = new request.GraphQLClient(
"https://app-name.herokuapp.com/v1/graphql",
{
headers: {
"content-type": "application/json",
"x-hasura-admin-secret": "Password",
},
}
);
...
// On register.
exports.registerUser = functions.https.onCall((data) => {
const { email, password } = data;
try {
const userRecord = admin.auth().createUser({ email, password });
const customClaims = {
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user"],
"x-hasura-user-id": userRecord.uid,
},
};
admin.auth().setCustomUserClaims(userRecord.uid, customClaims);
return userRecord.toJSON();
} catch (e) {
let errorCode = "unknown";
let msg = "Something went wrong, please try again later";
if (e.code === "auth/email-already-exists") {
errorCode = "already-exists";
msg = e.message;
}
throw new functions.https.HttpsError(errorCode, msg, JSON.stringify(e));
}
});
...
// On sign up.
exports.processSignUp = functions.auth.user().onCreate(async (user) => {
const { uid: id, email } = user;
const mutation = `
mutation($id: String!, $email: String) {
insert_users(objects: [{
id: $id,
email: $email,
}]) {
affected_rows
}
}
`;
try {
const data = await client.request(mutation, { id, email });
return data;
} catch (e) {
throw new functions.https.HttpsError("invalid-argument", e.message);
}
});
In the package.json for your functions, try changing the node engine to 10 and your grapql-request package to 1.8.2.

Using vuex, firestore, and createUserWithEmailAndPassword, how do I create a user profile collection when a user registers?

For the app I'm building, I want my users to have a profile created for them when they register; the profile would contain the user's username, email, and the uid created by firebase authentication. I've got the authentication portion using createUserWithEmailAndPassword to work on its own. I'm also able to create a "users" collection, capturing a username and the user's email, on its own as well. However, I'm running into trouble grabbing and saving the uid to the user's profile in the user's collection.
Here is the code I have at the moment:
import * as firebase from "firebase/app";
import db from "../../components/firebase/firebaseInit";
actions: {
registerUser({ commit }, payload) {
commit("setLoading", true);
commit("clearError");
firebase
.auth()
.createUserWithEmailAndPassword(payload.email, payload.password)
.then(user => {
commit("setLoading", false);
const newUser = {
email: user.email,
id: user.uid,
courses: []
};
commit("setUser", newUser);
db.collection("users")
.add({
username: payload.username,
email: user.email,
userId: user.uid
})
.then(() => {
console.log("New user added!");
})
.catch(err => {
console.log(err);
});
})
.catch(err => {
commit("setLoading", false);
commit("setError", err);
});
},
In the research I've done, I've found these suggested solutions:
Get Current User Login User Information in Profile Page - Firebase and Vuejs
Cloud Firestore saving additional user data
And this video:
https://www.youtube.com/watch?v=qWy9ylc3f9U
And I have tried using the set() method instead of add(), as well.
But none of them seem to work, for me at least.
Thank you in advance for your help.
And if you need to see any more code, just let me know.
You haven't shared the error message you get, but most probably the error comes from the fact that the createUserWithEmailAndPassword() method returns a UserCredential and not a User.
So you have to do as follows:
import * as firebase from "firebase/app";
import db from "../../components/firebase/firebaseInit";
actions: {
registerUser({ commit }, payload) {
commit("setLoading", true);
commit("clearError");
firebase
.auth()
.createUserWithEmailAndPassword(payload.email, payload.password)
.then(userCredential=> {
commit("setLoading", false);
const user = userCredential.user; // <-- Here is the main change
const newUser = {
email: user.email,
id: user.uid,
courses: []
};
commit("setUser", newUser);
return db.collection("users")
.add({
username: payload.username,
email: user.email,
userId: user.uid
});
})
.then(() => {
console.log("New user added!");
})
.catch(err => {
commit("setLoading", false);
commit("setError", err);
});
},

Reference.set failed: First argument contains undefined

I have created a firebase function that listen on onCreate event, however the DocumentSnapshot.data() is returning empty.
The function code is:
exports.createClientAccount = functions.firestore
.document('/userProfile/{userId}/clientList/{clientId}')
.onCreate(async (snap, context) => {
console.log('****snap.data(): ', snap.data()); //Showing Empty from the console.
return admin
.auth()
.createUser({
uid: context.params.clientId,
email: snap.data().email,
password: '123456789',
displayName: snap.data().fullName,
})
.then(userRecord => {
return admin
.database()
.ref(`/userProfile/${userRecord.uid}`)
.set({
fullName: userRecord.displayName, //ERROR here: Reference.set failed: First argument contains undefined
email: userRecord.email,
coachId: context.params.userId,
admin: false,
startingWeight: snap.data().startingWeight,
});
})
.catch(error => {
console.error('****Error creating new user',error);
});
});
The document IS created on the firebase database under
/userProfile/{userId}/clientList/{clientId}
clientId document created on the database
As per the documentation, onCreate listens when a new document is created and returns the snapshot of the data created through the DocumentSnapshot interface. However, I have checked from the console that snap.data() is empty. I don't understand why it is empty if the document is created successfully on the database.
image showing error returned by the functions when creating the userProfile
From the function code, return admin.auth.createUser({}) is creating the user as anonymous because snap.data().email is undefined, but it should create a non anonymous user.
First, please try to change document('/userProfile/{userId}/clientList/{clientId}') to document('userProfile/{userId}/clientList/{clientId}').
path should not start with /.
exports.createClientAccount = functions.firestore
.document('userProfile/{userId}/clientList/{clientId}')
At the end problem was that when I created the document with add({}) I was not including the fields in the instruction. This is the function that creates the client document and now the function gets triggered correctly.
async clientCreate(
fullName: string,
email: string,
startingWeight: number
): Promise<any> {
const newClientRef = await this.firestore
.collection(`userProfile/${this.userId}/clientList/`)
.add({
fullName,
email,
startingWeight: startingWeight * 1,
});
return newClientRef.update({
id: newClientRef.id
});
}
I was calling this.firestore.collection(...).add({}) with no fields, so when it happened, the cloud function got triggered and the DocumentSnapshot.data() was empty thus returning the Reference.set error. The cloud function createClientAccount is correct. Thanks.

Flutter calling firebase cloud function admin.auth.updateUser

EDIT**
Ok so I was able to get the parameters working thanks to first answer provided but now I have an issue whereby my function is creating a new user entirely in Firebase and not update an existing one, the uid that i am passing into the auth.admin.updateUser is teh uid of the existing user who's email i want to update. Here is the updated cloud function which is adding a new user rather than updating the existing:
exports.updateEmail = functions.https.onCall((data, context) => {
const email = data.email;
const uid = data.uid;
admin.auth().updateUser(uid, {
email: email
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully updated user", userRecord.toJSON());
return response.status(200).json(userRecord.toJSON());
})
.catch(function(error) {
console.log("Error updating user:", error);
return response.status(404).json({
error: 'Something went wrong.'
});
});
});
I got the function from the firebase docs but it isn't doing what I intended it to do.
ORIGINAL POST**
I'm having some difficulty getting a cloud function to work when calling the function from within my flutter code. The issue that I am having is that the uid and email fields are undefined even though I am passing them through to the cloud function using busboy fields.
I'm trying to pass the email and uid field though to the function as follows:
final request = http.MultipartRequest('POST', Uri.parse('****************my function url************'));
request.fields['email'] = Uri.encodeComponent(newEmail);
request.fields['uid'] = Uri.encodeComponent(selectedUser.uid);
request.headers['Authorization'] = 'Bearer ${_authenticatedUser.token}';
final http.StreamedResponse streamedResponse = await request.send();
And on the Node.js side I am trying to use these fields using busboy, here is my cloud function in Node.js:
exports.changeEmail = functions.https.onRequest((request, response) => {
if (!request.headers.authorization ||
!request.headers.authorization.startsWith('Bearer ')
) {
return response.status(401).json({
error: 'Unauthorized.'
});
}
let idToken;
idToken = request.headers.authorization.split('Bearer ')[1];
let email;
let uid;
const busboy = new Busboy({
headers: request.headers
});
busboy.on('field', (fieldname, value) => {
if (fieldname == 'email') {
email = decodeURIComponent(value);
}
if (fieldname == 'uid') {
uid = decodeURIComponent(value);
}
});
admin.auth().updateUser(uid, {
email: email
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully updated user", userRecord.toJSON());
return response.status(200).json(userRecord.toJSON());
})
.catch(function(error) {
console.log("Error updating user:", error);
return response.status(404).json({
error: 'Something went wrong.'
});
});
});
Even though I am passing the fields in with busboy fields they are not getting set in the function, is there something I am doing wrong here?
Why don't you use a callable function? It will automatically receive the authentication data.
The documentation even has examples on how to get the uid and email:
Declare the function:
exports.addMessage = functions.https.onCall((data, context) => {
// ...
});
Get the user properties from the context parameter:
// Message text passed from the client.
const text = data.text;
// Authentication / user information is automatically added to the request.
const uid = context.auth.uid;
const name = context.auth.token.name || null;
const picture = context.auth.token.picture || null;
const email = context.auth.token.email || null;
Call the function from your Flutter code:
Install cloud_functions package and then:
import 'package:cloud_functions/cloud_functions.dart';
await CloudFunctions.instance.call(functionName: 'addMessage');
If the user is authenticated before calling the function that's all you need to do.
You can also pass additional parameters to the function:
await CloudFunctions.instance.call(functionName: 'addMessage', parameters: {"email": "whatever#example.com"});
Any parameters will be passed to the data parameter on the function side.

Creating a new user via Firebase Cloud Functions - account created but can not sign in

I am new here.
I am currently developing an app using Vue.js with firebase auth, firebase realtime database and firebase cloud functions as a backend. One part of the functionalities that the app has to include an admin account that has a possibility to create an accounts for other people. After creation new user receives an email with a login and a password to log in.
Because the signing up method (https://firebase.google.com/docs/auth/web/password-auth) automatically relog user to a newly created account which is obviously not wanted behavior I found a way to create a user via cloud functions. The code successfully creates an account in the firebase authentication panel with the exception that I cannot log in to the newly created accounts. I get a message that: "The password is invalid or the user does not have a password".
Additionally I am not sure if it means anything in this case but the accounts created with the cloud functions method does not have a mail icon in firebase authentication panel (Image).
Cloud Function Code:
exports.createUser = functions.https.onCall((data, context) => {
console.log(data)
return admin.auth().createUser({
email: data.email,
emailVerified: true,
password: data.password,
displayName: data.email,
disabled: false,
})
.then(user => {
return {
response: user
}
})
.catch(error => {
throw new functions.https.HttpsError('failed to create a user')
});
});
Sign In Code:
signIn: async function(){
if(this.email && this.password){
let getUsers = firebase.functions().httpsCallable('getUsers')
this.feedback = null
this.spin = true
let destination = null
let logedUser = null
let type = null
this.feedback = 'Logging in...'
await firebase.auth().signInWithEmailAndPassword(this.email, this.password)
.then(response => {
this.feedback = 'Authorization finished...'
logedUser = response.user
})
.catch( error => {
this.feedback = error.message
this.spin = false
})
//... more code here but I am certain it has nothing to do with the problem.
Due to the asynchronous character of the HTTPS callable function, with your current code you are trying to sign-in before the user is completely created through the Cloud Function.
In addition, you are actually not calling the Cloud Function with the mail and password parameters.
You should do as follows, based on the documentation.
....
let getUsers = firebase.functions().httpsCallable('getUsers')
await getUsers({email: this.email, password: this.password})
.then(result => {
return firebase.auth().signInWithEmailAndPassword(this.email, this.password)
})
.then(userCredential => {
this.feedback = 'Authorization finished...'
logedUser = userCredential.user
return true
})
.catch( error => {
this.feedback = error.message
this.spin = false
})
....

Resources