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}