1 |
3a515b92
|
cagy
|
# private [![Build Status](https://travis-ci.org/benjamn/private.png?branch=master)](https://travis-ci.org/benjamn/private) [![Greenkeeper badge](https://badges.greenkeeper.io/benjamn/private.svg)](https://greenkeeper.io/)
|
2 |
|
|
|
3 |
|
|
A general-purpose utility for associating truly private state with any JavaScript object.
|
4 |
|
|
|
5 |
|
|
Installation
|
6 |
|
|
---
|
7 |
|
|
|
8 |
|
|
From NPM:
|
9 |
|
|
|
10 |
|
|
npm install private
|
11 |
|
|
|
12 |
|
|
From GitHub:
|
13 |
|
|
|
14 |
|
|
cd path/to/node_modules
|
15 |
|
|
git clone git://github.com/benjamn/private.git
|
16 |
|
|
cd private
|
17 |
|
|
npm install .
|
18 |
|
|
|
19 |
|
|
Usage
|
20 |
|
|
---
|
21 |
|
|
**Get or create a secret object associated with any (non-frozen) object:**
|
22 |
|
|
```js
|
23 |
|
|
var getSecret = require("private").makeAccessor();
|
24 |
|
|
var obj = Object.create(null); // any kind of object works
|
25 |
|
|
getSecret(obj).totallySafeProperty = "p455w0rd";
|
26 |
|
|
|
27 |
|
|
console.log(Object.keys(obj)); // []
|
28 |
|
|
console.log(Object.getOwnPropertyNames(obj)); // []
|
29 |
|
|
console.log(getSecret(obj)); // { totallySafeProperty: "p455w0rd" }
|
30 |
|
|
```
|
31 |
|
|
Now, only code that has a reference to both `getSecret` and `obj` can possibly access `.totallySafeProperty`.
|
32 |
|
|
|
33 |
|
|
*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.*
|
34 |
|
|
|
35 |
|
|
**Create a unique property name that cannot be enumerated or guessed:**
|
36 |
|
|
```js
|
37 |
|
|
var secretKey = require("private").makeUniqueKey();
|
38 |
|
|
var obj = Object.create(null); // any kind of object works
|
39 |
|
|
|
40 |
|
|
Object.defineProperty(obj, secretKey, {
|
41 |
|
|
value: { totallySafeProperty: "p455w0rd" },
|
42 |
|
|
enumerable: false // optional; non-enumerability is the default
|
43 |
|
|
});
|
44 |
|
|
|
45 |
|
|
Object.defineProperty(obj, "nonEnumerableProperty", {
|
46 |
|
|
value: "anyone can guess my name",
|
47 |
|
|
enumerable: false
|
48 |
|
|
});
|
49 |
|
|
|
50 |
|
|
console.log(obj[secretKey].totallySafeProperty); // p455w0rd
|
51 |
|
|
console.log(obj.nonEnumerableProperty); // "anyone can guess my name"
|
52 |
|
|
console.log(Object.keys(obj)); // []
|
53 |
|
|
console.log(Object.getOwnPropertyNames(obj)); // ["nonEnumerableProperty"]
|
54 |
|
|
|
55 |
|
|
for (var key in obj) {
|
56 |
|
|
console.log(key); // never called
|
57 |
|
|
}
|
58 |
|
|
```
|
59 |
|
|
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.
|
60 |
|
|
|
61 |
|
|
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.
|
62 |
|
|
|
63 |
|
|
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`.
|
64 |
|
|
|
65 |
|
|
Rationale
|
66 |
|
|
---
|
67 |
|
|
|
68 |
|
|
In JavaScript, the only data that are truly private are local variables
|
69 |
|
|
whose values do not *leak* from the scope in which they were defined.
|
70 |
|
|
|
71 |
|
|
This notion of *closure privacy* is powerful, and it readily provides some
|
72 |
|
|
of the benefits of traditional data privacy, a la Java or C++:
|
73 |
|
|
```js
|
74 |
|
|
function MyClass(secret) {
|
75 |
|
|
this.increment = function() {
|
76 |
|
|
return ++secret;
|
77 |
|
|
};
|
78 |
|
|
}
|
79 |
|
|
|
80 |
|
|
var mc = new MyClass(3);
|
81 |
|
|
console.log(mc.increment()); // 4
|
82 |
|
|
```
|
83 |
|
|
You can learn something about `secret` by calling `.increment()`, and you
|
84 |
|
|
can increase its value by one as many times as you like, but you can never
|
85 |
|
|
decrease its value, because it is completely inaccessible except through
|
86 |
|
|
the `.increment` method. And if the `.increment` method were not
|
87 |
|
|
available, it would be as if no `secret` variable had ever been declared,
|
88 |
|
|
as far as you could tell.
|
89 |
|
|
|
90 |
|
|
This style breaks down as soon as you want to inherit methods from the
|
91 |
|
|
prototype of a class:
|
92 |
|
|
```js
|
93 |
|
|
function MyClass(secret) {
|
94 |
|
|
this.secret = secret;
|
95 |
|
|
}
|
96 |
|
|
|
97 |
|
|
MyClass.prototype.increment = function() {
|
98 |
|
|
return ++this.secret;
|
99 |
|
|
};
|
100 |
|
|
```
|
101 |
|
|
The only way to communicate between the `MyClass` constructor and the
|
102 |
|
|
`.increment` method in this example is to manipulate shared properties of
|
103 |
|
|
`this`. Unfortunately `this.secret` is now exposed to unlicensed
|
104 |
|
|
modification:
|
105 |
|
|
```js
|
106 |
|
|
var mc = new MyClass(6);
|
107 |
|
|
console.log(mc.increment()); // 7
|
108 |
|
|
mc.secret -= Infinity;
|
109 |
|
|
console.log(mc.increment()); // -Infinity
|
110 |
|
|
mc.secret = "Go home JavaScript, you're drunk.";
|
111 |
|
|
mc.increment(); // NaN
|
112 |
|
|
```
|
113 |
|
|
Another problem with closure privacy is that it only lends itself to
|
114 |
|
|
per-instance privacy, whereas the `private` keyword in most
|
115 |
|
|
object-oriented languages indicates that the data member in question is
|
116 |
|
|
visible to all instances of the same class.
|
117 |
|
|
|
118 |
|
|
Suppose you have a `Node` class with a notion of parents and children:
|
119 |
|
|
```js
|
120 |
|
|
function Node() {
|
121 |
|
|
var parent;
|
122 |
|
|
var children = [];
|
123 |
|
|
|
124 |
|
|
this.getParent = function() {
|
125 |
|
|
return parent;
|
126 |
|
|
};
|
127 |
|
|
|
128 |
|
|
this.appendChild = function(child) {
|
129 |
|
|
children.push(child);
|
130 |
|
|
child.parent = this; // Can this be made to work?
|
131 |
|
|
};
|
132 |
|
|
}
|
133 |
|
|
```
|
134 |
|
|
The desire here is to allow other `Node` objects to manipulate the value
|
135 |
|
|
returned by `.getParent()`, but otherwise disallow any modification of the
|
136 |
|
|
`parent` variable. You could expose a `.setParent` function, but then
|
137 |
|
|
anyone could call it, and you might as well give up on the getter/setter
|
138 |
|
|
pattern.
|
139 |
|
|
|
140 |
|
|
This module solves both of these problems.
|
141 |
|
|
|
142 |
|
|
Usage
|
143 |
|
|
---
|
144 |
|
|
|
145 |
|
|
Let's revisit the `Node` example from above:
|
146 |
|
|
```js
|
147 |
|
|
var p = require("private").makeAccessor();
|
148 |
|
|
|
149 |
|
|
function Node() {
|
150 |
|
|
var privates = p(this);
|
151 |
|
|
var children = [];
|
152 |
|
|
|
153 |
|
|
this.getParent = function() {
|
154 |
|
|
return privates.parent;
|
155 |
|
|
};
|
156 |
|
|
|
157 |
|
|
this.appendChild = function(child) {
|
158 |
|
|
children.push(child);
|
159 |
|
|
var cp = p(child);
|
160 |
|
|
if (cp.parent)
|
161 |
|
|
cp.parent.removeChild(child);
|
162 |
|
|
cp.parent = this;
|
163 |
|
|
return child;
|
164 |
|
|
};
|
165 |
|
|
}
|
166 |
|
|
```
|
167 |
|
|
Now, in order to access the private data of a `Node` object, you need to
|
168 |
|
|
have access to the unique `p` function that is being used here. This is
|
169 |
|
|
already an improvement over the previous example, because it allows
|
170 |
|
|
restricted access by other `Node` instances, but can it help with the
|
171 |
|
|
`Node.prototype` problem too?
|
172 |
|
|
|
173 |
|
|
Yes it can!
|
174 |
|
|
```js
|
175 |
|
|
var p = require("private").makeAccessor();
|
176 |
|
|
|
177 |
|
|
function Node() {
|
178 |
|
|
p(this).children = [];
|
179 |
|
|
}
|
180 |
|
|
|
181 |
|
|
var Np = Node.prototype;
|
182 |
|
|
|
183 |
|
|
Np.getParent = function() {
|
184 |
|
|
return p(this).parent;
|
185 |
|
|
};
|
186 |
|
|
|
187 |
|
|
Np.appendChild = function(child) {
|
188 |
|
|
p(this).children.push(child);
|
189 |
|
|
var cp = p(child);
|
190 |
|
|
if (cp.parent)
|
191 |
|
|
cp.parent.removeChild(child);
|
192 |
|
|
cp.parent = this;
|
193 |
|
|
return child;
|
194 |
|
|
};
|
195 |
|
|
```
|
196 |
|
|
Because `p` is in scope not only within the `Node` constructor but also
|
197 |
|
|
within `Node` methods, we can finally avoid redefining methods every time
|
198 |
|
|
the `Node` constructor is called.
|
199 |
|
|
|
200 |
|
|
Now, you might be wondering how you can restrict access to `p` so that no
|
201 |
|
|
untrusted code is able to call it. The answer is to use your favorite
|
202 |
|
|
module pattern, be it CommonJS, AMD `define`, or even the old
|
203 |
|
|
Immediately-Invoked Function Expression:
|
204 |
|
|
```js
|
205 |
|
|
var Node = (function() {
|
206 |
|
|
var p = require("private").makeAccessor();
|
207 |
|
|
|
208 |
|
|
function Node() {
|
209 |
|
|
p(this).children = [];
|
210 |
|
|
}
|
211 |
|
|
|
212 |
|
|
var Np = Node.prototype;
|
213 |
|
|
|
214 |
|
|
Np.getParent = function() {
|
215 |
|
|
return p(this).parent;
|
216 |
|
|
};
|
217 |
|
|
|
218 |
|
|
Np.appendChild = function(child) {
|
219 |
|
|
p(this).children.push(child);
|
220 |
|
|
var cp = p(child);
|
221 |
|
|
if (cp.parent)
|
222 |
|
|
cp.parent.removeChild(child);
|
223 |
|
|
cp.parent = this;
|
224 |
|
|
return child;
|
225 |
|
|
};
|
226 |
|
|
|
227 |
|
|
return Node;
|
228 |
|
|
}());
|
229 |
|
|
|
230 |
|
|
var parent = new Node;
|
231 |
|
|
var child = new Node;
|
232 |
|
|
parent.appendChild(child);
|
233 |
|
|
assert.strictEqual(child.getParent(), parent);
|
234 |
|
|
```
|
235 |
|
|
Because this version of `p` never leaks from the enclosing function scope,
|
236 |
|
|
only `Node` objects have access to it.
|
237 |
|
|
|
238 |
|
|
So, you see, the claim I made at the beginning of this README remains
|
239 |
|
|
true:
|
240 |
|
|
|
241 |
|
|
> In JavaScript, the only data that are truly private are local variables
|
242 |
|
|
> whose values do not *leak* from the scope in which they were defined.
|
243 |
|
|
|
244 |
|
|
It just so happens that closure privacy is sufficient to implement a
|
245 |
|
|
privacy model similar to that provided by other languages.
|