WIP: start moving web sessions to cluster

This commit is contained in:
Shane Kilkelly
2016-11-08 15:32:36 +00:00
parent 0b50623d51
commit bfa0e7cf89
7 changed files with 198 additions and 105 deletions
@@ -32,21 +32,27 @@ module.exports = AuthenticationController =
afterLoginSessionSetup: (req, user, callback=(err)->) ->
req.login user, (err) ->
if err?
logger.err {user_id: user._id, err}, "error from req.login"
return callback(err)
# Regenerate the session to get a new sessionID (cookie value) to
# protect against session fixation attacks
oldSession = req.session
req.session.destroy()
req.sessionStore.generate(req)
for key, value of oldSession
req.session[key] = value
# copy to the old `session.user` location, for backward-comptability
req.session.user = req.session.passport.user
req.session.save (err) ->
req.session.destroy (err) ->
if err?
logger.err {user_id: user._id}, "error saving regenerated session after login"
logger.err {user_id: user._id, err}, "error when trying to destroy old session"
return callback(err)
UserSessionsManager.trackSession(user, req.sessionID, () ->)
callback(null)
req.sessionStore.generate(req)
for key, value of oldSession
req.session[key] = value
# copy to the old `session.user` location, for backward-comptability
req.session.user = req.session.passport.user
req.session.save (err) ->
if err?
logger.err {user_id: user._id}, "error saving regenerated session after login"
return callback(err)
UserSessionsManager.trackSession(user, req.sessionID, () ->)
callback(null)
passportLogin: (req, res, next) ->
# This function is middleware which wraps the passport.authenticate middleware,
@@ -3,13 +3,15 @@ redis = require('redis-sharelatex')
logger = require("logger-sharelatex")
Async = require('async')
_ = require('underscore')
UserSessionsRedis = require('./UserSessionsRedis')
rclient = redis.createClient(Settings.redis.web)
# rclient = redis.createClient(Settings.redis.web)
rclient = UserSessionsRedis.client()
module.exports = UserSessionsManager =
_sessionSetKey: (user) ->
return "UserSessions:#{user._id}"
return "UserSessions:{#{user._id}}"
# mimic the key used by the express sessions
_sessionKey: (sessionId) ->
@@ -0,0 +1,18 @@
Settings = require 'settings-sharelatex'
redis = require 'redis-sharelatex'
ioredis = require 'ioredis'
logger = require 'logger-sharelatex'
module.exports = Redis =
client: () ->
redisSessionsSettings = Settings.redis.websessions or Settings.redis.web
if redisSessionsSettings?.cluster?
logger.log {}, "using redis cluster for web sessions"
rclient = new ioredis.Cluster(redisSessionsSettings.cluster)
else
rclient = redis.createClient(redisSessionsSettings)
return rclient
@@ -8,7 +8,9 @@ expressLocals = require('./ExpressLocals')
Router = require('../router')
metrics.inc("startup")
redis = require("redis-sharelatex")
rclient = redis.createClient(Settings.redis.web)
UserSessionsRedis = require('../Features/User/UserSessionsRedis')
sessionsRedisClient = UserSessionsRedis.client()
session = require("express-session")
RedisStore = require('connect-redis')(session)
@@ -19,7 +21,8 @@ csrf = require('csurf')
csrfProtection = csrf()
cookieParser = require('cookie-parser')
sessionStore = new RedisStore(client:rclient)
# Init the session store
sessionStore = new RedisStore(client:sessionsRedisClient)
passport = require('passport')
LocalStrategy = require('passport-local').Strategy
+32 -24
View File
@@ -38,6 +38,16 @@ module.exports = settings =
port: "6379"
password: ""
# websessions:
# cluster: [
# {host: 'localhost', port: 7000}
# {host: 'localhost', port: 7001}
# {host: 'localhost', port: 7002}
# {host: 'localhost', port: 7003}
# {host: 'localhost', port: 7004}
# {host: 'localhost', port: 7005}
# ]
api:
host: "localhost"
port: "6379"
@@ -107,8 +117,8 @@ module.exports = settings =
# references:
# url: "http://localhost:3040"
notifications:
url: "http://localhost:3042"
url: "http://localhost:3042"
templates:
user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"
showSocialButtons: false
@@ -131,13 +141,13 @@ module.exports = settings =
# this is only used if cookies are used for clsi backend
#clsiCookieKey: "clsiserver"
# Same, but with http auth credentials.
httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000'
maxEntitiesPerProject: 2000
# Security
# --------
security:
@@ -166,12 +176,12 @@ module.exports = settings =
price: 0
features: defaultFeatures
}]
enableSubscriptions:false
# i18n
# ------
#
#
i18n:
subdomainLang:
www: {lngCode:"en", url: siteUrl}
@@ -180,7 +190,7 @@ module.exports = settings =
# Spelling languages
# ------------------
#
# You must have the corresponding aspell package installed to
# You must have the corresponding aspell package installed to
# be able to use a language.
languages: [
{name: "English", code: "en"},
@@ -228,7 +238,7 @@ module.exports = settings =
# analytics:
# ga:
# token: ""
#
#
# ShareLaTeX's help desk is provided by tenderapp.com
# tenderUrl: ""
#
@@ -262,10 +272,10 @@ module.exports = settings =
# then set this to true to allow it to correctly detect the forwarded IP
# address and http/https protocol information.
behindProxy: false
# Cookie max age (in milliseconds). Set to false for a browser session.
cookieSessionLength: 5 * 24 * 60 * 60 * 1000 # 5 days
# Should we allow access to any page without logging in? This includes
# public projects, /learn, /templates, about pages, etc.
allowPublicAccess: if process.env["SHARELATEX_ALLOW_PUBLIC_ACCESS"] == 'true' then true else false
@@ -273,10 +283,10 @@ module.exports = settings =
# Use a single compile directory for all users in a project
# (otherwise each user has their own directory)
# disablePerUserCompiles: true
# Maximum size of text documents in the real-time editing system.
max_doc_length: 2 * 1024 * 1024 # 2mb
# Internal configs
# ----------------
path:
@@ -285,11 +295,11 @@ module.exports = settings =
# them to disk here).
dumpFolder: Path.resolve __dirname + "/../data/dumpFolder"
uploadFolder: Path.resolve __dirname + "/../data/uploads"
# Automatic Snapshots
# -------------------
automaticSnapshots:
# How long should we wait after the user last edited to
# How long should we wait after the user last edited to
# take a snapshot?
waitTimeAfterLastEdit: 5 * minutes
# Even if edits are still taking place, this is maximum
@@ -305,13 +315,13 @@ module.exports = settings =
# user: ""
# password: ""
# projectId: ""
appName: "ShareLaTeX (Community Edition)"
adminEmail: "placeholder@example.com"
nav:
title: "ShareLaTeX Community Edition"
left_footer: [{
text: "Powered by <a href='https://www.sharelatex.com'>ShareLaTeX</a> © 2016"
}]
@@ -377,11 +387,11 @@ module.exports = settings =
"/templates/index": "/templates/"
proxyUrls: {}
reloadModuleViewsOnEachRequest: true
domainLicences: [
]
sixpack:
@@ -390,12 +400,12 @@ module.exports = settings =
# ----------
# LDAP
# ----------
# Settings below use a working LDAP test server kindly provided by forumsys.com
# When testing with forumsys.com use username = einstein and password = password
# ldap :
# host: 'ldap://ldap.forumsys.com'
# dn: 'uid=:userKey,dc=example,dc=com'
@@ -406,13 +416,13 @@ module.exports = settings =
# placeholder: 'email@example.com'
# emailAtt: 'mail'
# anonymous: false
# adminDN: 'cn=read-only-admin,dc=example,dc=com'
# adminDN: 'cn=read-only-admin,dc=example,dc=com'
# adminPW: 'password'
# starttls: true
# tlsOptions:
# rejectUnauthorized: false
# ca: ['/etc/ldap/ca_certs.pem']
#templateLinks: [{
# name : "CV projects",
# url : "/templates/cv"
@@ -420,5 +430,3 @@ module.exports = settings =
# name : "all projects",
# url: "/templates/all"
#}]
+120 -65
View File
@@ -274,12 +274,12 @@
"dependencies": {
"bytes": {
"version": "2.4.0",
"from": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz",
"from": "bytes@2.4.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz"
},
"content-type": {
"version": "1.0.2",
"from": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
"from": "content-type@~1.0.1",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
},
"debug": {
@@ -328,7 +328,7 @@
},
"on-finished": {
"version": "2.3.0",
"from": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"from": "on-finished@~2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"dependencies": {
"ee-first": {
@@ -345,7 +345,7 @@
},
"raw-body": {
"version": "2.1.7",
"from": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz",
"from": "raw-body@~2.1.5",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz",
"dependencies": {
"unpipe": {
@@ -357,7 +357,7 @@
},
"type-is": {
"version": "1.6.13",
"from": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
"from": "type-is@~1.6.10",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
"dependencies": {
"media-typer": {
@@ -387,26 +387,41 @@
"resolved": "https://registry.npmjs.org/bufferedstream/-/bufferedstream-1.6.0.tgz"
},
"connect-redis": {
"version": "2.3.0",
"from": "https://registry.npmjs.org/connect-redis/-/connect-redis-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-2.3.0.tgz",
"version": "3.1.0",
"from": "connect-redis@3.1.0",
"dependencies": {
"debug": {
"version": "1.0.4",
"from": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
"resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
"version": "2.3.0",
"from": "debug@^2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz",
"dependencies": {
"ms": {
"version": "0.6.2",
"from": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
"version": "0.7.2",
"from": "ms@0.7.2"
}
}
},
"redis": {
"version": "0.12.1",
"from": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz",
"resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz"
"version": "2.6.3",
"from": "redis@^2.1.0",
"resolved": "https://registry.npmjs.org/redis/-/redis-2.6.3.tgz",
"dependencies": {
"double-ended-queue": {
"version": "2.1.0-0",
"from": "double-ended-queue@^2.1.0-0",
"resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz"
},
"redis-commands": {
"version": "1.3.0",
"from": "redis-commands@^1.2.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.0.tgz"
},
"redis-parser": {
"version": "2.1.1",
"from": "redis-parser@^2.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.1.1.tgz"
}
}
}
}
},
@@ -470,7 +485,7 @@
},
"json-stringify-safe": {
"version": "5.0.1",
"from": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"from": "json-stringify-safe@~5.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
}
}
@@ -616,7 +631,7 @@
},
"content-type": {
"version": "1.0.2",
"from": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
"from": "content-type@~1.0.1",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
},
"cookie": {
@@ -631,7 +646,7 @@
},
"debug": {
"version": "2.2.0",
"from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"from": "debug@~2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"dependencies": {
"ms": {
@@ -685,7 +700,7 @@
},
"on-finished": {
"version": "2.3.0",
"from": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"from": "on-finished@~2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"dependencies": {
"ee-first": {
@@ -829,7 +844,7 @@
},
"type-is": {
"version": "1.6.13",
"from": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
"from": "type-is@~1.6.10",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
"dependencies": {
"media-typer": {
@@ -864,68 +879,61 @@
}
},
"express-session": {
"version": "1.11.3",
"from": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz",
"version": "1.14.2",
"from": "express-session@1.14.2",
"dependencies": {
"cookie": {
"version": "0.1.3",
"from": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz"
"version": "0.3.1",
"from": "cookie@0.3.1"
},
"cookie-signature": {
"version": "1.0.6",
"from": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
"from": "cookie-signature@1.0.6"
},
"crc": {
"version": "3.3.0",
"from": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz",
"resolved": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz"
"version": "3.4.1",
"from": "crc@3.4.1"
},
"debug": {
"version": "2.2.0",
"from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"from": "debug@~2.2.0",
"dependencies": {
"ms": {
"version": "0.7.1",
"from": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
"from": "ms@0.7.1"
}
}
},
"depd": {
"version": "1.0.1",
"from": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz"
"version": "1.1.0",
"from": "depd@~1.1.0"
},
"on-headers": {
"version": "1.0.1",
"from": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
"from": "on-headers@~1.0.1"
},
"parseurl": {
"version": "1.3.1",
"from": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz"
"from": "parseurl@~1.3.1"
},
"uid-safe": {
"version": "2.0.0",
"from": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz",
"version": "2.1.3",
"from": "uid-safe@~2.1.3",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.3.tgz",
"dependencies": {
"base64-url": {
"version": "1.2.1",
"from": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz",
"resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz"
"version": "1.3.3",
"from": "base64-url@1.3.3"
},
"random-bytes": {
"version": "1.0.0",
"from": "random-bytes@~1.0.0"
}
}
},
"utils-merge": {
"version": "1.0.0",
"from": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz"
"from": "utils-merge@1.0.0"
}
}
},
@@ -1166,6 +1174,53 @@
}
}
},
"ioredis": {
"version": "2.4.0",
"from": "ioredis@",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-2.4.0.tgz",
"dependencies": {
"bluebird": {
"version": "3.4.6",
"from": "bluebird@^3.3.4",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz"
},
"cluster-key-slot": {
"version": "1.0.8",
"from": "cluster-key-slot@^1.0.6",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz"
},
"debug": {
"version": "2.3.0",
"from": "debug@^2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz",
"dependencies": {
"ms": {
"version": "0.7.2",
"from": "ms@0.7.2"
}
}
},
"double-ended-queue": {
"version": "2.1.0-0",
"from": "double-ended-queue@^2.1.0-0",
"resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz"
},
"flexbuffer": {
"version": "0.0.6",
"from": "flexbuffer@0.0.6"
},
"redis-commands": {
"version": "1.3.0",
"from": "redis-commands@^1.2.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.0.tgz"
},
"redis-parser": {
"version": "1.3.0",
"from": "redis-parser@^1.3.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz"
}
}
},
"jade": {
"version": "1.3.1",
"from": "https://registry.npmjs.org/jade/-/jade-1.3.1.tgz",
@@ -1602,7 +1657,7 @@
},
"logger-sharelatex": {
"version": "1.3.1",
"from": "git+https://github.com/sharelatex/logger-sharelatex.git#bf413ec621a000cf0e08c939de38d5e24541a08c",
"from": "logger-sharelatex@git+https://github.com/sharelatex/logger-sharelatex.git#bf413ec621a000cf0e08c939de38d5e24541a08c",
"resolved": "git+https://github.com/sharelatex/logger-sharelatex.git#bf413ec621a000cf0e08c939de38d5e24541a08c",
"dependencies": {
"bunyan": {
@@ -1812,7 +1867,7 @@
},
"metrics-sharelatex": {
"version": "1.3.0",
"from": "git+https://github.com/sharelatex/metrics-sharelatex.git#080c4aeb696edcd5d6d86f202f2c528f0661d7a6",
"from": "metrics-sharelatex@git+https://github.com/sharelatex/metrics-sharelatex.git#080c4aeb696edcd5d6d86f202f2c528f0661d7a6",
"resolved": "git+https://github.com/sharelatex/metrics-sharelatex.git#080c4aeb696edcd5d6d86f202f2c528f0661d7a6",
"dependencies": {
"coffee-script": {
@@ -2535,32 +2590,32 @@
},
"passport-ldapauth": {
"version": "0.6.0",
"from": "passport-ldapauth@*",
"from": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-0.6.0.tgz",
"resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-0.6.0.tgz",
"dependencies": {
"passport-strategy": {
"version": "1.0.0",
"from": "passport-strategy@>=1.0.0 <2.0.0",
"from": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz"
},
"ldapauth-fork": {
"version": "2.5.3",
"from": "ldapauth-fork@>=2.5.0 <2.6.0",
"from": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-2.5.3.tgz",
"resolved": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-2.5.3.tgz",
"dependencies": {
"bcryptjs": {
"version": "2.3.0",
"from": "bcryptjs@2.3.0",
"from": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.3.0.tgz"
},
"lru-cache": {
"version": "3.2.0",
"from": "lru-cache@3.2.0",
"from": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
"dependencies": {
"pseudomap": {
"version": "1.0.2",
"from": "pseudomap@>=1.0.1 <2.0.0",
"from": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz"
}
}
@@ -3430,7 +3485,7 @@
},
"json-stringify-safe": {
"version": "5.0.1",
"from": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"from": "json-stringify-safe@~5.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"mime-types": {
@@ -3462,7 +3517,7 @@
},
"stringstream": {
"version": "0.0.5",
"from": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
"from": "stringstream@~0.0.4",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
},
"tough-cookie": {
@@ -3472,7 +3527,7 @@
},
"tunnel-agent": {
"version": "0.4.3",
"from": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
"from": "tunnel-agent@~0.4.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz"
}
}
@@ -3558,7 +3613,7 @@
},
"depd": {
"version": "1.1.0",
"from": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
"from": "depd@~1.1.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
},
"dottie": {
@@ -3656,7 +3711,7 @@
},
"settings-sharelatex": {
"version": "1.0.0",
"from": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559",
"from": "settings-sharelatex@git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559",
"resolved": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559",
"dependencies": {
"coffee-script": {
+3 -2
View File
@@ -17,17 +17,18 @@
"bcrypt": "0.8.3",
"body-parser": "^1.13.1",
"bufferedstream": "1.6.0",
"connect-redis": "2.3.0",
"connect-redis": "^3.1.0",
"contentful": "^3.3.14",
"cookie": "^0.2.3",
"cookie-parser": "1.3.5",
"csurf": "^1.8.3",
"dateformat": "1.0.4-1.2.3",
"express": "4.13.0",
"express-session": "1.11.3",
"express-session": "^1.14.2",
"grunt": "^0.4.5",
"heapdump": "^0.3.7",
"http-proxy": "^1.8.1",
"ioredis": "^2.4.0",
"jade": "~1.3.1",
"ldapjs": "^1.0.0",
"lodash": "^4.13.1",