Check If Firebase User Exist Without Throwing Error - node.js

I have a website that offers a simple messaging service. Individuals can pay for the service, or a business can pay for a monthly subscription and then add their clients/users for free. When the business adds a client/user email, that triggers the function below. I'm using firebase functions and createUser to create the user on my server(less). However, sometimes a business tries to register a user and that user already exist. In this case, I want to send the user a reminder email.
The code I have works fine, but it feels funky having a chain within my catch/error. Is there another way to detect if an email is already registered with a Firebase account that won't throw an error?
exports.newUserRegisteredByBusiness = functions.database.ref('users/{uid}/users/invited/{shortEmail}').onWrite( (data, context) => {
//don't run function if data is null
if (!data.after.val()){
console.log('SKIP: newUserRegisteredByBusiness null so skipping')
return null
} else {
let businessUID = context.params.uid
let email = data.after.val()
let shortEmail = context.params.shortEmail
let password // = something I randomly generate
return admin.auth().createUser({ email: email, password: password}).then( (user)=> {
//write new user data
let updates = {}
let userData // = stuff I need for service to run
updates['users/' + user.uid ] = userData;
return admin.database().ref().update(updates)
}).then( () =>{
//email new user about their new account
return emailFunctions.newUserRegisteredByBusiness(email, password)
}).catch( (error) =>{
//if user already exist we will get error here.
if (error.code === 'auth/email-already-exists'){
//email and remind user about account
return emailFunctions.remindUsersAccountWasCreated(email).then( ()=> {
//Once email sends, delete the rtbd invite value that triggered this whole function
//THIS IS WHERE MY CODE FEELS FUNKY! Is it ok to have this chain?
return admin.database().ref('users/' + businessUID + '/users/invited/' + shortEmail).set(null)
})
} else {
//delete the rtbd value that triggered this whole function
return admin.database().ref('users/' + businessUID + '/users/invited/' + shortEmail).set(null)
}
});
}
})

To find if a user account was already created for a given email address, you call admin.auth().getUserByEmail.
admin.auth().getUserByEmail(email).then(user => {
// User already exists
}).catch(err => {
if (err.code === 'auth/user-not-found') {
// User doesn't exist yet, create it...
}
})
While you're still using a catch() it feels like a much less failed operation.

To avoid further implementation in the catch block you can wrap this Firebase function into this code:
async function checkUserInFirebase(email) {
return new Promise((resolve) => {
admin.auth().getUserByEmail(email)
.then((user) => {
resolve({ isError: false, doesExist: true, user });
})
.catch((err) => {
resolve({ isError: true, err });
});
});
}
...
const rFirebase = await checkUserInFirebase('abc#gmail.com');

Related

Sequelize not retrieving all data after insert

I have noticed that my backend is not retrieving the expected data after an insert.
In my React application, I have one function which inserts data into the database and after getting a response, a new request is sent to update the current component state with the newly fetched data.
All my functions are using await/async and in the backend, all transactions are correctly used and committed in order.
My client is calling the following endpoints:
-POST: api/ticket ( INSERT AN ITEM)
-GET: api/ticket (GET ALL ITEMS)
Here is what the backend is showing which looks correct to me, the problem is that in the 'SELECT' statement, the inserted item is not retrieved.
The transactions are started from two different routes but I don't see why it should be an issue.
In addition, I tried to change the AddItem function to output the same findAll statement which is called when using the GET method and the data returned are correct.
So why if I separate these two flows I do not get all the items? I always need to refresh the page to get the added item.
START TRANSACTION;
Executing (a9d14d5c-c0ac-4821-9b88-293b086debaa): INSERT INTO `messages` (`id`,`message`,`createdAt`,`updatedAt`,`ticketId`,`userId`) VALUES (DEFAULT,?,?,?,?,?);
Executing (a9d14d5c-c0ac-4821-9b88-293b086debaa): COMMIT;
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): START TRANSACTION;
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): SELECT `ticket`.`id`, `ticket`.`subject`, `ticket`.`status`, `ticket`.`createdAt`, `ticket`.`updatedAt`, `ticket`.`deletedAt`, `ticket`.`userId`, `messages`.`id` AS `messages.id`, `messages`.`message` AS `messages.message`, `messages`.`sender` AS `messages.sender`, `messages`.`createdAt` AS `messages.createdAt`, `messages`.`updatedAt` AS `messages.updatedAt`, `messages`.`deletedAt` AS `messages.deletedAt`, `messages`.`ticketId` AS `messages.ticketId`, `messages`.`userId` AS `messages.userId`, `messages->user`.`id` AS `messages.user.id`, `messages->user`.`firstname` AS `messages.user.firstname`, `messages->user`.`surname` AS `messages.user.surname`, `messages->user`.`email` AS `messages.user.email`, `messages->user`.`password` AS `messages.user.password`, `messages->user`.`stripeId` AS `messages.user.stripeId`, `messages->user`.`token` AS `messages.user.token`, `messages->user`.`birthDate` AS `messages.user.birthDate`, `messages->user`.`status` AS `messages.user.status`, `messages->user`.`confirmationCode` AS `messages.user.confirmationCode`, `messages->user`.`createdAt` AS `messages.user.createdAt`, `messages->user`.`updatedAt` AS `messages.user.updatedAt`, `messages->user`.`deletedAt` AS `messages.user.deletedAt` FROM `tickets` AS `ticket` LEFT OUTER JOIN `messages` AS `messages` ON `ticket`.`id` = `messages`.`ticketId` AND (`messages`.`deletedAt` IS NULL) LEFT OUTER JOIN `users` AS `messages->user` ON `messages`.`userId` = `messages->user`.`id` AND (`messages->user`.`deletedAt` IS NULL) WHERE (`ticket`.`deletedAt` IS NULL);
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): COMMIT;
-- POST '/api/ticket
exports.addMessage = async (req, res) => {
try {
const result = await sequelize.transaction(async (t) => {
var ticketId = req.body.ticketId;
const userId = req.body.userId;
const message = req.body.message;
const subject = req.body.subject;
// Validate input - If new ticket, a subject must be provided
if (!ticketId && !subject) {
return res
.status(400)
.send({ message: "New ticket must have a subject" });
}
// Validate input - If ticket exists, userId and message must be provided
if (!userId && !message && ticketId) {
return res
.status(400)
.send({ message: "UserID and message are required" });
}
// Create ticket is no ticketID was provided
if (!ticketId) {
const [ticket, created] = await Ticket.findOrCreate({
where: {
subject: subject,
userId: userId,
},
transaction: t,
});
ticketId = ticket.id;
}
// Create a new message object
const messageObject = await db.message.create(
{
message: message,
userId: userId,
ticketId: ticketId,
},
{ transaction: t }
);
// Output message object
return res.send(messageObject);
});
} catch (err) {
console.log(err);
return res.status(500).send({
message:
err.message || "Some error occurred while creating the ticket message.",
});
}
};
-- GET: api/ticket
exports.findAll = async (req, res) => {
try {
const result = await sequelize.transaction(async (t) => {
const tickets = await db.ticket.findAll(
{
include: [{ model: db.message, include: [db.user] }],
},
{ transaction: t }
);
tickets.forEach((ticket) => {
console.log(JSON.stringify(ticket.messages.length));
});
return res.send(tickets);
});
} catch (err) {
console.log(err);
res.status(500).send({
message: err.message || "Some error occurred while retrieving Tickets.",
});
}
};
You sent a response to a client before the transaction actually was committed. You just need to move res.send(messageObject); outside the transaction call.
You can try to look what's going on in the current version of your code if you add several console.log with messages to see what the actual order of actions is (I mean a couple of messages in POST (the last statement inside transaction and after transaction before res.send) and at least one at the beginning of GET).
Actually if the transaction was rolled back you'd send an uncommited and already removed object/record that I suppose is not your goal.

How to fix error: Cannot set headers after they are sent?

I am using PostgreSQL for the first time with an express server and am running into an error. On my register user route I am trying to check if the username or email already exists, because they need to be unique. What keeps happening is, say I pass in a username that is already in the database then the first return will run and return that the username is already in use. But what is happening is it is returning the username is already in use and it still running the rest of the code so it trying to return multiple json responses.
module.exports.register = async (req, res, next) => {
try {
const { username, email, password } = req.body;
postgres
.query("SELECT * FROM users WHERE username = $1", [username])
.then((data) => {
if (data.rows.length > 0) {
return res.status(409).json({
msg: "Username is already in use",
status: false,
});
}
})
.catch((err) => {
console.log(err);
});
postgres
.query("SELECT * FROM users WHERE email = $1", [email])
.then((data) => {
if (data.rows.length > 0) {
return res.status(409).json({
msg: "Email is already in use",
status: false,
});
}
})
.catch((err) => {
console.log(err);
});
const hashedPassword = await bcrypt.hash(password, 10);
postgres.query(
"INSERT INTO users (username, email, password) VALUES ($1,$2,$3) RETURNING *",
[username, email, hashedPassword],
(err, data) => {
if (err) {
console.log(err.stack);
} else {
return res.json({ user: data.rows, status: true });
}
}
);
} catch (err) {
next(err);
}
};
I can't figure out why the rest of the code is running even though I am returning res.json. If anybody has any suggestions/solutions I would really appreciate it!
The return in front of the res.status(409) is returning you out of the then of the postgres.query function instead of the full register function. As a result it jumps out of the then and runs the rest of the code from there, so it's still hashing the password and attempting an insert into the users table (which hopefully fails on a unique index).
In order to fix this you can either 1) Define a variable before the function, change it if something was found and then do a return outside of the then statement if the variable was changed 2) perform all the rest of the code in the then statement (since you're returning out of that it will not be run) or 3) use awaits instead and throw/next+return/res.json+return an the HTTP 409 error.
Option 3 will take the most effort but you should definitely learn to use this route as soon as possible as it makes writing async code a lot easier (plus you'll avoid getting a bunch of nasty nested then statement). You could try out using option 1 and 2 just to get a feel for how the flow of the express code works.

function exits when passing data to aws cognito after reading file from s3 in aws lambda in nodejs

i am new to this , i am having problem , i have to create almost 200 users in cognito after reading data from csv file which is located in S3 bucket
the problem is , if a user already exists in Cognito , my code stop executing and give me an error "An account with the given email already exists." is there a way that i can pass the whole data. if there is user already in the cognito with the same email, it skips that user and checks for the new user data , and at the end which users are already exists in cognito .this the function to create user in cognito
here is the function for creating the cognito user
function RegisterUser(data2) {
console.log(data2[1])
for(let i=0;i<=data2.length;i++){
var attributeList = [];
var cognitoUser;
attributeList.push(new AmazonCognitoIdentity.CognitoUserAttribute({ Name: "name", Value: data2[i][0]}));
attributeList.push(new AmazonCognitoIdentity.CognitoUserAttribute({ Name: "email", Value: data2[i][1] }));
try{
return new Promise((resolve, reject) => {
userPool.signUp(data2[i][1], data2[i][2], attributeList, null, (err, result) => {
if (err) {
console.log(err.message);
reject(err);
return;
}
cognitoUser = result.user;
resolve(cognitoUser);
});
});
}catch(err){
return{
success:false,
message:err
}
}
}
}
here is the lambda handler
exports.handler = async (event, context) => {
try {
// Converted it to async/await syntax just to simplify.
const data = await S3.getObject({Bucket: 'user-data-file', Key: 'SampleCSVFile_2kb.csv'}).promise();
var data1 = Buffer.from(data.Body).toString();
var data2 = data1.split("\r\n"); // SPLIT ROWS
for (let i in data2) { // SPLIT COLUMNS
data2[i] = data2[i].split(",");
}
const userPoolResponse = await RegisterUser(data2);
}
catch (err) {
return {
statusCode: err.statusCode || 400,
body: err.message || JSON.stringify(err.message)
}
}
}
A quick google search brought this up: How to check Email Already exists in AWS Cognito?
Which sure thats Front end but your use case seem to be a quick once in a while run script, not a regular use User System - in which case, this is basic programing 101 to solve. You put another try catch around your call to register the user. You check the exception thrown, and if its 'already registered' you pass and continue in the loop without interruption. The above link can give you some idea of what to look for to determine if it is that exception or not.

Firebase Functions won't read document on Firestore

Hi I'm trying to read a users document stored on Firestore using Firebase Functions. Each user has a unique document with extra data that cannot be stored on Firebase Auth. The document name is the user UID.
But I can't access the doc when I'm trying to read it on my callable function.
Code to create doc when user is created:
exports.createdacc = functions.auth.user().onCreate(user => {
console.log('User created', user.phoneNumber);
return admin.firestore().collection('users').doc(user.uid).set({
number: user.phoneNumber,
verified: false,
});
});
Callable function to read that doc so I can make some decisions
exports.checkVerification = functions.https.onCall((data, context) => {
if (!context.auth){
throw new functions.https.HttpsError('unauthenticated');
}
console.log('user is ', context.auth.uid);
const user = admin.firestore().collection('users').doc(context.auth.uid);
user.get().then(doc => {
//temp code -- Not working
console.log('data read');
if (doc.get().verified){
console.log('verified');
} else {
console.log('not verified');
}
return "success";
}).catch(error => {
throw new functions.https.HttpsError('internal');
});
});
Why cant I read the doc? Nothing inside there executes.
Try to use data() at callback of user.get()
user.get().then(doc => {
//you get user doc value by using data()
const userData = doc.data();
// then you can use all properties from userData
const verified = userData.verified;
});
You don't return the promise returned by user.get().then(...);: your Cloud Function may be cleaned up before the asynchronous work is complete and the response sent back to the front-end.
Note that doing doc.get().verified is incorrect: as you will see in the doc, you need to pass the field path of a specific field to this method. So either you do doc.get("verified") or you can do doc.data().verified;.
Therefore the following should work:
exports.checkVerification = functions.https.onCall((data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated');
}
console.log('user is ', context.auth.uid);
const user = admin.firestore().collection('users').doc(context.auth.uid);
return user.get().then(doc => {
console.log('data read');
if (doc.get("verified") {
console.log('verified');
} else {
console.log('not verified');
}
return "success";
}).catch(error => {
throw new functions.https.HttpsError('internal');
});
});
In addition, note that you may throw an error if the user document does not exist and return a specific error to the front-end, i.e. not the generic internal one (maybe not-found, see the list of possible codes).
I have seen, on occasion, that information coming in to the function via context and data are actually JSON, and not strictly a standard Javascript object. In a similar issue of matching (in my case, a customClaim on the context.auth.token), I had to do something like:
JSON.parse(JSON.stringify(context.auth.token.customCLaim))
They behave like an object (i.e. I can call/assign context.auth.token.customClaim), but results from a console.log are different.
console.log(context.auth.token.customCLaim);
//prints {"userID": "1234567890"}
console.log(JSON.parse(JSON.stringify(context.auth.token.customClaim)));
//prints {userID: "1234567890"}
Subtle, but it tripped me up in a few authentication cases.

Stripe - Update default card

I am trying to allow the user to update their default payment method after they add it. I am getting this in Firebase Functions: Error: No such source: card_1EhmibFZW9pBNLO2aveVfEm6.
This leads me to believe that I need to pass default_source a src_XXX... id rather than a card_XXX... id. Anyone have an idea on this?
Firebase Function:
// Update Stripe default card based on user choice
exports.updateDefaultSource = functions.firestore
.document("users/{userId}")
.onUpdate(async (change, context) => {
const newValue = change.after.data();
const previousValue = change.before.data();
console.log("previousValue.default_source: "+previousValue.default_source)
console.log("newValue.default_source: "+newValue.default_source)
if (
previousValue.default_source &&
newValue.default_source !== previousValue.default_source
) {
// this triggers on every update to profile (more overhead), can we reduce this?
try {
console.log("newValue.default_source: "+newValue.default_source)
const response = await stripe.customers.update(
previousValue.customer_id,
{ default_source: newValue.default_source },
(err, customer) => {
console.log(err);
}
);
return console.log("Response from Stripe update: " + response);
} catch (error) {
console.log(error);
await change.ref.set(
{ error: userFacingMessage(error) },
{ merge: true }
);
return reportError(error, { user: context.params.userId });
}
}
});
Firebase Function logs after I add the second Card to account:
Looks like this error solved itself, not 100% sure on how, but my guess is it had to do with Redux and/or Redux Persist not having everything loaded into the store.
My main question was answered by #hmunoz on whether or not the default_source accepted the card_123 type, which it does.

Resources