diff --git a/Documentation/Books/Users/Foxx/FoxxSessions.mdpp b/Documentation/Books/Users/Foxx/FoxxSessions.mdpp index 1f419a6830..5cdf03befd 100644 --- a/Documentation/Books/Users/Foxx/FoxxSessions.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxSessions.mdpp @@ -21,11 +21,12 @@ If *type* is set to *"cookie"*, the session cookie will be updated after every r * *type* (optional): sessions type, currently only *"cookie"* and *"header"* are supported. Default: *"cookie"*. * *cookieName* (optional): name of the session cookie if using cookie sessions. If a *cookieSecret* is provided, the signature will be stored in a cookie named *cookieName + "_sig"*. Default: *"sid"*. * *cookieSecret* (optional): secret string to sign session cookies with if using cookie sessions. + * *cookieAlgorithm* (optional): algorithm to sign session cookies with if using cookie sessions. Default: *"sha256"*. * *headerName* (optional): name of the session header if using header sessions. Default: *"X-Session-Id"*. - * *headerJwt* (optional): whether the session header should wrap the session ID in a JSON Web Token. Default: *false* unless *headerJwtSecret* is provided. - * *headerJwtSecret* (optional): secret string to sign session header JSON Web Tokens. - * *headerJwtAlgorithm* (optional): algorithm to sign session header JSON Web Tokens with, has no effect if no *headerJwtSecret* is provided. Default: *"HS256"*. - * *headerJwtVerify* (optional): whether incoming session header JSON Web Tokens should be verified, has no effect if no *headerJwtSecret* is provided. Default: *true*. + * *jwt* (optional): whether the session ID should be wrapped in a JSON Web Token. Default: *false* unless *jwtSecret* is provided. + * *jwtSecret* (optional): secret string to sign session JSON Web Tokens with. + * *jwtAlgorithm* (optional): algorithm to sign session JSON Web Tokens with if a *jwtSecret* is provided. Default: *"HS256"*. + * *jwtVerify* (optional): whether incoming session JSON Web Tokens should be verified, has no effect if no *jwtSecret* is provided. Default: *true*. * *autoCreateSession* (optional): whether a session should always be created if none exists. Default: *true*. @EXAMPLES diff --git a/js/apps/system/sessions/storage.js b/js/apps/system/sessions/storage.js index 97a9d381e4..aa84467077 100644 --- a/js/apps/system/sessions/storage.js +++ b/js/apps/system/sessions/storage.js @@ -6,8 +6,6 @@ internal = require('internal'), arangodb = require('org/arangodb'), db = arangodb.db, - addCookie = require('org/arangodb/actions').addCookie, - crypto = require('org/arangodb/crypto'), Foxx = require('org/arangodb/foxx'), errors = require('./errors'), cfg = applicationContext.configuration, @@ -102,28 +100,6 @@ return null; } - function fromCookie(req, cookieName, secret) { - var session = null, - value = req.cookies[cookieName], - signature; - if (value) { - if (secret) { - signature = req.cookies[cookieName + '_sig'] || ''; - if (!crypto.constantEquals(signature, crypto.hmac(secret, value))) { - return null; - } - } - try { - session = getSession(value); - } catch (e) { - if (!(e instanceof errors.SessionNotFound)) { - throw e; - } - } - } - return session; - } - _.extend(Session.prototype, { enforceTimeout: function () { if (this.hasExpired()) { @@ -131,11 +107,17 @@ } }, hasExpired: function () { - return Date.now() > this.getExpiry(); + return this.getTTL() === 0; + }, + getTTL: function () { + if (!cfg.timeToLive) { + return Infinity; + } + return Math.max(0, this.getExpiry() - Date.now()); }, getExpiry: function () { if (!cfg.timeToLive) { - return Number.MAX_VALUE; + return Infinity; } var prop = cfg.ttlType; if (!prop || !this.get(prop)) { @@ -143,21 +125,6 @@ } return this.get(prop) + cfg.timeToLive; }, - addCookie: function (res, cookieName, secret) { - var value = this.get('_key'), - ttl = cfg.timeToLive; - ttl = ttl ? Math.floor(ttl / 1000) : undefined; - addCookie(res, cookieName, value, ttl); - if (secret) { - addCookie(res, cookieName + '_sig', crypto.hmac(secret, value), ttl); - } - }, - clearCookie: function (res, cookieName, secret) { - addCookie(res, cookieName, '', -(7 * 24 * 60 * 60)); - if (secret) { - addCookie(res, cookieName + '_sig', '', -(7 * 24 * 60 * 60)); - } - }, setUser: function (user) { var session = this; if (user) { @@ -194,7 +161,6 @@ } }); - exports.fromCookie = fromCookie; exports.create = createSession; exports.get = getSession; exports.delete = deleteSession; diff --git a/js/server/modules/org/arangodb/foxx/sessions.js b/js/server/modules/org/arangodb/foxx/sessions.js index c7dce19ed3..3d9d75591c 100644 --- a/js/server/modules/org/arangodb/foxx/sessions.js +++ b/js/server/modules/org/arangodb/foxx/sessions.js @@ -49,20 +49,26 @@ function decorateController(auth, controller) { controller.before('/*', function (req) { var sessions = auth.getSessionStorage(); + var sid; if (cfg.type === 'cookie') { - req.session = sessions.fromCookie(req, cfg.cookieName, cfg.cookieSecret); - } else if (cfg.type === 'header') { - var sid = req.headers[cfg.headerName.toLowerCase()]; - if (sid) { - if (cfg.headerJwt) { - sid = crypto.jwtDecode(cfg.headerJwtSecret, sid, !cfg.headerJwtVerify); + sid = req.cookie(cfg.cookieName, cfg.cookieSecret ? { + signed: { + secret: cfg.cookieSecret, + algorithm: cfg.cookieAlgorithm } - try { - req.session = sessions.get(sid); - } catch (e) { - if (!(e instanceof sessions.errors.SessionNotFound)) { - throw e; - } + } : undefined); + } else if (cfg.type === 'header') { + sid = req.headers[cfg.headerName.toLowerCase()]; + } + if (sid) { + if (cfg.jwt) { + sid = crypto.jwtDecode(cfg.jwt.secret, sid, !cfg.jwt.verify); + } + try { + req.session = sessions.get(sid); + } catch (e) { + if (!(e instanceof sessions.errors.SessionNotFound)) { + throw e; } } } @@ -73,13 +79,19 @@ function decorateController(auth, controller) { controller.after('/*', function (req, res) { if (req.session) { + var sid = req.session.get('_key'); + if (cfg.jwt) { + sid = crypto.jwtEncode(cfg.jwt.secret, sid, cfg.jwt.algorithm); + } if (cfg.type === 'cookie') { - req.session.addCookie(res, cfg.cookieName, cfg.cookieSecret); + res.cookie(cfg.cookieName, sid, { + ttl: req.session.getTTL() / 1000, + signed: cfg.cookieSecret ? { + secret: cfg.cookieSecret, + algorithm: cfg.cookieAlgorithm + } : undefined + }); } else if (cfg.type === 'header') { - var sid = req.session.get('_key'); - if (cfg.headerJwt) { - sid = crypto.jwtEncode(cfg.headerJwtSecret, sid, cfg.headerJwtAlgorithm); - } res.set(cfg.headerName, sid); } } @@ -115,7 +127,13 @@ function createDestroySessionHandler(auth, opts) { req.session = auth.getSessionStorage().create(); } else { if (cfg.type === 'cookie') { - req.session.clearCookie(res, cfg.cookieName, cfg.cookieSecret); + res.cookie(cfg.cookieName, '', { + ttl: -(7 * 24 * 60 * 60), + sign: cfg.cookieSecret ? { + secret: cfg.cookieSecret, + algorithm: cfg.cookieAlgorithm + } : undefined + }); } delete req.session; } @@ -171,35 +189,35 @@ function Sessions(opts) { if (opts.headerName && typeof opts.headerName !== 'string') { throw new Error('Header name must be a string or empty.'); } - if (opts.headerJwt !== false) { - if (opts.headerJwtVerify !== false) { - opts.headerJwtVerify = true; - } - if (!opts.headerJwtSecret) { - opts.headerJwtSecret = ''; - if (!opts.headerJwtAlgorithm) { - opts.headerJwtAlgorithm = 'none'; - } else if (opts.headerJwtAlgorithm !== 'none') { - throw new Error('Must provide a JWT secret to use any algorithm other than "none".'); - } - } else { - opts.headerJwt = true; - if (typeof opts.headerJwtSecret !== 'string') { - throw new Error('Header JWT secret must be a string or empty.'); - } - if (!opts.headerJwtAlgorithm) { - opts.headerJwtAlgorithm = 'HS256'; - } else { - opts.headerJwtAlgorithm = crypto.jwtCanonicalAlgorithmName(opts.headerJwtAlgorithm); - } - } - } if (!opts.headerName) { opts.headerName = 'X-Session-Id'; } } else { throw new Error('Only the following session types are supported at this time: ' + sessionTypes.join(', ')); } + if (opts.jwt !== false) { + if (opts.jwtVerify !== false) { + opts.jwtVerify = true; + } + if (!opts.jwtSecret) { + opts.jwtSecret = ''; + if (!opts.jwtAlgorithm) { + opts.jwtAlgorithm = 'none'; + } else if (opts.jwtAlgorithm !== 'none') { + throw new Error('Must provide a JWT secret to use any algorithm other than "none".'); + } + } else { + opts.jwt = true; + if (typeof opts.jwtSecret !== 'string') { + throw new Error('Header JWT secret must be a string or empty.'); + } + if (!opts.jwtAlgorithm) { + opts.jwtAlgorithm = 'HS256'; + } else { + opts.jwtAlgorithm = crypto.jwtCanonicalAlgorithmName(opts.jwtAlgorithm); + } + } + } if (opts.autoCreateSession !== false) { opts.autoCreateSession = true; }