koa-generic-session
Advanced tools
Comparing version 1.11.6 to 2.0.0
2.0.0 / 2017-07-11 | ||
================== | ||
* Refactor to koa 2 - Supports node 6.x and 7.x (#123) | ||
1.11.6 / 2017-06-12 | ||
================== | ||
* fix: cookie options may be changed | ||
* fix: cookie options may be changed | ||
@@ -7,0 +12,0 @@ 1.11.5 / 2017-01-16 |
@@ -16,20 +16,24 @@ /**! | ||
var debug = require('debug')('koa-generic-session:memory_store'); | ||
const debug = require('debug')('koa-generic-session:memory_store'); | ||
var MemoryStore = module.exports = function () { | ||
this.sessions = {}; | ||
}; | ||
class MemoryStore { | ||
constructor() { | ||
this.sessions = {}; | ||
} | ||
MemoryStore.prototype.get = function *(sid) { | ||
debug('get value %j with key %s', this.sessions[sid], sid); | ||
return this.sessions[sid]; | ||
}; | ||
get(sid) { | ||
debug('get value %j with key %s', this.sessions[sid], sid); | ||
return this.sessions[sid]; | ||
} | ||
MemoryStore.prototype.set = function *(sid, val) { | ||
debug('set value %j for key %s', val, sid); | ||
this.sessions[sid] = val; | ||
}; | ||
set(sid, val) { | ||
debug('set value %j for key %s', val, sid); | ||
this.sessions[sid] = val; | ||
} | ||
MemoryStore.prototype.destroy = function *(sid) { | ||
delete this.sessions[sid]; | ||
}; | ||
destroy(sid) { | ||
delete this.sessions[sid]; | ||
} | ||
} | ||
module.exports = MemoryStore; |
@@ -0,1 +1,5 @@ | ||
'use strict'; | ||
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||
/**! | ||
@@ -10,4 +14,2 @@ * koa-generic-session - lib/session.js | ||
'use strict'; | ||
/** | ||
@@ -29,5 +31,3 @@ * Module dependencies. | ||
const warning = 'Warning: koa-generic-session\'s MemoryStore is not\n' + | ||
'designed for a production environment, as it will leak\n' + | ||
'memory, and will not scale past a single process.'; | ||
const warning = 'Warning: koa-generic-session\'s MemoryStore is not\n' + 'designed for a production environment, as it will leak\n' + 'memory, and will not scale past a single process.'; | ||
@@ -40,128 +40,24 @@ const defaultCookie = { | ||
maxAge: 24 * 60 * 60 * 1000 //one day in ms | ||
}; | ||
/** | ||
* setup session store with the given `options` | ||
* @param {Object} options | ||
* - [`key`] cookie name, defaulting to `koa.sid` | ||
* - [`store`] session store instance, default to MemoryStore | ||
* - [`ttl`] store ttl in `ms`, default to oneday | ||
* - [`prefix`] session prefix for store, defaulting to `koa:sess:` | ||
* - [`cookie`] session cookie settings, defaulting to | ||
* {path: '/', httpOnly: true, maxAge: null, rewrite: true, signed: true} | ||
* - [`defer`] defer get session, | ||
* - [`rolling`] rolling session, always reset the cookie and sessions, default is false | ||
* you should `yield this.session` to get the session if defer is true, default is false | ||
* - [`genSid`] you can use your own generator for sid | ||
* - [`errorHandler`] handler for session store get or set error | ||
* - [`valid`] valid(ctx, session), valid session value before use it | ||
* - [`beforeSave`] beforeSave(ctx, session), hook before save session | ||
* - [`sessionIdStore`] object with get, set, reset methods for passing session id throw requests. | ||
*/ | ||
module.exports = function (options) { | ||
options = options || {}; | ||
let key = options.key || 'koa.sid'; | ||
let client = options.store || new MemoryStore(); | ||
let errorHandler = options.errorHandler || defaultErrorHanlder; | ||
let reconnectTimeout = options.reconnectTimeout || 10000; | ||
let store = new Store(client, { | ||
ttl: options.ttl, | ||
prefix: options.prefix | ||
}); | ||
let genSid = options.genSid || uid.sync; | ||
let valid = options.valid || noop; | ||
let beforeSave = options.beforeSave || noop; | ||
let cookie = options.cookie || {}; | ||
copy(defaultCookie).to(cookie); | ||
let storeStatus = 'available'; | ||
let waitStore = Promise.resolve(); | ||
// notify user that this store is not | ||
// meant for a production environment | ||
if ('production' === process.env.NODE_ENV | ||
&& client instanceof MemoryStore) console.warn(warning); | ||
let sessionIdStore = options.sessionIdStore || { | ||
get: function() { | ||
return this.cookies.get(key, cookie); | ||
}, | ||
set: function(sid, session) { | ||
this.cookies.set(key, sid, session.cookie); | ||
}, | ||
reset: function() { | ||
this.cookies.set(key, null); | ||
} | ||
}; | ||
store.on('disconnect', function() { | ||
if (storeStatus !== 'available') return; | ||
storeStatus = 'pending'; | ||
waitStore = new Promise(function (resolve, reject) { | ||
setTimeout(function () { | ||
if (storeStatus === 'pending') storeStatus = 'unavailable'; | ||
reject(new Error('session store is unavailable')); | ||
}, reconnectTimeout); | ||
store.once('connect', resolve); | ||
}); | ||
}); | ||
store.on('connect', function() { | ||
storeStatus = 'available'; | ||
waitStore = Promise.resolve(); | ||
}); | ||
return options.defer ? deferSession : session; | ||
function addCommonAPI() { | ||
this._sessionSave = null; | ||
// more flexible | ||
this.__defineGetter__('sessionSave', function () { | ||
return this._sessionSave; | ||
}); | ||
this.__defineSetter__('sessionSave', function (save) { | ||
this._sessionSave = save; | ||
}); | ||
} | ||
/** | ||
* generate a new session | ||
* setup session store with the given `options` | ||
* @param {Object} options | ||
* - [`key`] cookie name, defaulting to `koa.sid` | ||
* - [`store`] session store instance, default to MemoryStore | ||
* - [`ttl`] store ttl in `ms`, default to oneday | ||
* - [`prefix`] session prefix for store, defaulting to `koa:sess:` | ||
* - [`cookie`] session cookie settings, defaulting to | ||
* {path: '/', httpOnly: true, maxAge: null, rewrite: true, signed: true} | ||
* - [`defer`] defer get session, | ||
* - [`rolling`] rolling session, always reset the cookie and sessions, default is false | ||
* you should `await ctx.session` to get the session if defer is true, default is false | ||
* - [`genSid`] you can use your own generator for sid | ||
* - [`errorHandler`] handler for session store get or set error | ||
* - [`valid`] valid(ctx, session), valid session value before use it | ||
* - [`beforeSave`] beforeSave(ctx, session), hook before save session | ||
* - [`sessionIdStore`] object with get, set, reset methods for passing session id throw requests. | ||
*/ | ||
function generateSession() { | ||
let session = {}; | ||
//you can alter the cookie options in nexts | ||
session.cookie = {}; | ||
for (let prop in cookie) { | ||
session.cookie[prop] = cookie[prop]; | ||
} | ||
compatMaxage(session.cookie); | ||
return session; | ||
} | ||
/** | ||
* check url match cookie's path | ||
*/ | ||
function matchPath(ctx) { | ||
let pathname = parse(ctx).pathname; | ||
let cookiePath = cookie.path || '/'; | ||
if (cookiePath === '/') { | ||
return true; | ||
} | ||
if (pathname.indexOf(cookiePath) !== 0) { | ||
debug('cookie path not match'); | ||
return false; | ||
} | ||
return true; | ||
} | ||
};module.exports = function (options = {}) { | ||
@@ -174,56 +70,61 @@ /** | ||
*/ | ||
function *getSession() { | ||
if (!matchPath(this)) return; | ||
if (storeStatus === 'pending') { | ||
debug('store is disconnect and pending'); | ||
yield waitStore; | ||
} else if (storeStatus === 'unavailable') { | ||
debug('store is unavailable'); | ||
throw new Error('session store is unavailable'); | ||
} | ||
let getSession = (() => { | ||
var _ref = _asyncToGenerator(function* (ctx) { | ||
if (!matchPath(ctx)) return; | ||
if (storeStatus === 'pending') { | ||
debug('store is disconnect and pending'); | ||
yield waitStore; | ||
} else if (storeStatus === 'unavailable') { | ||
debug('store is unavailable'); | ||
throw new Error('session store is unavailable'); | ||
} | ||
if (!this.sessionId) { | ||
this.sessionId = sessionIdStore.get.call(this); | ||
} | ||
if (!ctx.sessionId) { | ||
ctx.sessionId = sessionIdStore.get.call(ctx); | ||
} | ||
let session; | ||
let isNew = false; | ||
if (!this.sessionId) { | ||
debug('session id not exist, generate a new one'); | ||
session = generateSession(); | ||
this.sessionId = genSid.call(this, 24); | ||
isNew = true; | ||
} else { | ||
try { | ||
session = yield store.get(this.sessionId); | ||
debug('get session %j with key %s', session, this.sessionId); | ||
} catch (err) { | ||
if (err.code === 'ENOENT') { | ||
debug('get session error, code = ENOENT'); | ||
} else { | ||
debug('get session error: ', err.message); | ||
errorHandler(err, 'get', this); | ||
let session; | ||
let isNew = false; | ||
if (!ctx.sessionId) { | ||
debug('session id not exist, generate a new one'); | ||
session = generateSession(); | ||
ctx.sessionId = genSid.call(ctx, 24); | ||
isNew = true; | ||
} else { | ||
try { | ||
session = yield store.get(ctx.sessionId); | ||
debug('get session %j with key %s', session, ctx.sessionId); | ||
} catch (err) { | ||
if (err.code === 'ENOENT') { | ||
debug('get session error, code = ENOENT'); | ||
} else { | ||
debug('get session error: ', err.message); | ||
errorHandler(err, 'get', ctx); | ||
} | ||
} | ||
} | ||
} | ||
// make sure the session is still valid | ||
if (!session || | ||
!valid(this, session)) { | ||
debug('session is empty or invalid'); | ||
session = generateSession(); | ||
this.sessionId = genSid.call(this, 24); | ||
sessionIdStore.reset.call(this); | ||
isNew = true; | ||
} | ||
// make sure the session is still valid | ||
if (!session || !valid(ctx, session)) { | ||
debug('session is empty or invalid'); | ||
session = generateSession(); | ||
ctx.sessionId = genSid.call(ctx, 24); | ||
sessionIdStore.reset.call(ctx); | ||
isNew = true; | ||
} | ||
// get the originHash | ||
let originalHash = !isNew && hash(session); | ||
// get the originHash | ||
const originalHash = !isNew && hash(session); | ||
return { | ||
originalHash: originalHash, | ||
session: session, | ||
isNew: isNew | ||
return { | ||
originalHash: originalHash, | ||
session: session, | ||
isNew: isNew | ||
}; | ||
}); | ||
return function getSession(_x) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
} | ||
})(); | ||
@@ -235,59 +136,72 @@ /** | ||
*/ | ||
function *refreshSession (session, originalHash, isNew) { | ||
// reject any session changes, and do not update session expiry | ||
if(this._sessionSave === false) { | ||
return debug('session save disabled'); | ||
} | ||
//delete session | ||
if (!session) { | ||
if (!isNew) { | ||
debug('session set to null, destroy session: %s', this.sessionId); | ||
sessionIdStore.reset.call(this); | ||
return yield store.destroy(this.sessionId); | ||
let refreshSession = (() => { | ||
var _ref2 = _asyncToGenerator(function* (ctx, session, originalHash, isNew) { | ||
// reject any session changes, and do not update session expiry | ||
if (ctx._sessionSave === false) { | ||
return debug('session save disabled'); | ||
} | ||
return debug('a new session and set to null, ignore destroy'); | ||
} | ||
// force saving non-empty session | ||
if(this._sessionSave === true) { | ||
debug('session save forced'); | ||
return yield saveNow.call(this, this.sessionId, session); | ||
} | ||
//delete session | ||
if (!session) { | ||
if (!isNew) { | ||
debug('session set to null, destroy session: %s', ctx.sessionId); | ||
sessionIdStore.reset.call(ctx); | ||
return store.destroy(ctx.sessionId); | ||
} | ||
return debug('a new session and set to null, ignore destroy'); | ||
} | ||
let newHash = hash(session); | ||
// if new session and not modified, just ignore | ||
if (!options.allowEmpty && isNew && newHash === hash(generateSession())) { | ||
return debug('new session and do not modified'); | ||
} | ||
// force saving non-empty session | ||
if (ctx._sessionSave === true) { | ||
debug('session save forced'); | ||
return saveNow(ctx, ctx.sessionId, session); | ||
} | ||
// rolling session will always reset cookie and session | ||
if (!options.rolling && newHash === originalHash) { | ||
return debug('session not modified'); | ||
} | ||
const newHash = hash(session); | ||
// if new session and not modified, just ignore | ||
if (!options.allowEmpty && isNew && newHash === EMPTY_SESSION_HASH) { | ||
return debug('new session and do not modified'); | ||
} | ||
debug('session modified'); | ||
// rolling session will always reset cookie and session | ||
if (!options.rolling && newHash === originalHash) { | ||
return debug('session not modified'); | ||
} | ||
yield saveNow.call(this, this.sessionId, session); | ||
debug('session modified'); | ||
} | ||
yield saveNow(ctx, ctx.sessionId, session); | ||
}); | ||
function *saveNow(id, session) { | ||
compatMaxage(session.cookie); | ||
return function refreshSession(_x2, _x3, _x4, _x5) { | ||
return _ref2.apply(this, arguments); | ||
}; | ||
})(); | ||
// custom before save hook | ||
beforeSave(this, session); | ||
let saveNow = (() => { | ||
var _ref3 = _asyncToGenerator(function* (ctx, id, session) { | ||
compatMaxage(session.cookie); | ||
//update session | ||
try { | ||
yield store.set(id, session); | ||
sessionIdStore.set.call(this, id, session); | ||
debug('saved'); | ||
} catch (err) { | ||
debug('set session error: ', err.message); | ||
errorHandler(err, 'set', this); | ||
} | ||
} | ||
// custom before save hook | ||
beforeSave(ctx, session); | ||
//update session | ||
try { | ||
yield store.set(id, session); | ||
sessionIdStore.set.call(ctx, id, session); | ||
debug('saved'); | ||
} catch (err) { | ||
debug('set session error: ', err.message); | ||
errorHandler(err, 'set', ctx); | ||
} | ||
}); | ||
return function saveNow(_x6, _x7, _x8) { | ||
return _ref3.apply(this, arguments); | ||
}; | ||
})(); | ||
/** | ||
@@ -298,63 +212,80 @@ * common session middleware | ||
* ``` | ||
* let session = this.session; | ||
* let session = this.session | ||
* ``` | ||
*/ | ||
function *session(next) { | ||
this.sessionStore = store; | ||
if (this.session || this._session) { | ||
return yield next; | ||
} | ||
let result = yield getSession.call(this); | ||
if (!result) { | ||
return yield next; | ||
} | ||
addCommonAPI.call(this); | ||
this._session = result.session; | ||
let session = (() => { | ||
var _ref4 = _asyncToGenerator(function* (ctx, next) { | ||
ctx.sessionStore = store; | ||
if (ctx.session || ctx._session) { | ||
return next(); | ||
} | ||
const result = yield getSession(ctx); | ||
if (!result) { | ||
return next(); | ||
} | ||
// more flexible | ||
this.__defineGetter__('session', function () { | ||
return this._session; | ||
}); | ||
addCommonAPI(ctx); | ||
this.__defineSetter__('session', function (sess) { | ||
this._session = sess; | ||
}); | ||
ctx._session = result.session; | ||
this.regenerateSession = function *regenerateSession() { | ||
debug('regenerating session'); | ||
if (!result.isNew) { | ||
// destroy the old session | ||
debug('destroying previous session'); | ||
yield store.destroy(this.sessionId); | ||
} | ||
// more flexible | ||
Object.defineProperty(ctx, 'session', { | ||
get() { | ||
return this._session; | ||
}, | ||
set(sess) { | ||
this._session = sess; | ||
} | ||
}); | ||
this.session = generateSession(); | ||
this.sessionId = genSid.call(this, 24); | ||
sessionIdStore.reset.call(this); | ||
ctx.regenerateSession = (() => { | ||
var _ref5 = _asyncToGenerator(function* () { | ||
debug('regenerating session'); | ||
if (!result.isNew) { | ||
// destroy the old session | ||
debug('destroying previous session'); | ||
yield store.destroy(ctx.sessionId); | ||
} | ||
debug('created new session: %s', this.sessionId); | ||
result.isNew = true; | ||
} | ||
ctx.session = generateSession(); | ||
ctx.sessionId = genSid.call(ctx, 24); | ||
sessionIdStore.reset.call(ctx); | ||
// make sure `refreshSession` always called | ||
var firstError = null; | ||
try { | ||
yield next; | ||
} catch (err) { | ||
debug('next logic error: %s', err.message); | ||
firstError = err; | ||
} | ||
// can't use finally because `refreshSession` is async | ||
try { | ||
yield refreshSession.call(this, this.session, result.originalHash, result.isNew); | ||
} catch (err) { | ||
debug('refresh session error: %s', err.message); | ||
if (firstError) this.app.emit('error', err, this); | ||
firstError = firstError || err; | ||
} | ||
if (firstError) throw firstError; | ||
} | ||
debug('created new session: %s', ctx.sessionId); | ||
result.isNew = true; | ||
}); | ||
function regenerateSession() { | ||
return _ref5.apply(this, arguments); | ||
} | ||
return regenerateSession; | ||
})(); | ||
// make sure `refreshSession` always called | ||
let firstError = null; | ||
try { | ||
yield next(); | ||
} catch (err) { | ||
debug('next logic error: %s', err.message); | ||
firstError = err; | ||
} | ||
// can't use finally because `refreshSession` is async | ||
try { | ||
yield refreshSession(ctx, ctx.session, result.originalHash, result.isNew); | ||
} catch (err) { | ||
debug('refresh session error: %s', err.message); | ||
if (firstError) ctx.app.emit('error', err, ctx); | ||
firstError = firstError || err; | ||
} | ||
if (firstError) throw firstError; | ||
}); | ||
return function session(_x9, _x10) { | ||
return _ref4.apply(this, arguments); | ||
}; | ||
})(); | ||
/** | ||
@@ -365,76 +296,213 @@ * defer session middleware | ||
* ``` | ||
* let session = yield this.session; | ||
* let session = yield this.session | ||
* ``` | ||
*/ | ||
function *deferSession(next) { | ||
this.sessionStore = store; | ||
if (this.session) { | ||
return yield next; | ||
} | ||
let isNew = false; | ||
let originalHash = null; | ||
let touchSession = false; | ||
let getter = false; | ||
// if path not match | ||
if (!matchPath(this)) { | ||
return yield next; | ||
} | ||
let deferSession = (() => { | ||
var _ref6 = _asyncToGenerator(function* (ctx, next) { | ||
ctx.sessionStore = store; | ||
addCommonAPI.call(this); | ||
// TODO: | ||
// Accessing ctx.session when it's defined is causing problems | ||
// because it has side effect. So, here we use a flag to determine | ||
// that session property is already defined. | ||
if (ctx.__isSessionDefined) { | ||
return next(); | ||
} | ||
let isNew = false; | ||
let originalHash = null; | ||
let touchSession = false; | ||
let getter = false; | ||
this.__defineGetter__('session', function *() { | ||
if (touchSession) { | ||
return this._session; | ||
// if path not match | ||
if (!matchPath(ctx)) { | ||
return next(); | ||
} | ||
touchSession = true; | ||
getter = true; | ||
let result = yield getSession.call(this); | ||
// if cookie path not match | ||
// this route's controller should never use session | ||
if (!result) return; | ||
addCommonAPI(ctx); | ||
originalHash = result.originalHash; | ||
isNew = result.isNew; | ||
this._session = result.session; | ||
return this._session; | ||
}); | ||
Object.defineProperty(ctx, 'session', { | ||
get() { | ||
var _this = this; | ||
this.__defineSetter__('session', function (value) { | ||
touchSession = true; | ||
this._session = value; | ||
}); | ||
return _asyncToGenerator(function* () { | ||
if (touchSession) { | ||
return _this._session; | ||
} | ||
touchSession = true; | ||
getter = true; | ||
this.regenerateSession = function *regenerateSession() { | ||
debug('regenerating session'); | ||
// make sure that the session has been loaded | ||
yield this.session; | ||
const result = yield getSession(_this); | ||
// if cookie path not match | ||
// this route's controller should never use session | ||
if (!result) return; | ||
if (!isNew) { | ||
// destroy the old session | ||
debug('destroying previous session'); | ||
yield store.destroy(this.sessionId); | ||
originalHash = result.originalHash; | ||
isNew = result.isNew; | ||
_this._session = result.session; | ||
return _this._session; | ||
})(); | ||
}, | ||
set(value) { | ||
touchSession = true; | ||
this._session = value; | ||
} | ||
}); | ||
// internal flag to determine that session is already defined | ||
ctx.__isSessionDefined = true; | ||
ctx.regenerateSession = (() => { | ||
var _ref7 = _asyncToGenerator(function* () { | ||
debug('regenerating session'); | ||
// make sure that the session has been loaded | ||
yield ctx.session; | ||
if (!isNew) { | ||
// destroy the old session | ||
debug('destroying previous session'); | ||
yield store.destroy(ctx.sessionId); | ||
} | ||
ctx._session = generateSession(); | ||
ctx.sessionId = genSid.call(ctx, 24); | ||
sessionIdStore.reset.call(ctx); | ||
debug('created new session: %s', ctx.sessionId); | ||
isNew = true; | ||
return ctx._session; | ||
}); | ||
function regenerateSession() { | ||
return _ref7.apply(this, arguments); | ||
} | ||
return regenerateSession; | ||
})(); | ||
yield next(); | ||
if (touchSession) { | ||
// if only this.session=, need try to decode and get the sessionID | ||
if (!getter) { | ||
ctx.sessionId = sessionIdStore.get.call(ctx); | ||
} | ||
yield refreshSession(ctx, ctx._session, originalHash, isNew); | ||
} | ||
}); | ||
this._session = generateSession(); | ||
this.sessionId = genSid.call(this, 24); | ||
sessionIdStore.reset.call(this); | ||
debug('created new session: %s', this.sessionId); | ||
isNew = true; | ||
return this._session; | ||
return function deferSession(_x11, _x12) { | ||
return _ref6.apply(this, arguments); | ||
}; | ||
})(); | ||
const key = options.key || 'koa.sid'; | ||
const client = options.store || new MemoryStore(); | ||
const errorHandler = options.errorHandler || defaultErrorHanlder; | ||
const reconnectTimeout = options.reconnectTimeout || 10000; | ||
const store = new Store(client, { | ||
ttl: options.ttl, | ||
prefix: options.prefix | ||
}); | ||
const genSid = options.genSid || uid.sync; | ||
const valid = options.valid || noop; | ||
const beforeSave = options.beforeSave || noop; | ||
const cookie = options.cookie || {}; | ||
copy(defaultCookie).to(cookie); | ||
let storeStatus = 'available'; | ||
let waitStore = Promise.resolve(); | ||
// notify user that this store is not | ||
// meant for a production environment | ||
if ('production' === process.env.NODE_ENV && client instanceof MemoryStore) { | ||
// eslint-disable-next-line | ||
console.warn(warning); | ||
} | ||
const sessionIdStore = options.sessionIdStore || { | ||
get: function get() { | ||
return this.cookies.get(key, cookie); | ||
}, | ||
set: function set(sid, session) { | ||
this.cookies.set(key, sid, session.cookie); | ||
}, | ||
reset: function reset() { | ||
this.cookies.set(key, null); | ||
} | ||
}; | ||
yield next; | ||
store.on('disconnect', () => { | ||
if (storeStatus !== 'available') return; | ||
storeStatus = 'pending'; | ||
waitStore = new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
if (storeStatus === 'pending') storeStatus = 'unavailable'; | ||
reject(new Error('session store is unavailable')); | ||
}, reconnectTimeout); | ||
store.once('connect', resolve); | ||
}); | ||
}); | ||
if (touchSession) { | ||
// if only this.session=, need try to decode and get the sessionID | ||
if (!getter) { | ||
this.sessionId = sessionIdStore.get.call(this); | ||
store.on('connect', () => { | ||
storeStatus = 'available'; | ||
waitStore = Promise.resolve(); | ||
}); | ||
// save empty session hash for compare | ||
const EMPTY_SESSION_HASH = hash(generateSession()); | ||
return options.defer ? deferSession : session; | ||
function addCommonAPI(ctx) { | ||
ctx._sessionSave = null; | ||
// more flexible | ||
Object.defineProperty(ctx, 'sessionSave', { | ||
get: () => { | ||
return ctx._sessionSave; | ||
}, | ||
set: save => { | ||
ctx._sessionSave = save; | ||
} | ||
}); | ||
} | ||
yield refreshSession.call(this, this._session, originalHash, isNew); | ||
/** | ||
* generate a new session | ||
*/ | ||
function generateSession() { | ||
const session = {}; | ||
//you can alter the cookie options in nexts | ||
session.cookie = {}; | ||
for (const prop in cookie) { | ||
session.cookie[prop] = cookie[prop]; | ||
} | ||
compatMaxage(session.cookie); | ||
return session; | ||
} | ||
/** | ||
* check url match cookie's path | ||
*/ | ||
function matchPath(ctx) { | ||
const pathname = parse(ctx).pathname; | ||
const cookiePath = cookie.path || '/'; | ||
if (cookiePath === '/') { | ||
return true; | ||
} | ||
if (pathname.indexOf(cookiePath) !== 0) { | ||
debug('cookie path not match'); | ||
return false; | ||
} | ||
return true; | ||
} | ||
}; | ||
@@ -461,3 +529,3 @@ | ||
function defaultErrorHanlder (err, type, ctx) { | ||
function defaultErrorHanlder(err, type) { | ||
err.name = 'koa-generic-session ' + type + ' error'; | ||
@@ -467,4 +535,4 @@ throw err; | ||
function noop () { | ||
function noop() { | ||
return true; | ||
} | ||
} |
120
lib/store.js
@@ -16,69 +16,81 @@ /**! | ||
var util = require('util'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var debug = require('debug')('koa-generic-session:store'); | ||
var copy = require('copy-to'); | ||
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||
var defaultOptions = { | ||
const EventEmitter = require('events').EventEmitter; | ||
const debug = require('debug')('koa-generic-session:store'); | ||
const copy = require('copy-to'); | ||
const defaultOptions = { | ||
prefix: 'koa:sess:' | ||
}; | ||
function Store(client, options) { | ||
this.client = client; | ||
this.options = {}; | ||
copy(options).and(defaultOptions).to(this.options); | ||
EventEmitter.call(this); | ||
class Store extends EventEmitter { | ||
constructor(client, options) { | ||
super(); | ||
this.client = client; | ||
this.options = options; | ||
copy(options).and(defaultOptions).to(this.options); | ||
// delegate client connect / disconnect event | ||
if (typeof client.on === 'function') { | ||
client.on('disconnect', this.emit.bind(this, 'disconnect')); | ||
client.on('connect', this.emit.bind(this, 'connect')); | ||
// delegate client connect / disconnect event | ||
if (typeof client.on === 'function') { | ||
client.on('disconnect', this.emit.bind(this, 'disconnect')); | ||
client.on('connect', this.emit.bind(this, 'connect')); | ||
} | ||
} | ||
} | ||
util.inherits(Store, EventEmitter); | ||
get(sid) { | ||
var _this = this; | ||
Store.prototype.get = function *(sid) { | ||
var data; | ||
sid = this.options.prefix + sid; | ||
debug('GET %s', sid); | ||
data = yield this.client.get(sid); | ||
if (!data) { | ||
debug('GET empty'); | ||
return null; | ||
return _asyncToGenerator(function* () { | ||
sid = _this.options.prefix + sid; | ||
debug('GET %s', sid); | ||
const data = yield _this.client.get(sid); | ||
if (!data) { | ||
debug('GET empty'); | ||
return null; | ||
} | ||
if (data && data.cookie && typeof data.cookie.expires === 'string') { | ||
// make sure data.cookie.expires is a Date | ||
data.cookie.expires = new Date(data.cookie.expires); | ||
} | ||
debug('GOT %j', data); | ||
return data; | ||
})(); | ||
} | ||
if (data && data.cookie && typeof data.cookie.expires === 'string') { | ||
// make sure data.cookie.expires is a Date | ||
data.cookie.expires = new Date(data.cookie.expires); | ||
} | ||
debug('GOT %j', data); | ||
return data; | ||
}; | ||
Store.prototype.set = function *(sid, sess) { | ||
var ttl = this.options.ttl; | ||
if (!ttl) { | ||
var maxAge = sess.cookie && sess.cookie.maxAge; | ||
if (typeof maxAge === 'number') { | ||
ttl = maxAge; | ||
} | ||
// if has cookie.expires, ignore cookie.maxAge | ||
if (sess.cookie && sess.cookie.expires) { | ||
ttl = Math.ceil(sess.cookie.expires.getTime() - Date.now()); | ||
} | ||
set(sid, sess) { | ||
var _this2 = this; | ||
return _asyncToGenerator(function* () { | ||
let ttl = _this2.options.ttl; | ||
if (!ttl) { | ||
const maxAge = sess.cookie && sess.cookie.maxAge; | ||
if (typeof maxAge === 'number') { | ||
ttl = maxAge; | ||
} | ||
// if has cookie.expires, ignore cookie.maxAge | ||
if (sess.cookie && sess.cookie.expires) { | ||
ttl = Math.ceil(sess.cookie.expires.getTime() - Date.now()); | ||
} | ||
} | ||
sid = _this2.options.prefix + sid; | ||
debug('SET key: %s, value: %s, ttl: %d', sid, sess, ttl); | ||
yield _this2.client.set(sid, sess, ttl); | ||
debug('SET complete'); | ||
})(); | ||
} | ||
sid = this.options.prefix + sid; | ||
debug('SET key: %s, value: %s, ttl: %d', sid, sess, ttl); | ||
yield this.client.set(sid, sess, ttl); | ||
debug('SET complete'); | ||
}; | ||
destroy(sid) { | ||
var _this3 = this; | ||
Store.prototype.destroy = function *(sid) { | ||
sid = this.options.prefix + sid; | ||
debug('DEL %s', sid); | ||
yield this.client.destroy(sid); | ||
debug('DEL %s complete', sid); | ||
}; | ||
return _asyncToGenerator(function* () { | ||
sid = _this3.options.prefix + sid; | ||
debug('DEL %s', sid); | ||
yield _this3.client.destroy(sid); | ||
debug('DEL %s complete', sid); | ||
})(); | ||
} | ||
} | ||
module.exports = Store; | ||
module.exports = Store; |
@@ -5,6 +5,10 @@ { | ||
"repository": "koajs/generic-session", | ||
"version": "1.11.6", | ||
"version": "2.0.0", | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">= 6.0.0" | ||
}, | ||
"main": "lib/session", | ||
"files": [ | ||
"lib", | ||
"index.js" | ||
"lib" | ||
], | ||
@@ -19,24 +23,25 @@ "keywords": [ | ||
"copy-to": "~2.0.1", | ||
"crc": "~3.4.0", | ||
"debug": "*", | ||
"crc": "~3.4.4", | ||
"debug": "~2.6.3", | ||
"parseurl": "~1.3.1", | ||
"uid-safe": "~2.1.1" | ||
"uid-safe": "~2.1.4" | ||
}, | ||
"devDependencies": { | ||
"autod": "~2.1.3", | ||
"autod": "~2.8.0", | ||
"babel-cli": "^6.24.1", | ||
"babel-preset-env": "^1.4.0", | ||
"blanket": "*", | ||
"contributors": "*", | ||
"istanbul-harmony": "*", | ||
"koa": "^1.2.4", | ||
"istanbul": "^0.4.5", | ||
"koa": "^2.2.0", | ||
"koa-redis": "~2.1.1", | ||
"mm": "~1.5.0", | ||
"mocha": "2", | ||
"mm": "^2.1.0", | ||
"mocha": "^3.2.0", | ||
"pedding": "^1.0.0", | ||
"should": "~10.0.0", | ||
"supertest": "~2.0.1" | ||
"should": "^11.2.1", | ||
"supertest": "^3.0.0" | ||
}, | ||
"engines": { | ||
"node": ">= 0.11.9" | ||
}, | ||
"license": "MIT" | ||
"scripts": { | ||
"prepublish": "make build" | ||
} | ||
} |
@@ -20,3 +20,3 @@ generic-session | ||
[david-url]: https://david-dm.org/koajs/generic-session | ||
[node-image]: https://img.shields.io/badge/node.js-%3E=_0.11-red.svg?style=flat-square | ||
[node-image]: https://img.shields.io/badge/node.js-%3E=_6.0.0-red.svg?style=flat-square | ||
[node-url]: http://nodejs.org/download/ | ||
@@ -36,2 +36,4 @@ [download-image]: https://img.shields.io/npm/dm/koa-generic-session.svg?style=flat-square | ||
**For async/await and Node v6.9.0+ support use v2.x of this package, for older use v1.x** | ||
## Usage | ||
@@ -47,3 +49,3 @@ | ||
var app = koa(); | ||
var app = new koa(); // for koa v1 use `var app = koa();` | ||
app.keys = ['keys', 'keykeys']; | ||
@@ -50,0 +52,0 @@ app.use(session({ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
33905
549
0
169
13
7
2
+ Addeddebug@2.6.9(transitive)
+ Addedms@2.0.0(transitive)
- Removeddebug@4.3.7(transitive)
- Removedms@2.1.3(transitive)
Updatedcrc@~3.4.4
Updateddebug@~2.6.3
Updateduid-safe@~2.1.4