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