Server Methods, Security and Optimistic UI

Hi,

TL;DR how do I leverage Optimistic UI and have unassailable security using Methods to write to the DB?

I have a method that allows a user to be request a point and then have another user grant that point. It’s essential that the transaction happens on the server to stop malicious users from stealing points.Currently the method is in the server folder. There’s a UI lag because of this. I want to lose that lag.

I’m uncertain how to go about fixing this. It seems there are a few avenues.

Sharing the method seems like the Meteor Guides way of doing things but I’m uncertain how to secure this. If the code is readable that’s not good. Even if that’s not an issue is the code tamper proof on the server? If I don’t mind the user seeing the method can I be sure they can’t interfere with the server. This would allow me the benefits of Optimistic UI, right?

Or…

Should I create a method with isClient and isServer methods? If that’s the case, is the code wrapped in isServer hidden from the client? Will rollbacks happen nicely if the server rejects the call? How secure is isServer?

or…

Build my own helper function to update the UI to my own specs and use callbacks in the Methods.call function to control those helper functions. This seems like I’m rebuilding Meteor and losing out on a lot of the killer features of the framework.

The code is theoretically temper proof on the /server folder. If you want optimistic UI, you should put this method outside of /server or /client folder, so it can run on both environments.

If you need to hide your method implementation, you can leave it on the /server folder and create a method with the same name, on the /client folder, and code it as you like, but focusing on the optimistic UI, just doing the same validations as the server for example.

This /client only implementation will act as a stub: will run on the client on behalf of your server, while your call goes to the server (a.k.a optimistic UI)

I’m not sure which are the good practices in this case.

1 Like

Thanks for the insight!

Actually, the Guide talks about this exactly - see the Secret server code section.

Given that the whole point of running things on the server is for security, I would hope these methods are secure! :slight_smile:

1 Like

Thanks for the reply.
I was thinking about a UI element that depends for it’s visibility on a boolean in a record being true.

Clicking the UI element sets the value on the record to false and the UI element disappears.

Say I want to hide the method that sets the value and there’s no way to write a stub on the client without exposing the code then I’m forced to wait the server round trip out.

As far as I can see the only way to hide the UI element instantly is to wrap it in another value that depends on the value of the record but can be overridden on click and set again once the server returns a success or error.

I can’t figure out another way to do it. In the example in the Guide I’d have to write something that would control the wrapper I described right?

Depending on what you want to keep secret/secure, couldn’t you split things up to leverage Meteor’s optimistic UI capabilities a little better? For example do you really need to hide the code that determines if a UI element should show or not? If not extract this code out into the common client/server accessible part of the method. You would then only put the code you really need hidden in a server side library (the part of your code that deals with points for example), that is then referenced in your shared method within a if (!this.isSimulation) block (like the Guide references MMR.updateWithSecretAlgorithm).

Hey, thanks for the comment. I see where you’re coming from and I understand the concepts your referring to in the guide. The method I described would change records on the DB not change the UI. The UI would change by inference from the updated value.

Right - maybe an example would help. Let’s say you have a “request points” button. When clicked you want the button to disappear based on a value being stored in the database. You also want some secret server side stuff to happen with regards to points handling. Here’s a quick example showing what this could look like.

sample.html:

<body>
  {{#if Template.subscriptionsReady}}
    {{#unless pointsRequested}}
      <button>Request Points</button>
    {{else}}
      Points requested!
      (<a href="#" class="js-reset">reset</a>)
    {{/unless}}
  {{/if}}
</body>

sample.js:

Points = new Mongo.Collection('points');
const testLabel = 'test';

if (Meteor.isClient) {

  Template.body.onCreated(function onCreated() {
    this.pointsRequested = new ReactiveVar(false);
    const handle = this.subscribe('points');
    this.autorun(() => {
      if (handle.ready()) {
        const point = Points.findOne({ label: testLabel });
        this.pointsRequested.set(point.requested);
      }
    });
  });

  Template.body.helpers({
    pointsRequested() {
      return Template.instance().pointsRequested.get();
    }
  });

  Template.body.events({
    'click button'() {
      Meteor.call('requestPoints');
    },
    'click .js-reset'(event) {
      event.preventDefault();
      Meteor.call('reset');
    }
  });

}

if (Meteor.isServer) {

  Meteor.startup(() => {
    if (Points.find().count() === 0) {
      Points.insert({
        label: testLabel,
        requested: false
      });
    }
  });

  Meteor.publish('points', function publishPoints() {
    return Points.find();
  });

}

Meteor.methods({

  requestPoints() {
    Points.update({ label: testLabel }, { $set: { requested: true }});
    if (!this.isSimulation) {
      console.log('Secret stuff is happening - check the server console.');
      Meteor._sleepForMs(5000);
      SecretStuff.doSomethingSecret();
    }
  },

  reset() {
    Points.update({ label: testLabel }, { $set: { requested: false }});
  }

});

server/secret.js:

SecretStuff = {
  doSomethingSecret() {
    console.log('Something secret just happened!');
  }
};

Thanks for the detailed reply. You’ve coded what I described as the possible third solution in my original post, i.e. create another variable and set it. I’d choose to use the callback in Meteor.call to set the var. It’s a fine solution but introduces another level of abstraction which I was wondering if I could avoid, was I missing some meteor magic? Looking at the code you prescribed it seems not.

What I started off with originally (using viewmodel which I love) was something like this. Hope coffescript is ok…

pointsRequested: ->
    if Points.find({requested:true}).count() > 0 then true else false

requestPoint: ->
      Meteor.call('secretStuff')

method.js

Meteor.method
    secretStuff:->
           mindaltering code that write to the Points collection

Not a lot of code to do what it needs to do and if the server code is hidden then it should be secure. But to get the UI to update instantly I’d do what you did, which is a lot more code.

What I think I’ll do is write a mixin that I can use to control the UI elements which need this behaviour.

Thanks for all the input, between you and josmardias I think I have my answer.