diff --git a/README.md b/README.md
index 667ddc1b299a823b015ea5f775ff3b96edbe37ce..958a2579ac4e7db28f191c157a28fb4ebcaeb004 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ it go.  [npm](http://npmjs.org/) is a good tool to use to acquire the requisite
 * xml2js (>= 0.1.5)
 * sqlite (>= 1.0.3)
 * mustache (>= 0.3.1)
-* cookie-sessions (>= 0.0.2)
+* cookie-sessions (patched version included in-tree, nothing to be done)
 
 ## Getting started:
 
diff --git a/authority/server/run.js b/authority/server/run.js
index 73816d6f5c457e00e8e2ea0e8d79b5717aad9f5a..ea8bfa97f78054fbeaa3166c8ce1db1037a1e060 100644
--- a/authority/server/run.js
+++ b/authority/server/run.js
@@ -1,9 +1,10 @@
-const path = require('path'),
-       url = require('url'),
-     wsapi = require('./wsapi.js'),
- httputils = require('./httputils.js'),
-   connect = require('connect'),
- webfinger = require('./webfinger.js');
+const        path = require('path'),
+              url = require('url'),
+            wsapi = require('./wsapi.js'),
+        httputils = require('./httputils.js'),
+          connect = require('connect'),
+        webfinger = require('./webfinger.js'),
+         sessions = require('cookie-sessions'); 
 
 const STATIC_DIR = path.join(path.dirname(__dirname), "static");
 
@@ -40,16 +41,9 @@ exports.handler = function(request, response, serveFile) {
 
 exports.setup = function(server) {
   var week = (7 * 24 * 60 * 60 * 1000);
-
-  server
-    .use(connect.cookieParser())
-    .use(connect.session({
-      secret: "mouse dog",
-      cookie: {
-        path: '/wsapi',
-        httpOnly: true,
-        expires:  new Date(Date.now() + week),// a week XXX: think about session security, etc
-        maxAge: week
-      }
-    }));
+  server.use(sessions({
+      secret: 'v3wy s3kr3t',
+      session_key: "browserid_state",
+      path: '/'
+  }));
 }
diff --git a/authority/server/wsapi.js b/authority/server/wsapi.js
index 1dc316de155b6049e64d15df1618db98f18ed75d..dbe09145cb195f9a186ef9512431ce81e29d3ea1 100644
--- a/authority/server/wsapi.js
+++ b/authority/server/wsapi.js
@@ -24,8 +24,12 @@ function checkParams(getArgs, resp, params) {
   return true;
 }
 
-function isAuthed(req, resp) {
-  if (typeof req.session.authenticatedUser !== 'string') {
+function isAuthed(req) {
+  return (req.session && typeof req.session.authenticatedUser === 'string');
+}
+    
+function checkAuthed(req, resp) {
+  if (!isAuthed(req)) {
     httputils.badRequest(resp, "requires authentication");
     return false;
   }
@@ -59,11 +63,13 @@ exports.stage_user = function(req, resp) {
     // upon success, stage_user returns a secret (that'll get baked into a url
     // and given to the user), on failure it throws
     var secret = db.stageUser(getArgs);
-    httputils.jsonResponse(resp, true);
 
     // store the email being registered in the session data
+    if (!req.session) req.session = {};
     req.session.pendingRegistration = getArgs.email;
 
+    httputils.jsonResponse(resp, true);
+
     // let's now kick out a verification email!
     email.sendVerificationEmail(getArgs.email, secret);
   } catch(e) {
@@ -75,6 +81,13 @@ exports.stage_user = function(req, resp) {
 exports.registration_status = function(req, resp) {
   logRequest("registration_status", req.session);
 
+  if (!req.session || !typeof req.session.pendingRegistration == 'string') {
+    httputils.badRequest(
+      resp,
+      "api abuse: registration_status called without a pending email for registration");
+    return;
+  }
+
   var email = req.session.pendingRegistration;
   db.emailKnown(email, function(known) {
     if (known) {
@@ -96,7 +109,10 @@ exports.authenticate_user = function(req, resp) {
   if (!checkParams(getArgs, resp, [ "email", "pass" ])) return;
 
   db.checkAuth(getArgs.email, getArgs.pass, function(rv) {
-    if (rv) req.session.authenticatedUser = getArgs.email;
+    if (rv) {
+      if (!req.session) req.session = {}; 
+      req.session.authenticatedUser = getArgs.email;
+    }
     httputils.jsonResponse(resp, rv);
   });
 };
@@ -107,7 +123,7 @@ exports.add_email = function (req, resp) {
 
   if (!checkParams(getArgs, resp, [ "email", "pubkey" ])) return;
 
-  if (!isAuthed(req, resp)) return;
+  if (!checkAuthed(req, resp)) return;
 
   logRequest("add_email", getArgs);
 
@@ -115,11 +131,12 @@ exports.add_email = function (req, resp) {
     // upon success, stage_user returns a secret (that'll get baked into a url
     // and given to the user), on failure it throws
     var secret = db.stageEmail(req.session.authenticatedUser, getArgs.email, getArgs.pubkey);
-    httputils.jsonResponse(resp, true);
 
     // store the email being registered in the session data
     req.session.pendingRegistration = getArgs.email;
 
+    httputils.jsonResponse(resp, true);
+
     // let's now kick out a verification email!
     email.sendVerificationEmail(getArgs.email, secret);
   } catch(e) {
@@ -132,7 +149,7 @@ exports.set_key = function (req, resp) {
   var urlobj = url.parse(req.url, true);
   var getArgs = urlobj.query;
   if (!checkParams(getArgs, resp, [ "email", "pubkey" ])) return;
-  if (!isAuthed(req, resp)) return;
+  if (!checkAuthed(req, resp)) return;
   logRequest("set_key", getArgs);
   db.addKeyToEmail(req.session.authenticatedUser, getArgs.email, getArgs.pubkey, function (rv) {
     httputils.jsonResponse(resp, rv);
@@ -145,7 +162,7 @@ exports.am_authed = function(req,resp) {
 };
 
 exports.sync_emails = function(req,resp) {
-  if (!isAuthed(req, resp)) return;
+  if (!checkAuthed(req, resp)) return;
 
   var requestBody = "";
   req.on('data', function(str) {
diff --git a/authority/static/dialog/main.js b/authority/static/dialog/main.js
index 0357e73de89e07e046b5d08838c826f6406048dc..0d6058fbf267cb27f7da9a57cc51aebd7a8b27e3 100644
--- a/authority/static/dialog/main.js
+++ b/authority/static/dialog/main.js
@@ -587,18 +587,23 @@
       window.localStorage.emails = JSON.stringify({});
     }
 
+    function onsuccess(rv) {
+      trans.complete(rv);
+    }
+    function onerror(error) {
+      errorOut(trans, error);
+    }
+
+    // wherever shall we start?
     if (haveIDs) {
-      runSignInDialog(function(rv) {
-        trans.complete(rv);
-      }, function(error) {
-        errorOut(trans, error);
-      });
+      runSignInDialog(onsuccess, onerror);
     } else {
-      runAuthenticateDialog(undefined, function(rv) {
-        trans.complete(rv);
-      }, function(error) {
-        errorOut(trans, error);
-      });
+      // do we even need to authenticate?
+      checkAuthStatus(function() {
+        syncIdentities(onsuccess, onerror);
+      }, function() {
+        runAuthenticateDialog(onsuccess, onerror);
+      }, onsuccess, onerror);
     }
   });
 
diff --git a/node_modules/README.md b/node_modules/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..71a00cdf9da41b8a06c9f41f69302afd3e5e86cc
--- /dev/null
+++ b/node_modules/README.md
@@ -0,0 +1,5 @@
+Here live first and third party re-usable modules for node.
+
+* cookie-sessions - encrypted cookie based session storage (will a less stateful
+  server make), forked and improved, thus included inline here.  Original work:
+  https://github.com/caolan/cookie-sessions
diff --git a/node_modules/cookie-sessions/.gitmodules b/node_modules/cookie-sessions/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..3b655320c9768cf0a1db8bfc65e3cc4ef6b685ff
--- /dev/null
+++ b/node_modules/cookie-sessions/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "deps/nodeunit"]
+	path = deps/nodeunit
+	url = git://github.com/caolan/nodeunit.git
diff --git a/node_modules/cookie-sessions/LICENSE b/node_modules/cookie-sessions/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..b7f9d5001c0b8d12720085c7982321910515cbe3
--- /dev/null
+++ b/node_modules/cookie-sessions/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Caolan McMahon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/cookie-sessions/README.md b/node_modules/cookie-sessions/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9ba215e563011a19f63615f283b93dce8e50e51b
--- /dev/null
+++ b/node_modules/cookie-sessions/README.md
@@ -0,0 +1,47 @@
+# Cookie-Sessions
+
+Secure cookie-based session middleware for
+[Connect](http://github.com/senchalabs/connect). This is a new module and I
+wouldn't recommend for production use just yet.
+
+Session data is stored on the request object in the 'session' property:
+
+    var connect = require('connect'),
+        sessions = require('cookie-sessions');
+
+    Connect.createServer(
+        sessions({secret: '123abc'}),
+        function(req, res, next){
+            req.session = {'hello':'world'};
+            res.writeHead(200, {'Content-Type':'text/plain'});
+            res.end('session data updated');
+        }
+    ).listen(8080);
+
+The session data is JSON.stringified, encrypted and timestamped, then a HMAC
+signature is attached to test for tampering. The main function accepts a
+number of options:
+
+    * secret -- The secret to encrypt the session data with
+    * timeout -- The amount of time in miliseconds before the cookie expires
+      (default: 24 hours)
+    * session_key -- The cookie key name to store the session data in
+      (default: _node)
+
+
+## Why store session data in cookies?
+
+* Its fast, you don't need to hit the filesystem or a database to look up
+  session data
+* It scales easily. You don't need to worry about sticky-sessions when
+  load-balancing across multiple nodes.
+* No server-side persistence requirements
+
+## Caveats
+
+* You can only store 4k of data in a cookie
+* Higher-bandwidth requirements, since the cookie is sent to the server with
+  every request.
+
+__In summary:__ don't use cookie storage if you keep a lot of data in your
+sessions!
diff --git a/node_modules/cookie-sessions/index.js b/node_modules/cookie-sessions/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..888d419abbb0493bb583c72c2d27d9d2f2c4bbcb
--- /dev/null
+++ b/node_modules/cookie-sessions/index.js
@@ -0,0 +1,3 @@
+// This file is just added for convenience so this repository can be
+// directly checked out into a project's deps folder
+module.exports = require('./lib/cookie-sessions');
diff --git a/node_modules/cookie-sessions/lib/cookie-sessions.js b/node_modules/cookie-sessions/lib/cookie-sessions.js
new file mode 100644
index 0000000000000000000000000000000000000000..68b515a9c8bce9866610beec5d3938c5143de723
--- /dev/null
+++ b/node_modules/cookie-sessions/lib/cookie-sessions.js
@@ -0,0 +1,218 @@
+var crypto = require('crypto');
+var url = require('url');
+
+var exports = module.exports = function(settings){
+
+    var default_settings = {
+        // don't set a default cookie secret, must be explicitly defined
+        session_key: '_node',
+        timeout: 1000 * 60 * 60 * 24, // 24 hours
+        path: '/'
+    };
+    var s = extend(default_settings, settings);
+    if(!s.secret) throw new Error('No secret set in cookie-session settings');
+
+    if(typeof s.path !== 'string' || s.path.indexOf('/') != 0)
+        throw new Error('invalid cookie path, must start with "/"');
+
+    return function(req, res, next){
+        // if the request is not under the specified path, do nothing.
+        if (url.parse(req.url).pathname.indexOf(s.path) != 0) {
+            next();
+            return;
+        }
+
+        // Read session data from a request and store it in req.session
+        req.session = exports.readSession(
+            s.session_key, s.secret, s.timeout, req);
+
+        // proxy writeHead to add cookie to response
+        var _writeHead = res.writeHead;
+        res.writeHead = function(statusCode){
+
+            var reasonPhrase, headers;
+            if (typeof arguments[1] === 'string') {
+                reasonPhrase = arguments[1];
+                headers = arguments[2] || {};
+            }
+            else {
+                headers = arguments[1] || {};
+            }
+
+            // Add a Set-Cookie header to all responses with the session data
+            // and the current timestamp. The cookie needs to be set on every
+            // response so that the timestamp is up to date, and the session
+            // does not expire unless the user is inactive.
+
+            var cookiestr;
+            if (req.session === undefined) {
+                if ("cookie" in req.headers) {
+                    cookiestr = escape(s.session_key) + '='
+                        + '; expires=' + exports.expires(0)
+                        + '; path=' + s.path;
+                }
+            } else {
+                cookiestr = escape(s.session_key) + '='
+                    + escape(exports.serialize(s.secret, req.session))
+                    + '; expires=' + exports.expires(s.timeout)
+                    + '; path=' + s.path;
+            }
+            
+            if (cookiestr !== undefined) {
+                if(Array.isArray(headers)) headers.push(['Set-Cookie', cookiestr]);
+                else {
+                    // if a Set-Cookie header already exists, convert headers to
+                    // array so we can send multiple Set-Cookie headers.
+                    if(headers['Set-Cookie'] !== undefined){
+                        headers = exports.headersToArray(headers);
+                        headers.push(['Set-Cookie', cookiestr]);
+                    }
+                    // if no Set-Cookie header exists, leave the headers as an
+                    // object, and add a Set-Cookie property
+                    else {
+                        headers['Set-Cookie'] = cookiestr;
+                    }
+                }
+            }
+
+            var args = [statusCode, reasonPhrase, headers];
+            if (!args[1]) {
+                args.splice(1, 1);
+            }
+            // call the original writeHead on the request
+            return _writeHead.apply(res, args);
+        }
+        next();
+
+    };
+};
+
+exports.headersToArray = function(headers){
+    if(Array.isArray(headers)) return headers;
+    return Object.keys(headers).reduce(function(arr, k){
+        arr.push([k, headers[k]]);
+        return arr;
+    }, []);
+};
+
+
+// Extend a given object with all the properties in passed-in object(s).
+// From underscore.js (http://documentcloud.github.com/underscore/)
+function extend(obj) {
+    Array.prototype.slice.call(arguments).forEach(function(source) {
+      for (var prop in source) obj[prop] = source[prop];
+    });
+    return obj;
+};
+
+exports.deserialize = function(secret, timeout, str){
+    // Parses a secure cookie string, returning the object stored within it.
+    // Throws an exception if the secure cookie string does not validate.
+
+    if(!exports.valid(secret, timeout, str)){
+        throw new Error('invalid cookie');
+    }
+    var data = exports.decrypt(secret, exports.split(str).data_blob);
+    return JSON.parse(data);
+};
+
+exports.serialize = function(secret, data){
+    // Turns a JSON-compatibile object literal into a secure cookie string
+
+    var data_str = JSON.stringify(data);
+    var data_enc = exports.encrypt(secret, data_str);
+    var timestamp = (new Date()).getTime();
+    var hmac_sig = exports.hmac_signature(secret, timestamp, data_enc);
+    var result = hmac_sig + timestamp + data_enc;
+    if(!exports.checkLength(result)){
+        throw new Error('data too long to store in a cookie');
+    }
+    return result;
+};
+
+exports.split = function(str){
+    // Splits a cookie string into hmac signature, timestamp and data blob.
+    return {
+        hmac_signature: str.slice(0,40),
+        timestamp: parseInt(str.slice(40, 53), 10),
+        data_blob: str.slice(53)
+    };
+};
+
+exports.hmac_signature = function(secret, timestamp, data){
+    // Generates a HMAC for the timestamped data, returning the
+    // hex digest for the signature.
+    var hmac = crypto.createHmac('sha1', secret);
+    hmac.update(timestamp + data);
+    return hmac.digest('hex');
+};
+
+exports.valid = function(secret, timeout, str){
+    // Tests the validity of a cookie string. Returns true if the HMAC
+    // signature of the secret, timestamp and data blob matches the HMAC in the
+    // cookie string, and the cookie's age is less than the timeout value.
+
+    var parts = exports.split(str);
+    var hmac_sig = exports.hmac_signature(
+        secret, parts.timestamp, parts.data_blob
+    );
+    return (
+        parts.hmac_signature === hmac_sig &&
+        parts.timestamp + timeout > new Date().getTime()
+    );
+};
+
+exports.decrypt = function(secret, str){
+    // Decrypt the aes192 encoded str using secret.
+    var decipher = crypto.createDecipher("aes192", secret);
+    return decipher.update(str, 'hex', 'utf8') + decipher.final('utf8');
+};
+
+exports.encrypt = function(secret, str){
+    // Encrypt the str with aes192 using secret.
+    var cipher = crypto.createCipher("aes192", secret);
+    return cipher.update(str, 'utf8', 'hex') + cipher.final('hex');
+};
+
+exports.checkLength = function(str){
+    // Test if a string is within the maximum length allowed for a cookie.
+    return str.length <= 4096;
+};
+
+exports.readCookies = function(req){
+    // if "cookieDecoder" is in use, then req.cookies
+    // will already contain the parsed cookies
+    if (req.cookies) {
+        return req.cookies;
+    }
+    else {
+        // Extracts the cookies from a request object.
+        var cookie = req.headers.cookie;
+        if(!cookie){
+            return {};
+        }
+        var parts = cookie.split(/\s*;\s*/g).map(function(x){
+            return x.split('=');
+        });
+        return parts.reduce(function(a, x){
+            a[unescape(x[0])] = unescape(x[1]);
+            return a;
+        }, {});
+    }
+};
+
+exports.readSession = function(key, secret, timeout, req){
+    // Reads the session data stored in the cookie named 'key' if it validates,
+    // otherwise returns an empty object.
+
+    var cookies = exports.readCookies(req);
+    if(cookies[key]){
+        return exports.deserialize(secret, timeout, cookies[key]);
+    }
+    return undefined;
+};
+
+
+exports.expires = function(timeout){
+    return (new Date(new Date().getTime() + (timeout))).toUTCString();
+};
diff --git a/node_modules/cookie-sessions/package.json b/node_modules/cookie-sessions/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..374ff1a76ab790df27e89cad8094950e93a6001b
--- /dev/null
+++ b/node_modules/cookie-sessions/package.json
@@ -0,0 +1,16 @@
+{ "name": "cookie-sessions"
+, "description": "Secure cookie-based session middleware for Connect"
+, "main": "./index"
+, "author": "Caolan McMahon"
+, "version": "0.0.3"
+, "repository" :
+  { "type" : "git"
+  , "url" : "http://github.com/caolan/cookie-sessions.git"
+  }
+, "bugs" : { "web" : "http://github.com/caolan/cookie-sessions/issues" }
+, "licenses" :
+  [ { "type" : "MIT"
+    , "url" : "http://github.com/caolan/cookie-sessions/raw/master/LICENSE"
+    }
+  ]
+}
diff --git a/node_modules/cookie-sessions/test.js b/node_modules/cookie-sessions/test.js
new file mode 100755
index 0000000000000000000000000000000000000000..f41d154e3cf5dca054783c3abdb1588184768756
--- /dev/null
+++ b/node_modules/cookie-sessions/test.js
@@ -0,0 +1,22 @@
+#!/usr/local/bin/node
+
+require.paths.push(__dirname);
+require.paths.push(__dirname + '/deps');
+require.paths.push(__dirname + '/lib');
+
+try {
+    var testrunner = require('nodeunit').testrunner;
+}
+catch(e) {
+    var sys = require('sys');
+    sys.puts("Cannot find nodeunit module.");
+    sys.puts("You can download submodules for this project by doing:");
+    sys.puts("");
+    sys.puts("    git submodule init");
+    sys.puts("    git submodule update");
+    sys.puts("");
+    process.exit();
+}
+
+process.chdir(__dirname);
+testrunner.run(['test']);
diff --git a/node_modules/cookie-sessions/test/test-cookie-sessions.js b/node_modules/cookie-sessions/test/test-cookie-sessions.js
new file mode 100644
index 0000000000000000000000000000000000000000..432a8ad93dc42740d377392706bd0fc75b2e4eb5
--- /dev/null
+++ b/node_modules/cookie-sessions/test/test-cookie-sessions.js
@@ -0,0 +1,620 @@
+var sessions = require('cookie-sessions');
+
+
+exports['split'] = function(test){
+    var hmac_sig = 'c82d1eacb4adb15a3250a6df7c8f190b586ab6b9',
+        timestamp = 1264710287440,
+        data_blob = 'somedata';
+
+    var serialized_cookie = hmac_sig + timestamp + data_blob;
+    test.same(
+        sessions.split(serialized_cookie),
+        {hmac_signature: hmac_sig, timestamp: timestamp, data_blob: data_blob},
+        'split correctly seperates sig, timestamp and data blob'
+    );
+    test.done();
+};
+
+exports['valid'] = function(test){
+    var secret = 'secret';
+        current_valid_sig = '5eaaa22480acefd8b18d67bb194573dc1b75d9db',
+        expired_valid_sig = '9c7ad86913ceeced1f6f249ba52868006c8dfdab',
+        invalid_sig = '51a2a32485a6e7d8b9810711112513d14b15d16b',
+        expired_timestamp = 1264700000000,
+        current_timestamp = 1264710287440,
+        session_timeout = 54000,
+        data_blob = 'somedata';
+
+    var Date_copy = global.Date;
+    global.Date = function(){this.getTime = function(){return 1264710287000}};
+
+    test.ok(
+        sessions.valid(
+            secret, session_timeout,
+            current_valid_sig + current_timestamp + data_blob
+        ) === true,
+        'returns true for valid hmac sig within timeout'
+    );
+    test.ok(
+        sessions.valid(
+            secret, session_timeout,
+            expired_valid_sig + expired_timestamp + data_blob
+        ) === false,
+        'returns false for valid hmac sig past timeout'
+    );
+    test.ok(
+        sessions.valid(
+            secret, session_timeout,
+            invalid_sig + current_timestamp + data_blob
+        ) === false,
+        'returns false for invalid hmac sig within timeout'
+    );
+    test.ok(
+        sessions.valid(
+            secret, session_timeout,
+            invalid_sig + expired_timestamp + data_blob
+        ) === false,
+        'returns false for invalid hmac sig past timeout'
+    );
+
+    // restore Date
+    global.Date = Date_copy;
+    test.done();
+};
+
+exports['decrypt'] = function(test){
+    var r = sessions.decrypt(
+        'secret', '686734eb9e0fff9adea53983210825ef'
+    );
+    test.same(r, 'somedata', 'decrypt sucessfully returns decrypted data');
+    test.done();
+};
+
+exports['encrypt'] = function(test){
+    var r = sessions.encrypt('secret', 'somedata');
+    test.same(
+        r, '686734eb9e0fff9adea53983210825ef',
+        'encrypt sucessfully returns encrypted data'
+    );
+    test.done();
+};
+
+exports['deserialize valid cookie'] = function(test){
+    test.expect(8);
+    // copy some functions
+    var valid = sessions.valid;
+    var decrypt = sessions.decrypt;
+    var parse = JSON.parse;
+
+    sessions.valid = function(secret, timeout, str){
+        test.equals(secret, 'secret', 'valid called with secret');
+        test.equals(timeout, 123, 'valid called with timeout');
+        test.equals(str, 'cookiestring', 'valid called with cookie string');
+        return true;
+    };
+    sessions.split = function(str){
+        test.equals(str, 'cookiestring', 'split called with cookie string');
+        return {data_blob: 'datastr'};
+    };
+    sessions.decrypt = function(secret, str){
+        test.equals(secret, 'secret', 'decrypt called with secret');
+        test.equals(str, 'datastr', 'decrypt called with data string');
+        return 'decrypted_data';
+    };
+    JSON.parse = function(str){
+        test.equals(
+            str, 'decrypted_data', 'JSON.parse called with decrypted data'
+        );
+        return {test:'test'};
+    };
+    var r = sessions.deserialize('secret', 123, 'cookiestring');
+    test.same(r, {test:'test'}, 'deserialize returns parsed json data');
+
+    // restore copied functions:
+    sessions.valid = valid;
+    sessions.decrypt = decrypt;
+    JSON.parse = parse;
+    test.done();
+};
+
+exports['deserialize invalid cookie'] = function(test){
+    test.expect(1);
+    // copy some functions
+    var valid = sessions.valid;
+    var decrypt = sessions.decrypt;
+    var parse = JSON.parse;
+
+    sessions.valid = function(secret, timeout, str){
+        return false;
+    };
+    sessions.decrypt = function(secret, str){
+        test.ok(false, 'should not attempt to decrypt invalid cookie');
+    };
+    JSON.parse = function(str){
+        test.ok(false, 'should not attempt to parse invalid cookie');
+    };
+    try {
+        sessions.deserialize('secret', 123, 'cookiestring');
+    }
+    catch(e){
+        test.ok(true, 'throw exception on invalid cookie');
+    }
+
+    // restore copied functions:
+    sessions.valid = valid;
+    sessions.decrypt = decrypt;
+    JSON.parse = parse;
+    test.done();
+};
+
+exports['serialize'] = function(test){
+    test.expect(7);
+    // copy some functions
+    var encrypt = sessions.encrypt;
+    var hmac_signature = sessions.hmac_signature;
+    var stringify= JSON.stringify;
+    var Date_copy = global.Date;
+
+    global.Date = function(){this.getTime = function(){return 1234;}};
+    JSON.stringify = function(obj){
+        test.same(
+            obj, {test:'test'}, 'JSON.stringify called with cookie data obj'
+        );
+        return 'data';
+    };
+    sessions.encrypt = function(secret, str){
+        test.equals(secret, 'secret', 'encrypt called with secret');
+        test.equals(str, 'data', 'encrypt called with stringified data');
+        return 'encrypted_data';
+    };
+    sessions.hmac_signature = function(secret, timestamp, data_str){
+        test.equals(secret, 'secret', 'hmac_signature called with secret');
+        test.equals(timestamp, 1234, 'hmac_signature called with timestamp');
+        test.equals(
+            data_str, 'encrypted_data',
+            'hmac_signature called with encrypted data string'
+        );
+        return 'hmac';
+    };
+    var r = sessions.serialize('secret', {test:'test'});
+    test.equals(
+        r, 'hmac1234encrypted_data', 'serialize returns correct string'
+    );
+
+    // restore copied functions:
+    sessions.encrypt = encrypt;
+    sessions.hmac_signature = hmac_signature;
+    JSON.stringify = stringify;
+    global.Date = Date_copy;
+    test.done();
+};
+
+exports['serialize data over 4096 chars'] = function(test){
+    test.expect(1);
+    // copy some functions
+    var encrypt = sessions.encrypt;
+    var hmac_signature = sessions.hmac_signature;
+    var stringify= JSON.stringify;
+    var Date_copy = global.Date;
+
+    global.Date = function(){this.getTime = function(){return 1234;}};
+    JSON.stringify = function(obj){
+        return 'data';
+    };
+    sessions.encrypt = function(secret, str){
+        // lets make this too long!
+        var r = '';
+        for(var i=0; i<4089; i++){
+            r = r + 'x';
+        };
+        return r;
+    };
+    sessions.hmac_signature = function(secret, timestamp, data_str){
+        return 'hmac';
+    };
+    try {
+        var r = sessions.serialize('secret', {test:'test'});
+    }
+    catch(e){
+        test.ok(
+            true, 'serializing a cookie over 4096 chars throws an exception'
+        );
+    }
+
+    // restore copied functions:
+    sessions.encrypt = encrypt;
+    sessions.hmac_signature = hmac_signature;
+    JSON.stringify = stringify;
+    global.Date = Date_copy;
+    test.done();
+};
+
+exports['readCookies'] = function(test){
+    var req = {headers: {cookie: "name1=data1; test=\"abcXYZ%20123\""}};
+    var r = sessions.readCookies(req);
+    test.same(r, {name1: 'data1', test: '"abcXYZ 123"'}, 'test header read ok');
+    test.done();
+};
+
+exports['readCookies alternate format'] = function(test){
+    var req = {headers: {cookie: "name1=data1;test=\"abcXYZ%20123\""}};
+    var r = sessions.readCookies(req);
+    test.same(r, {name1: 'data1', test: '"abcXYZ 123"'}, 'test header read ok');
+    test.done();
+};
+
+exports['readCookies no cookie in headers'] = function(test){
+    var req = {headers: {}};
+    var r = sessions.readCookies(req);
+    test.same(r, {}, 'returns empty object');
+    test.done();
+};
+
+exports['readCookies from Connect cookieDecoder'] = function(test){
+    var req = {headers: {}, cookies: {'test':'cookie'}};
+    test.same(sessions.readCookies(req), {'test': 'cookie'});
+    test.done();
+};
+
+exports['readSession'] = function(test){
+    test.expect(5);
+    var readCookies = sessions.readCookies;
+    var deserialize = sessions.deserialize;
+
+    sessions.readCookies = function(r){
+        test.equals(r, 'request_obj', 'readCookies called with request object');
+        return {'node_session': 'cookie_data'};
+    };
+    sessions.deserialize = function(secret, timeout, str){
+        test.equals(secret, 'secret', 'readCookies called with secret');
+        test.equals(timeout, 12, 'readCookies called with timeout');
+        test.equals(str, 'cookie_data', 'readCookies called with cookie data');
+        return {test: 'test'};
+    };
+
+    var r = sessions.readSession(
+        'node_session', 'secret', 12, 'request_obj'
+    );
+    test.same(r, {test: 'test'}, 'session with key node_session read ok');
+
+    // restore copied functions
+    sessions.readCookies = readCookies;
+    sessions.deserialize = deserialize;
+    test.done();
+};
+
+exports['readSession no cookie'] = function(test){
+    test.expect(2);
+    var readCookies = sessions.readCookies;
+    var deserialize = sessions.deserialize;
+
+    sessions.readCookies = function(r){
+        test.equals(r, 'request_obj', 'readCookies called with request object');
+        return {};
+    };
+    sessions.deserialize = function(secret, timeout, str){
+        test.ok(false, 'should not call deserialize');
+    };
+
+    var r = sessions.readSession(
+        'node_session', 'secret', 12, 'request_obj'
+    );
+    test.same(r, undefined, 'return empty session');
+
+    // restore copied functions
+    sessions.readCookies = readCookies;
+    sessions.deserialize = deserialize;
+    test.done();
+};
+
+exports['onRequest'] = function(test){
+    test.expect(5);
+    var readSession = sessions.readSession;
+    var s = {
+        session_key:'_node',
+        secret: 'secret',
+        timeout: 86400
+    };
+    var req = {};
+
+    sessions.readSession = function(key, secret, timeout, req){
+        test.equals(key, '_node', 'readSession called with session key');
+        test.equals(secret, 'secret', 'readSession called with secret');
+        test.equals(timeout, 86400, 'readSession called with timeout');
+        return 'testsession';
+    };
+    var next = function(){
+        test.ok(true, 'chain.next called');
+        test.equals(
+            req.session, 'testsession', 'req.session equals session data'
+        );
+    };
+    sessions(s)(req, 'res', next);
+
+    // restore copied functions
+    sessions.readSession = readSession;
+    test.done();
+};
+
+exports['writeHead'] = function(test){
+    test.expect(6);
+
+    var s = {
+        session_key:'_node',
+        secret: 'secret',
+        timeout: 86400
+    };
+    var req = {headers: {cookie: "_node="}};
+    var res = {
+        writeHead: function(code, headers){
+            test.equals(
+                headers['Set-Cookie'],
+                '_node=serialized_session; ' +
+                'expires=expiry_date; ' +
+                'path=/'
+            );
+            test.equals(headers['original'], 'header');
+        }
+    };
+
+    var serialize = sessions.serialize;
+    sessions.serialize = function(secret, data){
+        test.equals(secret, 'secret', 'serialize called with secret');
+        test.same(data, {test:'test'}, 'serialize called with session data');
+        return 'serialized_session';
+    };
+
+    var expires = sessions.expires;
+    sessions.expires = function(timeout){
+        test.equals(timeout, s.timeout);
+        return 'expiry_date';
+    };
+
+    var next = function(){
+        test.ok(true, 'chain.next called');
+        req.session = {test:'test'};
+        res.writeHead(200, {'original':'header'});
+        // restore copied functions
+        sessions.serialize = serialize;
+        sessions.expires = expires;
+        test.done();
+    };
+    sessions(s)(req, res, next);
+};
+
+exports['writeHead doesnt write cookie if none exists and session is undefined'] = function(test){
+    test.expect(3);
+
+    var s = {
+        session_key:'_node',
+        secret: 'secret',
+        timeout: 86400
+    };
+    var req = {headers: {}};
+    var res = {
+        writeHead: function(code, headers){
+            test.ok(!("Set-Cookie" in headers));
+            test.equals(headers['original'], 'header');
+        }
+    };
+
+    var next = function(){
+        test.ok(true, 'chain.next called');
+        req.session = undefined;
+        res.writeHead(200, {'original':'header'});
+        test.done();
+    };
+    sessions(s)(req, res, next);
+};
+
+exports['writeHead writes empty cookie with immediate expiration if session is undefined'] = function(test){
+    test.expect(4);
+
+    var s = {
+        session_key:'_node',
+        secret: 'secret',
+        timeout: 86400
+    };
+    var req = {headers: {cookie: "_node=Blah"}};
+    var res = {
+        writeHead: function(code, headers){
+            test.equals(
+                headers['Set-Cookie'],
+                '_node=; ' +
+                'expires=now; ' +
+                'path=/'
+            );
+            test.equals(headers['original'], 'header');
+        }
+    };
+
+    var expires = sessions.expires;
+    sessions.expires = function(timeout){
+        test.equals(timeout, 0);
+        return 'now';
+    };
+    var readSession = sessions.readSession;
+    sessions.readSession = function(key, secret, timeout, req) {
+        return {"username": "Bob"};
+    };
+
+    var next = function(){
+        test.ok(true, 'chain.next called');
+        req.session = undefined;
+        res.writeHead(200, {'original':'header'});
+        // restore copied functions
+        sessions.expires = expires;
+        sessions.readSession = readSession;
+        test.done();
+    };
+    sessions(s)(req, res, next);
+};
+
+exports['onInit secret set'] = function(test){
+    test.expect(0);
+    var s = {secret: 'secret'};
+    try {
+        sessions({secret: 'secret'});
+    }
+    catch(e){
+        test.ok(false, 'do nothing if secret set in server settings');
+    }
+    test.done();
+};
+
+exports['onInit no secret set'] = function(test){
+    test.expect(1);
+    try {
+        sessions({});
+    }
+    catch(e){
+        test.ok(true, 'throw exception if no secret set in server settings');
+    }
+    test.done();
+};
+
+exports['set multiple cookies'] = function(test){
+    test.expect(3);
+    var _serialize = sessions.serialize;
+    sessions.serialize = function(){
+        return 'session_data';
+    };
+
+    var _expires = sessions.expires;
+    sessions.expires = function(timeout){
+        test.equals(timeout, 12345);
+        return 'expiry_date';
+    };
+
+    var req = {headers: {cookie:''}};
+    var res = {writeHead: function(statusCode, headers){
+        test.equals(statusCode, 200);
+        test.same(headers, [
+            ['other_header', 'val'],
+            ['Set-Cookie', 'testcookie=testvalue'],
+            ['Set-Cookie', '_node=session_data; ' +
+                           'expires=expiry_date; ' +
+                           'path=/']
+        ]);
+        sessions.serialize = _serialize;
+        sessions.expires = _expires;
+        test.done();
+    }};
+
+    sessions({secret: 'secret', timeout: 12345})(req, res, function(){
+        req.session = {test: 'test'};
+        res.writeHead(200, {
+            'other_header': 'val',
+            'Set-Cookie':'testcookie=testvalue'
+        });
+    });
+};
+
+exports['set single cookie'] = function(test){
+    test.expect(3);
+    var _serialize = sessions.serialize;
+    sessions.serialize = function(){
+        return 'session_data';
+    };
+
+    var _expires = sessions.expires;
+    sessions.expires = function(timeout){
+        test.equals(timeout, 12345);
+        return 'expiry_date';
+    };
+
+    var req = {headers: {cookie:''}};
+    var res = {writeHead: function(statusCode, headers){
+        test.equals(statusCode, 200);
+        test.same(headers, {
+            'other_header': 'val',
+            'Set-Cookie': '_node=session_data; ' +
+                          'expires=expiry_date; ' +
+                          'path=/'
+        });
+        sessions.serialize = _serialize;
+        sessions.expires = _expires;
+        test.done();
+    }};
+    sessions({secret: 'secret', timeout: 12345})(req, res, function(){
+        req.session = {test: 'test'};
+        res.writeHead(200, {'other_header': 'val'});
+    });
+};
+
+exports['handle headers as array'] = function(test){
+    test.expect(3);
+    var _serialize = sessions.serialize;
+    sessions.serialize = function(){
+        return 'session_data';
+    };
+
+    var _expires = sessions.expires;
+    sessions.expires = function(timeout){
+        test.equals(timeout, 12345);
+        return 'expiry_date';
+    };
+
+    var req = {headers: {cookie:''}};
+    var res = {writeHead: function(statusCode, headers){
+        test.equals(statusCode, 200);
+        test.same(headers, [
+            ['header1', 'val1'],
+            ['header2', 'val2'],
+            ['Set-Cookie', '_node=session_data; ' +
+                           'expires=expiry_date; ' +
+                           'path=/']
+        ]);
+        sessions.serialize = _serialize;
+        test.done();
+    }};
+    sessions({secret: 'secret', timeout: 12345})(req, res, function(){
+        req.session = {test: 'test'};
+        res.writeHead(200, [['header1', 'val1'],['header2', 'val2']]);
+    });
+};
+
+exports['convert headers to array'] = function(test){
+    test.same(
+        sessions.headersToArray({'key1':'val1','key2':'val2'}),
+        [['key1','val1'],['key2','val2']]
+    );
+    test.same(
+        sessions.headersToArray([['key1','val1'],['key2','val2']]),
+        [['key1','val1'],['key2','val2']]
+    );
+    test.done();
+};
+
+exports['send cookies even if there are no headers'] = function (test) {
+    test.expect(2);
+    var req = {headers: {cookie:''}};
+    var res = {
+        writeHead: function (code, headers) {
+            test.equal(code, 200);
+            test.ok(headers['Set-Cookie']);
+            test.done();
+        }
+    };
+    sessions({secret: 'secret', timeout: 12345})(req, res, function () {
+        req.session = {test: 'test'};
+        res.writeHead(200);
+    });
+};
+
+exports['send cookies when no headers but reason_phrase'] = function (test) {
+    test.expect(3);
+    var req = {headers: {cookie:''}};
+    var res = {
+        writeHead: function (code, reason_phrase, headers) {
+            test.equal(code, 200);
+            test.equal(reason_phrase, 'reason');
+            test.ok(headers['Set-Cookie']);
+            test.done();
+        }
+    };
+    sessions({secret: 'secret', timeout: 12345})(req, res, function () {
+        req.session = {test: 'test'};
+        res.writeHead(200, 'reason');
+    });
+};