started putting together basic components, specifically proxies to get the session object to behave properly
This commit is contained in:
Родитель
2696233bda
Коммит
caca8a9454
16
README.md
16
README.md
|
@ -1,12 +1,20 @@
|
|||
secure sessions stored in cookies, for node.js
|
||||
Secure sessions stored in cookies, for node.js
|
||||
Middleware for Connect / Express apps.
|
||||
|
||||
Session content is secure and tamper-free.
|
||||
|
||||
This does *not* use connect's built-int session middleware, because,
|
||||
if it did, things would get nasty in implementation given the conflict
|
||||
between the session ID and the session content itself. Also, this library
|
||||
uses its own cookie parser so that setup is easier and less error-prone.
|
||||
|
||||
I don't recommend using both this middleware and connect's built-in
|
||||
session middleware.
|
||||
|
||||
The session content is built to be secure and tamper-free.
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
We don't want this library to depend on making any other calls, e.g. cookieParser.
|
||||
|
||||
var cookieSessions = require("cookie-sessions");
|
||||
app.use(cookieSessions({
|
||||
cookieName: 'session',
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
|
||||
/*
|
||||
* Default handler for proxies
|
||||
*
|
||||
* based on
|
||||
* http://wiki.ecmascript.org/doku.php?id=harmony:proxy_defaulthandler
|
||||
*/
|
||||
|
||||
function Handler(target) {
|
||||
this.target = target;
|
||||
};
|
||||
|
||||
Handler.prototype = {
|
||||
|
||||
// == fundamental traps ==
|
||||
|
||||
// Object.getOwnPropertyDescriptor(proxy, name) -> pd | undefined
|
||||
getOwnPropertyDescriptor: function(name) {
|
||||
var desc = Object.getOwnPropertyDescriptor(this.target, name);
|
||||
if (desc !== undefined) { desc.configurable = true; }
|
||||
return desc;
|
||||
},
|
||||
|
||||
// Object.getPropertyDescriptor(proxy, name) -> pd | undefined
|
||||
getPropertyDescriptor: function(name) {
|
||||
var desc = Object.getPropertyDescriptor(this.target, name);
|
||||
if (desc !== undefined) { desc.configurable = true; }
|
||||
return desc;
|
||||
},
|
||||
|
||||
// Object.getOwnPropertyNames(proxy) -> [ string ]
|
||||
getOwnPropertyNames: function() {
|
||||
return Object.getOwnPropertyNames(this.target);
|
||||
},
|
||||
|
||||
// Object.getPropertyNames(proxy) -> [ string ]
|
||||
getPropertyNames: function() {
|
||||
return Object.getPropertyNames(this.target);
|
||||
},
|
||||
|
||||
// Object.defineProperty(proxy, name, pd) -> undefined
|
||||
defineProperty: function(name, desc) {
|
||||
return Object.defineProperty(this.target, name, desc);
|
||||
},
|
||||
|
||||
// delete proxy[name] -> boolean
|
||||
delete: function(name) { return delete this.target[name]; },
|
||||
|
||||
// Object.{freeze|seal|preventExtensions}(proxy) -> proxy
|
||||
fix: function() {
|
||||
// As long as target is not frozen, the proxy won't allow itself to be fixed
|
||||
if (!Object.isFrozen(this.target)) {
|
||||
return undefined;
|
||||
}
|
||||
var props = {};
|
||||
Object.getOwnPropertyNames(this.target).forEach(function(name) {
|
||||
props[name] = Object.getOwnPropertyDescriptor(this.target, name);
|
||||
}.bind(this));
|
||||
return props;
|
||||
},
|
||||
|
||||
// == derived traps ==
|
||||
|
||||
// name in proxy -> boolean
|
||||
has: function(name) { return name in this.target; },
|
||||
|
||||
// ({}).hasOwnProperty.call(proxy, name) -> boolean
|
||||
hasOwn: function(name) { return ({}).hasOwnProperty.call(this.target, name); },
|
||||
|
||||
// proxy[name] -> any
|
||||
get: function(receiver, name) { return this.target[name]; },
|
||||
|
||||
// proxy[name] = value
|
||||
set: function(receiver, name, value) {
|
||||
if (canPut(this.target, name)) { // canPut as defined in ES5 8.12.4 [[CanPut]]
|
||||
this.target[name] = value;
|
||||
return true;
|
||||
}
|
||||
return false; // causes proxy to throw in strict mode, ignore otherwise
|
||||
},
|
||||
|
||||
// for (var name in proxy) { ... }
|
||||
enumerate: function() {
|
||||
var result = [];
|
||||
for (var name in this.target) { result.push(name); };
|
||||
return result;
|
||||
},
|
||||
|
||||
/*
|
||||
// if iterators would be supported:
|
||||
// for (var name in proxy) { ... }
|
||||
iterate: function() {
|
||||
var props = this.enumerate();
|
||||
var i = 0;
|
||||
return {
|
||||
next: function() {
|
||||
if (i === props.length) throw StopIteration;
|
||||
return props[i++];
|
||||
}
|
||||
};
|
||||
},*/
|
||||
|
||||
// Object.keys(proxy) -> [ string ]
|
||||
keys: function() { return Object.keys(this.target); }
|
||||
};
|
||||
|
||||
module.exports = Handler;
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
var Cookies = require("cookies");
|
||||
var Proxy = require("node-proxy");
|
||||
var Handler = require("./ProxyHandler.js");
|
||||
|
||||
/*
|
||||
* Session object
|
||||
*
|
||||
* this should be implemented with proxies at some point
|
||||
*/
|
||||
function Session(req, res, cookies, opts) {
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
this.cookies = cookies;
|
||||
this.opts = opts;
|
||||
this.content = {};
|
||||
}
|
||||
|
||||
Session.prototype = {
|
||||
clear: function() {
|
||||
},
|
||||
|
||||
setExpires: function(numSeconds) {
|
||||
},
|
||||
|
||||
updateCookie: function() {
|
||||
this.cookies.set(this.opts.cookieName, JSON.stringify(this.content));
|
||||
},
|
||||
|
||||
// called to create a proxy that monitors the session
|
||||
// for new properties being set
|
||||
monitor: function() {
|
||||
// set up proxies
|
||||
var sessionHandler = new Handler(this);
|
||||
|
||||
// all values from content except special values
|
||||
sessionHandler.get = function(rcvr, name) {
|
||||
if (['clear', 'setExpires'].indexOf(name) > -1)
|
||||
return this.target[name];
|
||||
else
|
||||
return this.target.content[name];
|
||||
};
|
||||
|
||||
// set all values to content
|
||||
sessionHandler.set = function(rcvr, name, value) {
|
||||
this.target.content[name] = value;
|
||||
this.target.updateCookie();
|
||||
};
|
||||
|
||||
var proxySession = Proxy.create(sessionHandler);
|
||||
return proxySession;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var cookieSession = function(opts) {
|
||||
opts.cookieName = opts.cookieName || "session";
|
||||
|
||||
return function(req, res, next) {
|
||||
var cookies = new Cookies(req, res);
|
||||
req.session = new Session(req, res, cookies, opts).monitor();
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = cookieSession;
|
10
package.json
10
package.json
|
@ -10,16 +10,20 @@
|
|||
},
|
||||
"dependencies" : {
|
||||
"vows": "0.5.13",
|
||||
"cookies" : "https://github.com/jed/cookies/tarball/588822c"
|
||||
"cookies" : "https://github.com/jed/cookies/tarball/588822c",
|
||||
"express": "2.5.0",
|
||||
"tobi": "https://github.com/Cowboy-coder/tobi/tarball/master",
|
||||
"zombie": "0.12.9",
|
||||
"node-proxy": "0.5.2"
|
||||
},
|
||||
"author" : {
|
||||
"name" : "Ben Adida",
|
||||
"email" : "ben@adida.net"
|
||||
},
|
||||
"scripts" : {
|
||||
"test": "./node_modules/vows/bin/vows"
|
||||
"test": "./scripts/run_all_tests.sh"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6.2"
|
||||
"node": ">= 0.6.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
|
||||
BASEDIR=$(dirname $SCRIPT_DIR)
|
||||
export PATH=$PATH:$SCRIPT_DIR/../node_modules/.bin
|
||||
|
||||
VOWS=`which vows 2> /dev/null`
|
||||
if [ ! -x "$VOWS" ]; then
|
||||
echo "vows not found in your path. try: npm install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# vows hates absolute paths. sheesh.
|
||||
cd $BASEDIR
|
||||
|
||||
vows
|
||||
|
||||
# don't trigger npm doing funny stuff
|
||||
exit 0
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
var vows = require("vows"),
|
||||
assert = require("assert"),
|
||||
cookieSessions = require("../index"),
|
||||
express = require("express"),
|
||||
tobi = require("tobi"),
|
||||
Browser = require("zombie");
|
||||
|
||||
// set up the session middleware
|
||||
var middleware = cookieSessions({
|
||||
cookieName: 'session',
|
||||
secret: 'yo'
|
||||
});
|
||||
|
||||
var suite = vows.describe('all');
|
||||
|
||||
suite.addBatch({
|
||||
"request object" : {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(middleware);
|
||||
app.get("/foo", function(req, res) {
|
||||
console.log("yay");
|
||||
self.callback(null, req);
|
||||
res.send("hello");
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
browser.get("/foo", function(res, $) {});
|
||||
},
|
||||
"includes a session object": function(err, req) {
|
||||
assert.isObject(req.session);
|
||||
},
|
||||
"session object has clear method": function(err, req) {
|
||||
assert.isFunction(req.session.clear);
|
||||
},
|
||||
"session object has setExpires method": function(err, req) {
|
||||
assert.isFunction(req.session.setExpires);
|
||||
},
|
||||
"session object stores and retrieves values properly": function(err, req) {
|
||||
req.session.foo = 'bar';
|
||||
assert.equal(req.session.foo, 'bar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
suite.export(module);
|
Загрузка…
Ссылка в новой задаче