Как структурировать мое приложение для использования Firebase, Braintree, Ionic/AngularJS и минимального сервера nodejs

Обратитесь к этому вопросу: >Пользовательский интерфейс Braintree Dropin не работает с Ionic Framework без принудительного обновления

Мое текущее Ionic/Angular/Firebase + очень простое серверное приложение Node имеет проблему безопасности при использовании Braintree для списания средств с кредитной карты пользователя. Проблема, по словам @RaymondBerg, заключается в том, что клиент может опубликовать любой идентификатор клиента, создать токен Braintree и взимать плату с этого клиента. Поскольку вся моя авторизация пользователя происходила в Firebase/Angular — на стороне клиента. Поэтому, когда пользователь делает $HTTP.post с моего AngularJS/Ionic на мой сервер Node, я не хочу их снова авторизовать (поскольку я даже не знаю, как это сделать, поэтому я использую Firebase).

Итак, какова стратегия настройки Firebase и моего сервера Node для работы с платежной системой, такой как braintree?

Одна вещь, которую я могу придумать, это сначала создать узел в моей базе данных перед http-запросом, а затем передать клиентский $id для запроса на стороне клиента (приложение Ionic):

$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: {
            //user $id from Firebase
            userId: snapshot.key(),
          }
        })
       }

В Firebase я установил правило безопасности следующим образом:

  "braintreePayment": {
      ".read": false,
      ".write": false,
    },
   "braintreePaymentToken": {
      "$uid": {
       ".read": "auth != null",
       ".write": "auth != null && auth.uid == $uid",
      }
    },

Таким образом, временный узел braintreePaymentToken может быть записан ТОЛЬКО текущим пользователем, вошедшим в приложение. Другой пользователь, вошедший в систему (гнусный пользователь), не может писать на этом узле, т.к. их auth.uid не будет равен posterId, который posterId является пользователем, которому нужно платить.

На стороне сервера я использую один раз, чтобы увидеть, могу ли я найти значение:

var ref = new Firebase('[FirebaseURL]');
app.post('/api/v1/token', jsonParser, function (request, response) {

  var userId = request.body.userId;
  console.log (userId);

  //customerId from braintree is stored here so no one except the server can read it
  ref.child('braintreePayment').child(userId).once("value", function(snapshot){
    var exists = (snapshot.val() !== null);
    console.log (exists);
    if (exists) {
    console.log ("using exsiting customer!");

    //If braintreePaymentToken with userId child exsited, it mean this request is come from my Ionic client, not from anywhere else.

    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
        });
        //After I return the clientToken, I delete the braintreePaymentToken node. It is like using Firebase to send email with Zaiper. More secue I guess?
        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. Если нет, мы сохраняем один с идентификатором клиента транзакции возврата, созданным 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? Есть ли способ улучшить этот код, чтобы он был лучше, или есть лучший способ сделать это?

Спасибо!


person Hugh Hou    schedule 14.09.2015    source источник
comment
Эй, ты когда-нибудь понял это?   -  person Markinson    schedule 07.03.2018