summaryrefslogtreecommitdiff
path: root/web/session.lua
diff options
context:
space:
mode:
authorrebecca <ubq323@ubq323.website>2026-09-17 20:54:44 +0100
committerrebecca <ubq323@ubq323.website>2026-09-17 20:54:44 +0100
commit6c5d5fec129a29c6250b094f46881cfa5aaadcc7 (patch)
treeb66161fccfa91180e937f0689763f2124a4af97b /web/session.lua
parent7f60b7c4e8d99f97a0a24b60bd4c4fd15697a4bb (diff)
web: import from m1msq 15b348f
Diffstat (limited to 'web/session.lua')
-rw-r--r--web/session.lua59
1 files changed, 59 insertions, 0 deletions
diff --git a/web/session.lua b/web/session.lua
new file mode 100644
index 0000000..3c6c297
--- /dev/null
+++ b/web/session.lua
@@ -0,0 +1,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}