UNPKG

3.66 kBtext/coffeescriptView Raw
1hat = require 'hat'
2
3store = require './store'
4
5nop = (req, res, next) -> next()
6bind = (fst, snd) -> (req, res, next) -> fst req, res, -> snd req, res, next
7compose = (mw) -> mw.reduce ((state, current) -> bind state, current), nop
8
9dayDuration = 1000 * 60 * 60 * 24
10
11encodeCookie = (key, value, opts = {}) ->
12 pairs = []
13 pairs.push "#{key}=#{value}"
14 pairs.push "Max-Age=#{opts['max-age']}" if opts['max-age']
15 pairs.push "Domain=#{opts.domain}" if opts.domain
16 pairs.push "Path=#{opts.path}" if opts.path
17 pairs.push "Expires=#{opts.expires}" if opts.expires
18 pairs.push "HttpOnly" if opts.httpOnly
19 pairs.push "Secure" if opts.secure
20 return pairs.join '; '
21
22setCookieMiddleware = (req, res, next) ->
23 throw new Error 'missing req.cookies' unless req.cookies?
24 throw new Error 'missing opts' unless @opts
25 req.cookies.session_id ?= hat 128
26 req.cookies.session_secret ?= hat 128
27
28 for key, value of req.cookies
29 res.setHeader 'Set-Cookie', encodeCookie key, value, @opts
30 next()
31
32syncMiddleware = (req, res, next) ->
33 # load session
34 {session_id, session_secret} = req.cookies
35 store = @store
36 throw new Error 'missing datastore' unless store?
37 throw new Error 'missing store.setByIdAndSecret' unless store?.setByIdAndSecret?
38 throw new Error 'missing store.getByIdAndSecret' unless store?.getByIdAndSecret?
39
40 # hook original request.end
41 origEnd = res.end
42 res.end = (data, encoding) ->
43 res.end = origEnd
44 store.setByIdAndSecret session_id, session_secret, req.session, (err) ->
45 throw err if err?
46 res.end data, encoding
47
48 # load session data from database
49 store.getByIdAndSecret session_id, session_secret, (err, session) ->
50 throw err if err?
51 req.session = session
52 next()
53
54
55cleanupMiddleware = (req, res, next) ->
56 throw new Error 'missing store.deleteOlderThen' unless @store?.deleteOlderThen?
57 return next() if Math.random() > (@probability or 0.001)
58 console.info 'run session cleanup'
59 @days ?= 7
60 today = new Date()
61 cutofDate = new Date(+new Date - @days * dayDuration)
62 @store.deleteOlderThen cutofDate, (err) ->
63 return console.err err if err?
64 console.info 'session cleanup finished'
65 next()
66
67extendObject = (obj, key, value) ->
68 opts = {}
69 opts[key] = value: value
70 Object.create obj, opts
71
72module.exports =
73 cookieHandler: setCookieMiddleware
74 syncHandler: syncMiddleware
75 cleanupHandler: cleanupMiddleware
76
77 setCookieHandler: (handler) -> Object.create @, cookieHandler: value: handler
78 setCleanupeHandler: (handler) -> Object.create @, cleanupHandler: value: handler
79 setSyncHandler: (handler) -> Object.create @, syncHandler: value: handler
80
81 opts: {}
82 set: (key, value) -> extendObject @, key, value
83 setOpts: (key, value) -> extendObject @opts, key, value
84
85 middleware: ->
86 compose [
87 @cookieHandler.bind opts: @opts
88 @syncHandler.bind store: @_store
89 @cleanupHandler.bind store: @_store
90 ]
91
92 store: (store) -> Object.create @, {_store: value: store}
93 domain: (domain) -> @set 'opts', extendObject @opts, 'domain', domain
94 maxAge: (maxAge) ->
95 opts = Object.create @opts, {'max-age': value: maxAge}
96 Object.create @, opts: value: opts
97 path: (path) -> @set 'opts', @setOpts 'path', path
98 expires: (expires) -> @set 'opts', @setOpts 'expires', expires
99 isHttpOnly: (isHttpOnly) -> @set 'opts', @setOpts 'httpOnly', isHttpOnly
100 isSecure: (isSecure) -> @set 'opts', @setOpts 'secure', isSecure
101
102module.exports.defaultStores = store