Software /
code /
prosody
Comparison
plugins/mod_http_file_share.lua @ 12708:9953ac7b0c15
mod_http_file_share: Switch to new util.jwt API
Some changes/improvements in this commit:
- Default token lifetime is now 3600s (from 300s)
- Tokens are only validated once per upload
- "iat"/"exp" are handled automatically by util.jwt
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Mon, 11 Jul 2022 13:49:47 +0100 |
parent | 12444:b33558969b3e |
child | 12722:cd993fd7b60d |
comparison
equal
deleted
inserted
replaced
12707:f75235110045 | 12708:9953ac7b0c15 |
---|---|
10 local t_insert = table.insert; | 10 local t_insert = table.insert; |
11 local jid = require "util.jid"; | 11 local jid = require "util.jid"; |
12 local st = require "util.stanza"; | 12 local st = require "util.stanza"; |
13 local url = require "socket.url"; | 13 local url = require "socket.url"; |
14 local dm = require "core.storagemanager".olddm; | 14 local dm = require "core.storagemanager".olddm; |
15 local jwt = require "util.jwt"; | |
16 local errors = require "util.error"; | 15 local errors = require "util.error"; |
17 local dataform = require "util.dataforms".new; | 16 local dataform = require "util.dataforms".new; |
18 local urlencode = require "util.http".urlencode; | 17 local urlencode = require "util.http".urlencode; |
19 local dt = require "util.datetime"; | 18 local dt = require "util.datetime"; |
20 local hi = require "util.human.units"; | 19 local hi = require "util.human.units"; |
41 local file_types = module:get_option_set(module.name .. "_allowed_file_types", {}); | 40 local file_types = module:get_option_set(module.name .. "_allowed_file_types", {}); |
42 local safe_types = module:get_option_set(module.name .. "_safe_file_types", {"image/*","video/*","audio/*","text/plain"}); | 41 local safe_types = module:get_option_set(module.name .. "_safe_file_types", {"image/*","video/*","audio/*","text/plain"}); |
43 local expiry = module:get_option_number(module.name .. "_expires_after", 7 * 86400); | 42 local expiry = module:get_option_number(module.name .. "_expires_after", 7 * 86400); |
44 local daily_quota = module:get_option_number(module.name .. "_daily_quota", file_size_limit*10); -- 100 MB / day | 43 local daily_quota = module:get_option_number(module.name .. "_daily_quota", file_size_limit*10); -- 100 MB / day |
45 local total_storage_limit = module:get_option_number(module.name.."_global_quota", unlimited); | 44 local total_storage_limit = module:get_option_number(module.name.."_global_quota", unlimited); |
45 | |
46 local create_jwt, verify_jwt = require "util.jwt".init("HS256", secret); | |
46 | 47 |
47 local access = module:get_option_set(module.name .. "_access", {}); | 48 local access = module:get_option_set(module.name .. "_access", {}); |
48 | 49 |
49 if not external_base_url then | 50 if not external_base_url then |
50 module:depends("http"); | 51 module:depends("http"); |
167 | 168 |
168 return true; | 169 return true; |
169 end | 170 end |
170 | 171 |
171 function get_authz(slot, uploader, filename, filesize, filetype) | 172 function get_authz(slot, uploader, filename, filesize, filetype) |
172 local now = os.time(); | 173 return create_jwt({ |
173 return jwt.sign(secret, { | |
174 -- token properties | 174 -- token properties |
175 sub = uploader; | 175 sub = uploader; |
176 iat = now; | |
177 exp = now+300; | |
178 | 176 |
179 -- slot properties | 177 -- slot properties |
180 slot = slot; | 178 slot = slot; |
181 expires = expiry >= 0 and (now+expiry) or nil; | 179 expires = expiry >= 0 and (os.time()+expiry) or nil; |
182 -- file properties | 180 -- file properties |
183 filename = filename; | 181 filename = filename; |
184 filesize = filesize; | 182 filesize = filesize; |
185 filetype = filetype; | 183 filetype = filetype; |
186 }); | 184 }); |
247 return true; | 245 return true; |
248 end | 246 end |
249 | 247 |
250 function handle_upload(event, path) -- PUT /upload/:slot | 248 function handle_upload(event, path) -- PUT /upload/:slot |
251 local request = event.request; | 249 local request = event.request; |
252 local authz = request.headers.authorization; | 250 local upload_info = request.http_file_share_upload_info; |
253 if authz then | 251 |
254 authz = authz:match("^Bearer (.*)") | 252 if not upload_info then -- Initial handling of request |
255 end | 253 local authz = request.headers.authorization; |
256 if not authz then | 254 if authz then |
257 module:log("debug", "Missing or malformed Authorization header"); | 255 authz = authz:match("^Bearer (.*)") |
258 event.response.headers.www_authenticate = "Bearer"; | 256 end |
259 return 401; | 257 if not authz then |
260 end | 258 module:log("debug", "Missing or malformed Authorization header"); |
261 local authed, upload_info = jwt.verify(secret, authz); | 259 event.response.headers.www_authenticate = "Bearer"; |
262 if not (authed and type(upload_info) == "table" and type(upload_info.exp) == "number") then | 260 return 401; |
263 module:log("debug", "Unauthorized or invalid token: %s, %q", authed, upload_info); | 261 end |
264 return 401; | 262 local authed, authed_upload_info = verify_jwt(authz); |
265 end | 263 if not authed then |
266 if not request.body_sink and upload_info.exp < os.time() then | 264 module:log("debug", "Unauthorized or invalid token: %s, %q", authz, authed_upload_info); |
267 module:log("debug", "Authorization token expired on %s", dt.datetime(upload_info.exp)); | 265 return 401; |
268 return 410; | 266 end |
269 end | 267 if not path or upload_info.slot ~= path:match("^[^/]+") then |
270 if not path or upload_info.slot ~= path:match("^[^/]+") then | 268 module:log("debug", "Invalid upload slot: %q, path: %q", upload_info.slot, path); |
271 module:log("debug", "Invalid upload slot: %q, path: %q", upload_info.slot, path); | 269 return 400; |
272 return 400; | 270 end |
273 end | 271 if request.headers.content_length and tonumber(request.headers.content_length) ~= upload_info.filesize then |
274 if request.headers.content_length and tonumber(request.headers.content_length) ~= upload_info.filesize then | 272 return 413; |
275 return 413; | 273 -- Note: We don't know the size if the upload is streamed in chunked encoding, |
276 -- Note: We don't know the size if the upload is streamed in chunked encoding, | 274 -- so we also check the final file size on completion. |
277 -- so we also check the final file size on completion. | 275 end |
276 upload_info = authed_upload_info; | |
277 request.http_file_share_upload_info = upload_info; | |
278 end | 278 end |
279 | 279 |
280 local filename = get_filename(upload_info.slot, true); | 280 local filename = get_filename(upload_info.slot, true); |
281 | 281 |
282 do | 282 do |