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