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.
Related
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.
Codecademy video: link
Explanation:
As part of my Codecademy Back-End Engineer training, I have to do a project outside of their platform. The goal of this project is to make sure a node application is protected from common web attacks.
One challenge I faced was securing the code from Cross-Site Scripting (XSS) attacks. To do this, I used a package called express-validator#6.12.1. The code uses a function called validator.escape which is supposed to protect against any malicious code being inserted into an input form. However, I am getting an error in the console when I try to use it.
Terminal output :
TypeError: validator.escape is not a function
Here is the code :
const validator = require("express-validator");
app.post("/public_forum", function (request, response) {
if (request.session.loggedin) {
var comment = validator.escape(request.body.comment);
var username = request.session.username;
if (comment) {
db.all(
`INSERT INTO public_forum (username,message) VALUES ('${username}','${comment}')`,
(err, rows) => {
console.log(err);
}
);
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
console.log(rows);
console.log(err);
response.render("forum", { rows });
});
} else {
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
console.log(rows);
console.log(err);
response.render("forum", { rows });
});
}
comment = "";
} else {
response.redirect("/");
}
comment = "";
//response.end();
});
In the video of Codecademy, the guy uses this function.
Try with:
const {check, validationResult} = require('express-validator');
app.post('/public_forum', async function (request, response) {
if (request.session.loggedin) {
await check('comment').trim().escape().run(req);
const validationResult = await validationResult(req);
if (validationResult.isEmpty()) {
// Good to go...
const { comment } = req.body;
}
...
Link to official docs
I have implemented your code. I tried to add both a malicious and safe comment, but I got an error message on my browser that said, "Port 4000 Not Found." Every time I run the code, it kills the port. So I have implemented another code that works well based on what you sent me.
// This code defines a post request handler for the "/public_forum" endpoint.
app.post('/public_forum', async function (request, response) {
// Check if the user is logged in by checking the session data.
if (request.session.loggedin) {
// Trim and escape the incoming comment.
await check('comment').trim().escape().run(request);
// Get the validation result of the incoming comment.
const errors = validationResult(request);
// If the validation result contains errors, return a 400 status with the errors in a JSON format.
if (!errors.isEmpty()) {
return response.status(400).json({ errors: errors.array() });
}
// Get the comment from the request body.
const { comment } = request.body;
// If a valid comment exists, insert it into the "public_forum" database table.
if (comment) {
db.run(
`INSERT INTO public_forum (username,message) VALUES (?,?)`, [request.session.username, comment],
(err) => {
// If an error occurs while inserting the comment, log the error.
if (err) {
console.error(err);
}
}
);
}
// Select all the rows from the "public_forum" table.
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
// If an error occurs while selecting the rows, log the error.
if (err) {
console.error(err);
}
// Log the selected rows.
console.log(rows);
// Render the "forum" template, passing in the selected rows as a parameter.
response.render("forum", { rows });
});
} else {
// If the user is not logged in, redirect them to the homepage.
response.redirect("/");
}
});
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.
I'm developing a paypal checkout using the 'basic Smart Payment Buttons integration' and integrating it with server Node installing the 'checkout-server-sdk'.
I followed the documentations:
https://developer.paypal.com/docs/checkout/reference/server-integration/set-up-transaction/
https://github.com/paypal/Checkout-NodeJS-SDK
where they suggest to:
'createOrder' starting from the client and calling the server
generating on the server an orderID and return it to the client
'onApprove' send to the server the orderID and approve it on the server
return back to the client the response
I don't think it is a good flow.
Someone could:
start the payment
so the app create the order on the server taking the shoppingcart from db and elaborate a totalPrice of 100euros.
generete the orderID and send it back to the client
instead of approve this order, a 'bad user' could, in some way, send to the server another orderID that could correspond to a lower price (2euros)
so he could approve the payment of 2 euros
So I don't understand why we need to make the checkout jumping more times from client to server.
Or maybe am i doing something wrong on my checkoutflow ?
unfortunately I feel the Paypal documentation so unclear.
checkout.component.html
<!-- * here there is a form where i get shipment info, invoice info and so on ->
<!-- * PAYPAL SMART BUTTONS -->
<div>
<div #paypal></div>
</div>
checkout.component.ts
onFormSubmit() {
this.isFormSubmitted = true;
// set paypal settings and show the paypal buttons
this.paypalSetting(this.shippmentInfo, this.invoiceRequired, this.invoice, this.addressInvoice);
}
async paypalSetting(shipment, invoiceRequired, invoice, addressInvoice) {
await paypal
.Buttons({
style: {
size: 'responsive',
label: 'pay',
},
experience: {
input_fields: {
no_shipping: 1,
},
},
createOrder: async (data, actions) => {
console.log('CREATE ORDER -->');
var paypalOrderId;
//generate new order
await this.apiService.newOrder().toPromise().then(
(res) => {
console.log('ON CREATE: SUCCESSFULLY CREATED')
paypalOrderId = res.order.paypalOrderId;
// ????? someone here could change 'paypalOrderId' with another value !!!!
//I also would like to return the 'paypalOrderId' only here !!
},
(err) => {
console.log('ON CREATE: ERROR: ' + err);
// how should i manage this error ? i should skip the flow to onError but HOW ?
}
);
return paypalOrderId;
},
onApprove: async (data, actions) => {
console.log('APPROVE ORDER -->');
var paypalOrderId = data.orderID;
console.log('ON APPROVE: save the order on server/DB')
await this.apiService.saveOrder(shipment, invoiceRequired, invoice, addressInvoice, paypalOrderId).toPromise().then(
(res) => {
console.log('ON APPROVE: ORDER APPROVED')
this.isPaid = true;
//if isPaid i can show a 'success page'
},
(err) => {
console.log('ON APPROVE: ERROR: ' + err);
this.isPaid = false;
}
);
},
onError: (err) => {
console.log('ON ERROR: ' + err);
},
})
.render(this.paypalElement.nativeElement);
}
Node api.js
//* paypal
const paypal = require('#paypal/checkout-server-sdk');
const payPalClient = require('../paypalManager');
router.post('/newOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
const idUser = req.userId;
// I get the shoppingcart of the user 'idUser'
// i calculate the total price
var totalPrice;
//* Call PayPal to set up a transaction
let order;
const request = new paypal.orders.OrdersCreateRequest();
request.prefer("return=representation");
request.requestBody({
intent: 'CAPTURE',
purchase_units: [{
description: 'payment ecc..', /
amount: {
currency_code: 'EUR',
value: totalPrice
}
}],
application_context: {
brand_name: "brand",
shipping_preference: 'NO_SHIPPING',
},
});
let response = await payPalClient.client().execute(request);
order = response;
const paypalOrderId = order.result.id;
// return a successful response to the client with the order ID
return res.json({
status: 200,
order: {
paypalOrderId: paypalOrderId,
},
message: "Paypal order sucessfully created",
});
});
router.post('/saveOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
const idUser = req.userId;
var paypalOrderId = req.body.paypalOrderId;
try {
connection.beginTransaction(async () => {
try {
// here i insert all the checkout infos in DB
// confirm the queries executions
connection.commit(async function (err) {
if (err) {
//return connection.rollback(function () {
connection.rollback(function () {
return next(createError.Unauthorized("Sql query error: " + err)); //! or error.message
});
}
//* here i send the Emails to confirm the checkout
//* capture/approve the order
console.log('CAPTURING THE ORDER')
var request = new paypal.orders.OrdersCaptureRequest(paypalOrderId);
request.requestBody({});
// Call API with your client and get a response for your call
let response = await payPalClient.client().execute(request);
//*response
return res.json({
status: 200,
message: "Paypal sucessfully approved",
});
});// end commit
} catch (error) {
connection.rollback(function () {
return next(createError.Unauthorized("Sql query error " + error)); //! or error.message
});
}
});// end transaction
} catch (error) {
return next(error);
}
});
Node paypalManager.js
'use strict';
/**
* PayPal Node JS SDK dependency
*/
const checkoutNodeJssdk = require('#paypal/checkout-server-sdk');
/**
* Returns PayPal HTTP client instance with environment that has access
* credentials context. Use this instance to invoke PayPal APIs, provided the
* credentials have access.
*/
function client() {
return new checkoutNodeJssdk.core.PayPalHttpClient(environment());
}
/**
* Set up and return PayPal JavaScript SDK environment with PayPal access credentials.
* This sample uses SandboxEnvironment. In production, use LiveEnvironment.
*/
function environment() {
let clientId = process.env.PAYPAL_CLIENT_ID;
let clientSecret = process.env.PAYPAL_CLIENT_SECRET;
return new checkoutNodeJssdk.core.SandboxEnvironment(
clientId, clientSecret
);
}
module.exports = {
client: client,
prettyPrint: prettyPrint
};
The reason you are "jumping" between the client and the server, is the approval by the payer has to happen on the client. The payer cannot give their approval on your server, they are not sitting on your server. They are using a client browser.
Regarding:
a 'bad user' could, in some way, send to the server another orderID that could correspond to a lower price (2euros)
If this happens, your server should reject the undesired transaction, and not proceed with it. That's the point of having a server. Nothing happens unless your server OKs it.
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');