GithubHelp home page GithubHelp logo

private's Introduction

private Build Status Greenkeeper badge

A general-purpose utility for associating truly private state with any JavaScript object.

Installation

From NPM:

npm install private

From GitHub:

cd path/to/node_modules
git clone git://github.com/benjamn/private.git
cd private
npm install .

Usage

Get or create a secret object associated with any (non-frozen) object:

var getSecret = require("private").makeAccessor();
var obj = Object.create(null); // any kind of object works
getSecret(obj).totallySafeProperty = "p455w0rd";

console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // []
console.log(getSecret(obj)); // { totallySafeProperty: "p455w0rd" }

Now, only code that has a reference to both getSecret and obj can possibly access .totallySafeProperty.

Importantly, no global references to the secret object are retained by the private package, so as soon as obj gets garbage collected, the secret will be reclaimed as well. In other words, you don't have to worry about memory leaks.

Create a unique property name that cannot be enumerated or guessed:

var secretKey = require("private").makeUniqueKey();
var obj = Object.create(null); // any kind of object works

Object.defineProperty(obj, secretKey, {
  value: { totallySafeProperty: "p455w0rd" },
  enumerable: false // optional; non-enumerability is the default
});

Object.defineProperty(obj, "nonEnumerableProperty", {
  value: "anyone can guess my name",
  enumerable: false
});

console.log(obj[secretKey].totallySafeProperty); // p455w0rd
console.log(obj.nonEnumerableProperty); // "anyone can guess my name"
console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // ["nonEnumerableProperty"]

for (var key in obj) {
  console.log(key); // never called
}

Because these keys are non-enumerable, you can't discover them using a for-in loop. Because secretKey is a long string of random characters, you would have a lot of trouble guessing it. And because the private module wraps Object.getOwnPropertyNames to exclude the keys it generates, you can't even use that interface to discover it.

Unless you have access to the value of the secretKey property name, there is no way to access the value associated with it. So your only responsibility as secret-keeper is to avoid handing out the value of secretKey to untrusted code.

Think of this style as a home-grown version of the first style. Note, however, that it requires a full implementation of ES5's Object.defineProperty method in order to make any safety guarantees, whereas the first example will provide safety even in environments that do not support Object.defineProperty.

Rationale

In JavaScript, the only data that are truly private are local variables whose values do not leak from the scope in which they were defined.

This notion of closure privacy is powerful, and it readily provides some of the benefits of traditional data privacy, a la Java or C++:

function MyClass(secret) {
    this.increment = function() {
        return ++secret;
    };
}

var mc = new MyClass(3);
console.log(mc.increment()); // 4

You can learn something about secret by calling .increment(), and you can increase its value by one as many times as you like, but you can never decrease its value, because it is completely inaccessible except through the .increment method. And if the .increment method were not available, it would be as if no secret variable had ever been declared, as far as you could tell.

This style breaks down as soon as you want to inherit methods from the prototype of a class:

function MyClass(secret) {
    this.secret = secret;
}

MyClass.prototype.increment = function() {
    return ++this.secret;
};

The only way to communicate between the MyClass constructor and the .increment method in this example is to manipulate shared properties of this. Unfortunately this.secret is now exposed to unlicensed modification:

var mc = new MyClass(6);
console.log(mc.increment()); // 7
mc.secret -= Infinity;
console.log(mc.increment()); // -Infinity
mc.secret = "Go home JavaScript, you're drunk.";
mc.increment(); // NaN

Another problem with closure privacy is that it only lends itself to per-instance privacy, whereas the private keyword in most object-oriented languages indicates that the data member in question is visible to all instances of the same class.

Suppose you have a Node class with a notion of parents and children:

function Node() {
    var parent;
    var children = [];

    this.getParent = function() {
        return parent;
    };

    this.appendChild = function(child) {
        children.push(child);
        child.parent = this; // Can this be made to work?
    };
}

The desire here is to allow other Node objects to manipulate the value returned by .getParent(), but otherwise disallow any modification of the parent variable. You could expose a .setParent function, but then anyone could call it, and you might as well give up on the getter/setter pattern.

This module solves both of these problems.

Usage

Let's revisit the Node example from above:

var p = require("private").makeAccessor();

function Node() {
    var privates = p(this);
    var children = [];

    this.getParent = function() {
        return privates.parent;
    };

    this.appendChild = function(child) {
        children.push(child);
        var cp = p(child);
        if (cp.parent)
            cp.parent.removeChild(child);
        cp.parent = this;
        return child;
    };
}

Now, in order to access the private data of a Node object, you need to have access to the unique p function that is being used here. This is already an improvement over the previous example, because it allows restricted access by other Node instances, but can it help with the Node.prototype problem too?

Yes it can!

var p = require("private").makeAccessor();

function Node() {
    p(this).children = [];
}

var Np = Node.prototype;

Np.getParent = function() {
    return p(this).parent;
};

Np.appendChild = function(child) {
    p(this).children.push(child);
    var cp = p(child);
    if (cp.parent)
        cp.parent.removeChild(child);
    cp.parent = this;
    return child;
};

Because p is in scope not only within the Node constructor but also within Node methods, we can finally avoid redefining methods every time the Node constructor is called.

Now, you might be wondering how you can restrict access to p so that no untrusted code is able to call it. The answer is to use your favorite module pattern, be it CommonJS, AMD define, or even the old Immediately-Invoked Function Expression:

var Node = (function() {
    var p = require("private").makeAccessor();

    function Node() {
        p(this).children = [];
    }

    var Np = Node.prototype;

    Np.getParent = function() {
        return p(this).parent;
    };

    Np.appendChild = function(child) {
        p(this).children.push(child);
        var cp = p(child);
        if (cp.parent)
            cp.parent.removeChild(child);
        cp.parent = this;
        return child;
    };

    return Node;
}());

var parent = new Node;
var child = new Node;
parent.appendChild(child);
assert.strictEqual(child.getParent(), parent);

Because this version of p never leaks from the enclosing function scope, only Node objects have access to it.

So, you see, the claim I made at the beginning of this README remains true:

In JavaScript, the only data that are truly private are local variables whose values do not leak from the scope in which they were defined.

It just so happens that closure privacy is sufficient to implement a privacy model similar to that provided by other languages.

private's People

Contributors

benjamn avatar greenkeeper[bot] avatar jamestalmage avatar ysangkok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

private's Issues

Expose a better interface for (automatically) forgetting old secrets

I'm thinking each accessor function will have a bound method called .autoForget that can be used wherever you have complete knowledge about secret life cycles:

var p = require("private").makeAccessor();
p.autoForget(function() {
  var secret1 = p(obj);
  secret1.foo = "whatever";
  ...
});
var secret2 = p(obj);

Since secret1 is created inside an autoForget context, p.forget(obj) will be called automatically when execution in that context finishes, so secret1 !=== secret2.

I'm also tempted to make .makeAccessor accept an optional requireAutoForget parameter to enforce that the accessor is never used outside of an autoForget context.

cc @amasad

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml
  • The new Node.js version is in-range for the engines in 1 of your package.json files, so that was left alone

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Tagging releases

When you bump the npm version, it would be helpful to also tag the release, so tarballs get generated.

I might package private for Debian, if I can lay my hand on a tarball...

`git push origin master` necessary?

$ npm show private dist-tags
{ latest: '0.1.7' }

package.json in master is still at 0.1.6, yet a v0.1.7 tag exists. git push --tags origin master, under some configurations, doesn't actually push master itself, only the tags.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.