Refer to this question: The Braintree Dropin user interface does not work with the Ionic Framework if the force update
My current Ionic / Angular / Firebase + a very simple Node server application has a security issue when using Braintree to charge a user's credit card. The problem, according to @RaymondBerg, is that the client can publish any client identifier and create tokens with symbols and charge this client. Since all authorization of my user occurred in Firebase / Angular - client side. Therefore, when a user makes $ HTTP.post from my AngularJS / Ionic to my Node server, I donβt want to allow them again (since I donβt even know how to do this, so I use Firebase).
So, what is the strategy here to set up Firebase and my Node server to work with a payment system like braintree?
One thing I can think of is to first create a Node in my firebase before the HTTP request, and then pass the client id $ id for the request on the client side (Ionic app):
$scope.getToken = function () {
var ref = new Firebase('[FirebaseURL]/braintreePaymentToken');
var tokenObj = $firebaseObject(ref.child(posterId));
tokenObj.tokenGenerated = true;
tokenObj.$save().then(function(){
$http({
method: 'POST',
url: 'http://localhost:3000/api/v1/token',
data: {
userId: snapshot.key(),
}
})
}
In Firebase, I set a security rule as:
"braintreePayment": {
".read": false,
".write": false,
},
"braintreePaymentToken": {
"$uid": {
".read": "auth != null",
".write": "auth != null && auth.uid == $uid",
}
},
Thus, temp Node braintreePaymentToken can ONLY be recorded by the current application login user. Another login user (unholy user) cannot write on this Node b / c their auth.uid will not equal posterId, which posterId is the user to pay.
At the end of the server, I use it once to find out if I can find the value:
var ref = new Firebase('[FirebaseURL]');
app.post('/api/v1/token', jsonParser, function (request, response) {
var userId = request.body.userId;
console.log (userId);
ref.child('braintreePayment').child(userId).once("value", function(snapshot){
var exists = (snapshot.val() !== null);
console.log (exists);
if (exists) {
console.log ("using exsiting customer!");
ref.child('braintreePaymentToken').child(userId).once("value", function(snap) {
if (snap.val()) {
gateway.clientToken.generate({
customerId: snapshot.val().customerId
}, function (err, res) {
if (err) throw err;
response.json({
"client_token": res.clientToken
});
ref.child('braintreePaymentToken').child(userId).remove();
});
else {
response.json({
"client_token": "Unauthorized Access!"
});
}
} else {
console.log ("using no customer!");
gateway.clientToken.generate({}, function (err, res) {
if (err) throw err;
response.json({
"client_token": res.clientToken
});
});
}
});
});
( ), Firebase Once, , firebase/braintreePayment. , return transaction customerId, braintree.
app.post('/api/v1/process', jsonParser, function (request, response) {
var transaction = request.body;
ref.child('braintreePayment').child(transaction.userId).once("value", function(snapshot){
var exists = (snapshot.val() !== null);
console.log (exists);
if (exists) {
console.log ("Return customer!");
gateway.transaction.sale({
amount: transaction.amount,
paymentMethodNonce: transaction.payment_method_nonce,
options: {
submitForSettlement: true
},
}, function (err, result) {
if (err) throw err;
response.json(result);
});
} else {
console.log ("First time customer!");
gateway.transaction.sale({
amount: transaction.amount,
paymentMethodNonce: transaction.payment_method_nonce,
options: {
store_in_vault_on_success: true,
submitForSettlement: true
},
}, function (err, result) {
if (err) throw err;
console.log ("Customer Id: " + result.transaction.customer.id);
var customerId = result.transaction.customer.id;
ref.child('braintreePayment').child(transaction.userId).update({customerId: customerId});
response.json(result);
});
}
});
});
, . , , ...
Firebase, Node Braintree? OWASP? , , ?
!