Mon, 22 Nov 2021 12:16:17 +0000
Upgrade runtime environment to Debian bullseye
0 | 1 | local http_server = require"net.http.server"; |
2 | local files = require "net.http.files"; | |
3 | local http_codes = require"net.http.codes"; | |
4 | local promise = require "util.promise"; | |
5 | local url = require"socket.url"; | |
6 | local uuid = require"util.uuid"; | |
7 | local web = require "util.web"; | |
8 | local render = require "render".render; | |
9 | local ok, json = pcall(require, "cjson"); | |
10 | if not ok then json = require"util.json"; end | |
11 | local errors = require "util.error".init("web", {}); | |
12 | ||
13 | local templates; | |
14 | local config; | |
15 | ||
16 | local log = require "util.logger".init("web"); | |
17 | ||
18 | local usercookie = require"util.usercookie"; | |
19 | local secret = uuid.generate(); | |
20 | ||
21 | local check_auth = require "app.auth".check_auth; | |
22 | ||
23 | local function set_auth_cookie(username, response) | |
24 | local expires = config.cookie_ttl or 604800; | |
25 | local cookie = usercookie.generate(username, os.time()+expires, secret); | |
26 | cookie = "remember=".. cookie .. "; Path="..config.base_path | |
27 | .."; Max-Age="..tostring(expires).."; HttpOnly"; | |
28 | return web.set_cookie(response.headers, cookie); | |
29 | end | |
30 | ||
31 | local csrf_token_len = #uuid.generate(); | |
32 | ||
33 | local function check_csrf(event, viewdata) | |
34 | local request, response = event.request, event.response; | |
35 | web.unpack_cookies(request); | |
36 | local csrf_token = request.cookies.csrf_token; | |
37 | log("debug", "csrf_token=%s", tostring(csrf_token)); | |
38 | if csrf_token and #csrf_token == csrf_token_len then | |
39 | viewdata.csrf_token = csrf_token; | |
40 | else | |
41 | csrf_token = uuid.generate(); | |
42 | viewdata.csrf_token = csrf_token; | |
43 | web.set_cookie(response.headers, "csrf_token=" .. csrf_token .. "; Path="..config.base_path.."; HttpOnly"); | |
44 | end | |
45 | end | |
46 | ||
47 | local function wrap_handler(f, t, path_prefix_len) | |
48 | return function (event) | |
49 | log("debug", "Check auth..."); | |
50 | local authed = check_auth(event.request, config); | |
51 | log("debug", "Checked, %s", authed); | |
52 | event.config = config; | |
53 | local sub_path = nil; | |
54 | if path_prefix_len then | |
55 | sub_path = event.request.path:sub(path_prefix_len+2); | |
56 | end | |
57 | local p = f(event, sub_path); | |
58 | return promise.resolve(p):next(function (resp) | |
59 | if type(resp) == "table" then | |
60 | local headers = event.response.headers; | |
61 | local accept = event.request.headers.accept or ""; | |
62 | web.add_header(headers, "vary", "Accept, Cookie"); | |
63 | if accept:find"application/json" then | |
64 | headers.content_type = "application/json"; | |
65 | return json.encode(resp); | |
66 | elseif t then | |
67 | if authed or event.needs_csrf then check_csrf(event, resp); end | |
68 | resp.authenticated = event.request.authenticated; | |
69 | resp.config = config; | |
70 | resp = render(t, resp); | |
71 | if not headers.content_type then | |
72 | headers.content_type = "text/html; charset=utf-8"; | |
73 | end | |
74 | else | |
75 | return resp; | |
76 | end | |
77 | end | |
78 | return resp; | |
79 | end):catch(function (err) | |
80 | log("error", "Failed inside handler wrapper: %s", json.encode(err)); | |
81 | return promise.reject(errors.wrap(err)); | |
82 | end); | |
83 | end | |
84 | end | |
85 | ||
86 | local function handle_error(error, config) | |
87 | if not error.response then | |
88 | -- Top-level error, return text string | |
89 | if config.debug then | |
90 | return error.private_message; | |
91 | end | |
92 | return "An internal error occurred."; | |
93 | end | |
94 | ||
95 | error.response.headers.content_type = "text/html; charset=utf-8"; | |
96 | ||
97 | log("warn", "HTTP error handler triggered (debug: %s): %d %s", not not config.debug, error.code, json.encode(error.error)); | |
98 | ||
99 | local err = errors.wrap(error.err); | |
100 | ||
101 | local r = render(templates.error or "{text}", { | |
102 | config = config; | |
103 | code = err.code or error.code; | |
104 | long = err.condition or http_codes[error.code]; | |
105 | text = err.text or "An unexpected error occurred. Please try again later."; | |
106 | id = err.instance_id; | |
107 | internal_error = config.debug and error.error and error.error.context.wrapped_error; | |
108 | }); | |
109 | return r; | |
110 | end | |
111 | ||
112 | local function redirect(to, code) | |
113 | code = code or 303; | |
114 | return function (event) | |
115 | if event.request.url.query then | |
116 | event.response.headers.location = to.."?"..event.request.url.query; | |
117 | else | |
118 | event.response.headers.location = to; | |
119 | end | |
120 | return code; | |
121 | end | |
122 | end | |
123 | ||
124 | local function size_only(request, data) | |
125 | request.headers.content_size = #data; | |
126 | return 200; | |
127 | end | |
128 | ||
129 | local function head_handler(handler) | |
130 | return function (event) | |
131 | event.send = size_only; | |
132 | return handler(event); | |
133 | end | |
134 | end | |
135 | ||
136 | local function register_handlers(handlers, event_base, path_prefix) | |
137 | for method_path, handler in pairs(handlers) do | |
138 | if type(handler) == "table" then | |
139 | register_handlers(handler, event_base, (path_prefix and (path_prefix.."/") or "")..method_path); | |
140 | else | |
141 | local method, path, wildcard = method_path:match"^(%w+)_(.-)(_?)$"; | |
142 | method = method:upper(); | |
143 | wildcard = wildcard == "_" and (path == "" and "*" or "/*") or ""; | |
144 | local template_name = path; | |
145 | if path == "" then | |
146 | template_name = "index"; | |
147 | end | |
148 | ||
149 | if path_prefix then | |
150 | path = path_prefix .. "/" .. path; | |
151 | end | |
152 | ||
153 | if templates[template_name] then | |
154 | log("debug", "handler for %s %s (wildcard: %s) with template %s", method, path, wildcard, template_name); | |
155 | else | |
156 | log("debug", "No template (%s) for %s", template_name, path); | |
157 | end | |
158 | ||
159 | local path_prefix_len; | |
160 | if wildcard == "/*" then | |
161 | path_prefix_len = #path+1; | |
162 | end | |
163 | ||
164 | handler = wrap_handler(handler, templates[template_name], path_prefix_len); | |
165 | local head_event = event_base:format("HEAD", path, wildcard); | |
166 | http_server.add_handler(head_event, head_handler(handler)); | |
167 | ||
168 | local event_name = event_base:format(method, path, wildcard); | |
169 | http_server.add_handler(event_name, handler); | |
170 | log("debug", "Handler for %s added with template %s", event_name, template_name); | |
171 | end | |
172 | end | |
173 | end | |
174 | ||
175 | local function init(_config, events) | |
176 | config = _config or {}; | |
177 | ||
178 | templates = require "templates".init(config); | |
179 | ||
180 | local base_url = url.parse(config.base_url or "http://localhost:8006/"); | |
181 | local base_path = url.parse_path(config.base_path or base_url.path or "/"); | |
182 | base_path.is_directory = true; | |
183 | base_url.path = url.build_path(base_path); | |
184 | config.base_host = base_url.host; | |
185 | config.base_path = base_url.path; | |
186 | config.base_url = url.build(base_url); | |
187 | local event_base = ("%%s %s%s%%s%%s"):format(base_url.host, base_url.path); | |
188 | ||
189 | local handlers = require "app.routes"; | |
190 | ||
191 | handlers.get_static_ = files.serve { path = "./static", http_base = base_url.path.."static" }; | |
192 | ||
193 | http_server.add_host(base_url.host); | |
194 | http_server.set_default_host(base_url.host); | |
195 | http_server.add_handler("http-error", function (error) | |
196 | return handle_error(error, config) | |
197 | end); | |
198 | ||
199 | register_handlers(handlers, event_base); | |
200 | ||
201 | local listen_port = config.listen_port or 8007; | |
202 | local listen_interface = config.listen_interface or "*"; | |
203 | assert(http_server.listen_on(listen_port, listen_interface)); | |
204 | log("info", "Serving web interface on http://localhost:%d/", listen_port); | |
205 | end | |
206 | ||
207 | return { | |
208 | init = init; | |
209 | render = render; | |
210 | } |