Thu, 22 Jun 2023 21:31:36 +0100
web: Add a few new helper functions
local http_util = require "util.http"; local usercookie = require"util.usercookie"; local uuid = require "util.uuid"; local function unpack_cookies(request) if not request.cookies then request.cookies = usercookie.decode(request.headers.cookie); end end local post_parsers = { ["application/x-www-form-urlencoded"] = http_util.formdecode; }; local function parse_body(request) local content_type = request.headers.content_type; if not content_type then --log("warn", "No Content-Type header sent"); return nil, 400; end local post_parser = post_parsers[content_type]; if not post_parser then --log("warn", "Don't know how to parse %s", content_type); return nil, 415; end local post_body = post_parser(request.body); if type(post_body) ~= "table" then --log("warn", "Could not parse %s %q, got %s", content_type, request.body, type(post_body)); return nil, 415; end return post_body; end local csrf_token_len = #uuid.generate(); local function validate_csrf(csrf_token, request) if request.headers.origin == nil and request.headers.referer == nil then return true; -- Probably a non-browser request end if not (csrf_token and #csrf_token == csrf_token_len) then return false; end unpack_cookies(request); return request.cookies.csrf_token == csrf_token; end local function parse_body_and_csrf(request) local post_body, err = parse_body(request); if not post_body then return nil, err; end if not validate_csrf(post_body.csrf_token, request) then --log("warn", "CSRF error (token: '%s', cookie: '%s')", -- tostring(post_body.csrf_token), tostring(request.headers.cookie)); return nil, 400; end return post_body; end local function parse_query(request) local q = request.url.query; return q and http_util.formdecode(q) or nil; end -- Cookies local function add_header(headers, header, value) if headers[header] then headers[header] = headers[header] .. ", " .. value; else headers[header] = value; end end local function prefix_header(headers, header, value) if headers[header] then headers[header] = value .. ", " .. headers[header]; else headers[header] = value; end end local response_mt = {}; local function redirect(to, code) return setmetatable({ status_code = code or 303; headers = { Location = to; } }, response_mt); end local function is_response(obj) return getmetatable(obj) == response_mt; end local function set_cookie(headers, cookie, opts) if opts then local params = {""}; if opts.path then table.insert(params, "Path="..opts.path); end if opts.ttl then table.insert(params, ("Max-Age=%d"):format(opts.ttl)); end if opts.http_only then table.insert(params, "HttpOnly"); end if opts.secure then table.insert(params, "Secure"); end if #params > 1 then cookie = cookie .. table.concat(params, "; "); end end prefix_header(headers, "set_cookie", cookie); end local function set_auth_cookie(username, response, secret) local expires = config.cookie_ttl or 604800; local cookie = usercookie.generate(username, os.time()+expires, secret); cookie = "__Host-auth=".. cookie .. "; Path="..config.base_path .."; Max-Age="..tostring(expires).."; Secure; HttpOnly"; return set_cookie(response.headers, cookie); end local function verify_auth_cookie(request, secret) unpack_cookies(request); request.cookies.auth = usercookie.verify(request.cookies["__Host-auth"], secret); end return { unpack_cookies = unpack_cookies; validate_csrf = validate_csrf; parse_body_and_csrf = parse_body_and_csrf; parse_body = parse_body; parse_query = parse_query; add_header = add_header; prefix_header = prefix_header; redirect = redirect; set_cookie = set_cookie; set_auth_cookie = set_auth_cookie; verify_auth_cookie = verify_auth_cookie; is_response = is_response; };