summaryrefslogtreecommitdiff
path: root/web/session.lua
blob: 3c6c29722a1a62659394d985ace4f91a3a5ea87c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
local base64 = require'base64'
local hmac = require'openssl.hmac'
local pprint=require'pprint'
local json = require'dkjson'
local http_cookie = require'http.cookie'
local unpack=unpack or table.unpack

local t_enc = base64.makeencoder('-','_')
local t_dec = base64.makedecoder('-','_')
local function enc(s) return base64.encode(s,t_enc):gsub('=','') end
local function dec(s) return base64.decode(s..("="):rep((4-#s)%4),t_dec) end

local function encode(app, fields)
	local header = enc(json.encode{alg="HS256",typ="JWT"})
	fields.exp = os.time()+app.session_ttl
	local payload = enc(json.encode(fields))
	local front = header..'.'..payload
	local h = hmac.new(app.secret_key,'sha256')
	local sig = enc(h:final(front))
	return front..'.'..sig
end

local function _decode(app, token)
	local parts = {}
	for s in token:gmatch("[^%.]+") do table.insert(parts,s) end
	assert(#parts==3) local front=parts[1]..'.'..parts[2]
	local dparts = {} for i,v in ipairs(parts) do dparts[i]=dec(v) end
	local header,payload,sig = unpack(dparts)
	header=assert(json.decode(header)) payload=assert(json.decode(payload))
	assert(header.typ=='JWT' and header.alg=='HS256','bad header')
	assert(type(payload.exp)=='number' and payload.exp > os.time(),'expired')
	local nsig = hmac.new(app.secret_key,'sha256'):final(front)
	assert(nsig==sig,'bad signature')
	return payload
end
local function decode(...) return pcall(_decode,...) end

local function pre_req(req)
	if req.cookies.SESSION then
		local ok, val = decode(req.app,req.cookies.SESSION)
		if ok then req.session = val  return
		else print('\trejected session: '..val) end
	end
	req.session = setmetatable({},{__jsontype='object'})
end
local function post_req(req, body, headers, status)
	if req.session then headers:append('set-cookie',
		http_cookie.bake('SESSION',encode(req.app,req.session),
			os.time()+req.app.session_ttl, nil, '/'))
end end

local function install(app)
	assert(app.session_ttl, 'missing app.session_ttl')
	assert(app.secret_key, 'missing app.secret_key')
	app:add_hook('pre_req',pre_req)
	app:add_hook('post_req',post_req)
end

return {install=install}