Skip to content

Commit

Permalink
remove setMaxAge() interface because it cannot preserve the new maxAg…
Browse files Browse the repository at this point in the history
…e among requests
  • Loading branch information
lzztt committed Sep 14, 2016
1 parent bd6a21b commit 0ca89ce
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 26 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ This middleware guarantees the following:
- If a session has not been updated within `maxAge`, its data will be expired.
- Minimum public interfaces and configuration options.
- Cookie options: `maxAge`, `path`, `domain`, `secure`, `httpOnly`
- Session interfaces: `session`, `sessionHandler { regenerateId(), setMaxAge() }`
- Session interfaces: `session`, `sessionHandler { regenerateId() }`
- Store interfaces: `get()`, `set()`, `destroy()`


Expand Down Expand Up @@ -66,14 +66,13 @@ app.listen(3000)
- session data via `ctx.session` (the same way as `koa-generic-session`)
- session methods via `ctx.sessionHandler`
- `regenerateId()`: regenerate session id
- `setMaxAge(ms)`: update session's `maxAge` to `ms` milliseconds, only take effect when session data has been changed


## Options

- `key`: session cookie name and store key prefix
- `store`: session store
- `cookie`: cookie options, only supports `maxAge`, `path`, `domain`, `secure`, `httpOnly` (see option details in [`cookies`](https://github.com/pillarjs/cookies) module)
- `cookie`: cookie options, can be an object (static cookie options) or a function that returns an object (dynamic cookie options). Only `maxAge`, `path`, `domain`, `secure`, `httpOnly` are supported as option keys (see option details in [`cookies`](https://github.com/pillarjs/cookies) module).


## Session expiration
Expand All @@ -84,7 +83,17 @@ Default session has settings `cookie.maxAge = 0` for cookie and `ttl = ONE_DAY`

With settings that `cookie.maxAge > 0`, the `ttl` for store data will be always the same as `maxAge`.

When a middleware updates session data, it can also update the `maxAge` by calling `sessionHandler.setMaxAge(ms)`. Then the session's expiration time will be updated to `now() + maxAge` milliseconds.
When setting `cookie` option to a plain object, all sessions will use the same cookie options. If a function is assigned to `cookie`, cookie options will be dynamically calculated at each (non-empty) session's saving stage.
You may use different `maxAge` for user and guest sessions, by initializing the session middleware as below:
```javascript
session({
cookie: ctx => {
return {
maxAge: ctx.session.user ? ONE_MONTH : ONE_DAY,
}
}
})
```


## Session security
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "koa-session-minimal",
"version": "2.0.1",
"version": "3.0.0",
"description": "Minimal implementation of session middleware for Koa 2. Inspired by and compatible with koa-generic-session",
"main": "dist/session.js",
"scripts": {
Expand Down
31 changes: 16 additions & 15 deletions src/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ const MemoryStore = require('./memory_store')

const ONE_DAY = 24 * 3600 * 1000 // one day in milliseconds

const cookieOpt = cookie => {
const options = Object.assign({
maxAge: 0, // default to use session cookie
path: '/',
secure: false,
httpOnly: true,
}, cookie || {}, {
overwrite: true, // overwrite previous session cookie changes
signed: false, // disable signed option
})
if (!(options.maxAge >= 0)) options.maxAge = 0
return options
}

const deleteSession = (ctx, key, cookie, store, sid) => {
const deleteOption = Object.assign({}, cookie)
delete deleteOption.maxAge
Expand All @@ -22,20 +36,9 @@ module.exports = options => {
const opt = options || {}
const key = opt.key || 'koa:sess'
const store = new Store(opt.store || new MemoryStore())
const defaultCookie = Object.assign({
maxAge: 0, // default to use session cookie
path: '/',
secure: false,
httpOnly: true,
}, opt.cookie || {}, {
overwrite: true, // overwrite previous session cookie changes
signed: false, // disable signed option
})
if (!(defaultCookie.maxAge >= 0)) defaultCookie.maxAge = 0
const defaultCookie = opt.cookie instanceof Function ? opt.cookie : cookieOpt(opt.cookie)

return async (ctx, next) => {
const cookie = Object.assign({}, defaultCookie)

// initialize session id and data
const cookieSid = ctx.cookies.get(key)

Expand All @@ -57,13 +60,11 @@ module.exports = options => {
regenerateId: () => {
sid = uid.sync(24)
},
setMaxAge: ms => {
cookie.maxAge = ms >= 0 ? ms : 0
},
}

await next()

const cookie = defaultCookie instanceof Function ? cookieOpt(defaultCookie(ctx)) : defaultCookie
const sessionHasData = ctx.session && Object.keys(ctx.session).length

if (sid !== cookieSid) { // a new session id
Expand Down
32 changes: 29 additions & 3 deletions test/async_store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ const updateSession = (ctx, next) => {
break
default:
if (ctx.url.startsWith('/maxage/')) {
const ms = parseInt(ctx.url.replace('/maxage/', ''), 10)
ctx.sessionHandler.setMaxAge(ms)
ctx.ms = parseInt(ctx.url.replace('/maxage/', ''), 10)
ctx.session.time = Date.now()
}
}
Expand Down Expand Up @@ -399,6 +398,33 @@ describe('session with async store', () => {
})
})
})
})

describe('async store with dynamic cookie options', () => {
const app = new Koa()
const key = 'koa:sess'
const store = new SpyStore()

app.use(session({
store,
cookie: ctx => { // eslint-disable-line arrow-body-style
return {
maxAge: ctx.ms,
}
},
}))
app.use(updateSession)
app.use(sessionToBody)

const client = request(app.listen())
let startTime
let ttl

beforeEach(() => {
store.clear()
ttl = 24 * 3600 * 1000 // one day in milliseconds
startTime = Date.now()
})

it('setMaxAge(ms) should set maxAge and ttl', done => {
client.get('/maxage/0').expect(200).end((err1, res1) => {
Expand All @@ -413,7 +439,7 @@ describe('session with async store', () => {
destroy: [],
})

const maxAge = 1000
const maxAge = 123
client.get(`/maxage/${maxAge}`).expect(200).end((err2, res2) => {
if (err2) done(err2)
validateCookie(res2, key)
Expand Down
32 changes: 29 additions & 3 deletions test/generator_store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ const updateSession = (ctx, next) => {
break
default:
if (ctx.url.startsWith('/maxage/')) {
const ms = parseInt(ctx.url.replace('/maxage/', ''), 10)
ctx.sessionHandler.setMaxAge(ms)
ctx.ms = parseInt(ctx.url.replace('/maxage/', ''), 10)
ctx.session.time = Date.now()
}
}
Expand Down Expand Up @@ -399,6 +398,33 @@ describe('session with generator store', () => {
})
})
})
})

describe('generator store with dynamic cookie options', () => {
const app = new Koa()
const key = 'koa:sess'
const store = new SpyStore()

app.use(session({
store,
cookie: ctx => { // eslint-disable-line arrow-body-style
return {
maxAge: ctx.ms,
}
},
}))
app.use(updateSession)
app.use(sessionToBody)

const client = request(app.listen())
let startTime
let ttl

beforeEach(() => {
store.clear()
ttl = 24 * 3600 * 1000 // one day in milliseconds
startTime = Date.now()
})

it('setMaxAge(ms) should set maxAge and ttl', done => {
client.get('/maxage/0').expect(200).end((err1, res1) => {
Expand All @@ -413,7 +439,7 @@ describe('session with generator store', () => {
destroy: [],
})

const maxAge = 1000
const maxAge = 123
client.get(`/maxage/${maxAge}`).expect(200).end((err2, res2) => {
if (err2) done(err2)
validateCookie(res2, key)
Expand Down

0 comments on commit 0ca89ce

Please sign in to comment.