Is there any way to prevent override/overwrite of functions/variables in singleton instance?

pocesar picture pocesar · Feb 3, 2013 · Viewed 12.8k times · Source

Consider this pseudo code:

(function(window){
   var options = { /*where everything goes */ };

   var instance = (function(options){
       for (var i in options){
       if (options.hasOwnProperty(i)){
         this[i] = options[i];
       }
     }
   })(options);

   instance.callbacks = function(cb){
     //...
   }

   instance.is_allowed = function()
    //... checks, return boolean
   }

   window.instance = instance;
})(this);

If anyone ever wanted to manipulate this code (a malicious user for example), he would rewrite the is_allowed function with his own, for example, using the address bar (he doesn't have firebug, who knows).

javascript:(function(){ window.instance.is_allowed = function(){ return true; } })();

This is a naive example, but that's the point, anything in Javascript can be overwritten.

I know in es5 we have the Object.defineProperty so you can set:

// being explicit
Object.defineProperty(instance, "is_allowed", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: function(){
    // do checks
  }    
});

Actually, what is BEST in this sense is to use Object.freeze(instance) or Object.seal(instance) instead of Object.defineProperty, since the later can be called again with writable: false (silly huh?)

Is there ANY way that it work in old browsers (namely IE6-8) without too much hassle? If it's impossible, then I'll just shrug and move on.

Answer

Jonas Schubert Erlandsson picture Jonas Schubert Erlandsson · Feb 3, 2013

Proposal

I won't say I'm an expert on these things. But assuming you can wrap your code completely and use events to trigger behavior you can use a structure like this:

Closed = function(args) { return (function() {
  "use strict";

  var secret, init, get_secret, use_secret;

  init = function(something){
    secret = something;
  };

  get_secret = function() {
    return secret;
  };

  use_secret = function () {
    console.log(secret);
  };

  /* Run constructor */
  init(args);

  /* Publish API */
  return { use_secret:use_secret };

}())};

Setting it up with obj = Closed("Anything"); you can still have a malicious user overwrite the use_secret() method, since it's exposed, but the get_secret() method, and any other internals, are protected.

If your init method declares a number of event bindings to the application you can keep your state private in this way. The events will be able to trigger internal methods since they where bound from inside the inner closure but external code won't see them.

Reservations

While this might solve your problem, I'm not 100% sure it does, it's not to be trusted anyway. Any user that want to penetrate your application can as long as the security is on the client side. There is nothing to stop them from crafting their own object to replace yours after the fact anyway, ES5 or no ES5.

For anything that actually needs to be secure you will have to revalidate on the server side. Never trust client side code to protect you, the request might not even come from the page you served ...