aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/luarocks/cmd/upload.lua37
-rw-r--r--src/luarocks/cmd/upload.tl35
-rw-r--r--src/luarocks/core/types/args.d.tl183
-rw-r--r--src/luarocks/upload/api.lua109
-rw-r--r--src/luarocks/upload/api.tl111
5 files changed, 318 insertions, 157 deletions
diff --git a/src/luarocks/cmd/upload.lua b/src/luarocks/cmd/upload.lua
index e4319bb1..9cc98fd6 100644
--- a/src/luarocks/cmd/upload.lua
+++ b/src/luarocks/cmd/upload.lua
@@ -1,4 +1,4 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string 1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local upload = { Response = { version = {} } } 2local upload = { Response = { version = {} } }
3 3
4 4
@@ -40,6 +40,8 @@ function upload.add_to_parser(parser)
40 cmd:option("--temp-key", "Use the given a temporary API key in this " .. 40 cmd:option("--temp-key", "Use the given a temporary API key in this " ..
41 "invocation only. It will not be stored."): 41 "invocation only. It will not be stored."):
42 argname("<key>") 42 argname("<key>")
43 cmd:option("--code", "Two-factor code (or set $LUAROCKS_TFA_CODE."):
44 argname("<code>")
43 cmd:flag("--force", "Replace existing rockspec if the same revision of a " .. 45 cmd:flag("--force", "Replace existing rockspec if the same revision of a " ..
44 "module already exists. This should be used only in case of upload " .. 46 "module already exists. This should be used only in case of upload " ..
45 "mistakes: when updating a rockspec, increment the revision number " .. 47 "mistakes: when updating a rockspec, increment the revision number " ..
@@ -52,11 +54,44 @@ local function is_dev_version(version)
52 return version:match("^dev") or version:match("^scm") 54 return version:match("^dev") or version:match("^scm")
53end 55end
54 56
57local function prompt_tfa(api)
58 util.printout("Two-factor authentication required for this account.")
59 local initial = os.getenv("LUAROCKS_TFA_CODE") or api.code
60 local attempts = 0
61 while true do
62 local code = initial
63 initial = nil
64 if not code then
65 util.printout("Enter 2FA code: ")
66 code = io.stdin:read("*l")
67 if not (code and code ~= "") then
68 return nil, "no code provided"
69 end
70 end
71 local res = api:raw_method("verify_tfa", nil, {
72 code = code,
73 })
74 if res.success and res.tfa_token then
75 api.tfa_token = res.tfa_token
76 util.printout("Verified.")
77 return true
78 end
79 attempts = attempts + 1
80 local err = res.errors and table.concat(res.errors, ", ") or "verification failed"
81 util.printout(tostring(err))
82 if attempts >= 3 then
83 return nil, "two-factor verification failed after " .. tostring(attempts) .. " attempt(s)"
84 end
85 end
86end
87
55function upload.command(args) 88function upload.command(args)
56 local api, err = Api.new(args) 89 local api, err = Api.new(args)
57 if not api then 90 if not api then
58 return nil, err 91 return nil, err
59 end 92 end
93 api.code = args.code
94 api.on_tfa_required = prompt_tfa
60 if cfg.verbose then 95 if cfg.verbose then
61 api.debug = true 96 api.debug = true
62 end 97 end
diff --git a/src/luarocks/cmd/upload.tl b/src/luarocks/cmd/upload.tl
index edfe4f89..52957de1 100644
--- a/src/luarocks/cmd/upload.tl
+++ b/src/luarocks/cmd/upload.tl
@@ -40,6 +40,8 @@ function upload.add_to_parser(parser: Parser)
40 cmd:option("--temp-key", "Use the given a temporary API key in this ".. 40 cmd:option("--temp-key", "Use the given a temporary API key in this "..
41 "invocation only. It will not be stored.") 41 "invocation only. It will not be stored.")
42 :argname("<key>") 42 :argname("<key>")
43 cmd:option("--code", "Two-factor code (or set $LUAROCKS_TFA_CODE.")
44 :argname("<code>")
43 cmd:flag("--force", "Replace existing rockspec if the same revision of a ".. 45 cmd:flag("--force", "Replace existing rockspec if the same revision of a "..
44 "module already exists. This should be used only in case of upload ".. 46 "module already exists. This should be used only in case of upload "..
45 "mistakes: when updating a rockspec, increment the revision number ".. 47 "mistakes: when updating a rockspec, increment the revision number "..
@@ -52,11 +54,44 @@ local function is_dev_version(version: string): string
52 return version:match("^dev") or version:match("^scm") 54 return version:match("^dev") or version:match("^scm")
53end 55end
54 56
57local function prompt_tfa(api: Api.Api): boolean, string
58 util.printout("Two-factor authentication required for this account.")
59 local initial = os.getenv("LUAROCKS_TFA_CODE") or api.code
60 local attempts = 0
61 while true do
62 local code = initial
63 initial = nil
64 if not code then
65 util.printout("Enter 2FA code: ")
66 code = io.stdin:read("*l")
67 if not (code and code ~= "") then
68 return nil, "no code provided"
69 end
70 end
71 local res = api:raw_method("verify_tfa", nil, {
72 code = code
73 })
74 if res.success and res.tfa_token then
75 api.tfa_token = res.tfa_token as string
76 util.printout("Verified.")
77 return true
78 end
79 attempts = attempts + 1
80 local err = res.errors and table.concat(res.errors as {string}, ", ") or "verification failed"
81 util.printout(tostring(err))
82 if attempts >= 3 then
83 return nil, "two-factor verification failed after " .. tostring(attempts) .. " attempt(s)"
84 end
85 end
86end
87
55function upload.command(args: Args): boolean, string, string 88function upload.command(args: Args): boolean, string, string
56 local api, err = Api.new(args) 89 local api, err = Api.new(args)
57 if not api then 90 if not api then
58 return nil, err 91 return nil, err
59 end 92 end
93 api.code = args.code
94 api.on_tfa_required = prompt_tfa
60 if cfg.verbose then 95 if cfg.verbose then
61 api.debug = true 96 api.debug = true
62 end 97 end
diff --git a/src/luarocks/core/types/args.d.tl b/src/luarocks/core/types/args.d.tl
index 510855dd..5aac3f55 100644
--- a/src/luarocks/core/types/args.d.tl
+++ b/src/luarocks/core/types/args.d.tl
@@ -1,111 +1,112 @@
1local record args 1local record args
2 record Args 2 record Args
3 tree: string 3 add_server: string
4 global: boolean 4 all: boolean
5 deps_mode: string 5 api_key: string
6 ["local"]: boolean 6 append: boolean
7 project_tree: string 7 arch: string
8 server: string 8 args: {string}
9 dev: boolean 9 binary: boolean
10 only_server: string
11 verbose: string
12 lua_version: string
13 lua_dir: string
14 no_project: boolean
15 input: {string}
16 nodeps: boolean
17 timeout: number
18 command: string
19 key: string
20 value: string
21 only_sources: string
22 no_manifest: boolean
23 force_lock: boolean
24 rockspec: string
25 namespace: string
26 pack_binary_rock: boolean
27 only_deps: boolean
28 branch: string 10 branch: string
29 verify: boolean 11 build_deps: boolean
30 check_lua_versions: boolean 12 check_lua_versions: boolean
31 pin: boolean 13 code: string
32 no_install: boolean 14 command: string
33 sign: boolean 15 debug: boolean
34 no_doc: boolean 16 deps: boolean
35 keep: boolean 17 deps_mode: string
18 detailed: string
19 dev: boolean
20 dir: string
21 filter: string
36 force: boolean 22 force: boolean
37 force_fast: boolean 23 force_fast: boolean
38 rock: string 24 force_lock: boolean
39 version: string 25 full: boolean
40 scope: string 26 global: boolean
41 lua_incdir: string
42 lua_libdir: string
43 lua_ver: string
44 system_config: string
45 user_config: string
46 rock_trees: string
47 unset: boolean
48 json: boolean
49 home: boolean 27 home: boolean
50 porcelain: boolean
51 list: boolean
52 name: string
53 all: boolean
54 source: string
55 arch: string
56 location: string
57 tag: string
58 output: string
59 homepage: string 28 homepage: string
60 rockspec_format: string 29 index: boolean
61 summary: string 30 input: {string}
62 detailed: string 31 issues: boolean
32 json: boolean
33 keep: boolean
34 key: string
35 labels: boolean
36 lib: string
63 license: string 37 license: string
38 list: boolean
39 ["local"]: boolean
40 local_tree: string
41 location: string
42 lr_bin: string
43 lr_cpath: string
44 lr_path: string
45 lua_dir: string
46 lua_incdir: string
47 lua_libdir: string
64 lua_versions: string 48 lua_versions: string
65 lib: string 49 lua_version: string
66 no_gitignore: boolean 50 lua_ver: string
67 no_wrapper_scripts: boolean 51 modname: string
68 wrapper_dir: string 52 modules: boolean
69 reset: boolean 53 mversion: boolean
70 filter: string 54 namespace: string
71 outdated: boolean 55 name: string
72 new_version: string
73 dir: string
74 new_url: string 56 new_url: string
75 lr_path: string 57 new_version: string
76 lr_cpath: string
77 lr_bin: string
78 full: boolean
79 append: boolean
80 no_bin: boolean 58 no_bin: boolean
59 nodeps: boolean
60 no_doc: boolean
61 no_gitignore: boolean
62 no_install: boolean
63 no_manifest: boolean
64 no_project: boolean
65 no_refresh: boolean
66 no_wrapper_scripts: boolean
81 old_versions: boolean 67 old_versions: boolean
82 binary: boolean 68 only_deps: boolean
83 rock_tree: boolean 69 only_server: string
84 rock_namespace: boolean 70 only_sources: string
71 outdated: boolean
72 output: string
73 pack_binary_rock: boolean
74 pin: boolean
75 porcelain: boolean
76 prepare: boolean
77 project_tree: string
78 repository: string
79 reset: boolean
85 rock_dir: boolean 80 rock_dir: boolean
86 rock_license: boolean 81 rock_license: boolean
87 issues: boolean 82 rock_namespace: boolean
88 labels: boolean 83 rockspec_format: string
89 modules: boolean 84 rockspec: string
90 deps: boolean 85 rocks: {string}
91 build_deps: boolean 86 rock: string
92 test_deps: boolean 87 rock_tree: boolean
93 mversion: boolean 88 rock_trees: string
94 test_type: string 89 scope: string
95 args: {string} 90 server: string
96 prepare: boolean 91 sign: boolean
97 src_rock: string
98 skip_pack: boolean 92 skip_pack: boolean
99 modname: string 93 source: string
100 add_server: string 94 src_rock: string
101 no_refresh: boolean 95 summary: string
102 index: boolean 96 system_config: string
103 repository: string 97 tag: string
104 local_tree: string
105 temp_key: string 98 temp_key: string
106 api_key: string 99 test_deps: boolean
107 debug: boolean 100 test_type: string
108 rocks: {string} 101 timeout: number
102 tree: string
103 unset: boolean
104 user_config: string
105 value: string
106 verbose: string
107 verify: boolean
108 version: string
109 wrapper_dir: string
109 end 110 end
110end 111end
111 112
diff --git a/src/luarocks/upload/api.lua b/src/luarocks/upload/api.lua
index 3074965b..1251a9b9 100644
--- a/src/luarocks/upload/api.lua
+++ b/src/luarocks/upload/api.lua
@@ -1,4 +1,8 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local type = type; local api = { Configuration = {}, Api = {} } 1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local type = type; local api = { Configuration = {}, Api = {} }
2
3
4
5
2 6
3 7
4 8
@@ -55,9 +59,9 @@ function api.Api:save_config()
55 if not res then 59 if not res then
56 return nil, errraw 60 return nil, errraw
57 end 61 end
58 local reserrors = res.errors 62 local res_errors = res.errors
59 if type(reserrors) == "table" then 63 if type(res_errors) == "table" then
60 return nil, ("Server error: " .. tostring(reserrors[1])) 64 return nil, ("Server error: " .. tostring(res_errors[1]))
61 end 65 end
62 local upload_conf = upload_config_file() 66 local upload_conf = upload_config_file()
63 if not upload_conf then return nil end 67 if not upload_conf then return nil end
@@ -94,25 +98,39 @@ function api.Api:check_version()
94end 98end
95 99
96function api.Api:method(path, ...) 100function api.Api:method(path, ...)
97 local res, err = self:raw_method(path, ...) 101 local res = self:raw_method(path, ...)
98 if not res then 102 if res.two_factor_required then
99 return nil, err 103 assert(self.on_tfa_required, "Server requires two-factor verification but no handler is configured")
104 local ok, err = self:on_tfa_required()
105 if not ok then
106 return nil, err
107 end
108 res, err = self:raw_method(path, ...)
109 if err then
110 return nil, err
111 end
100 end 112 end
101 local reserrors = res.errors 113 local res_errors = res.errors
102 if type(reserrors) == "table" then 114 if type(res_errors) == "table" then
103 if reserrors[1] == "Invalid key" then 115 if res_errors[1] == "Invalid key" then
104 return nil, reserrors[1] .. " (use the --api-key flag to change)" 116 res_errors[1] = res_errors[1] .. " (use the --api-key flag to change)"
105 end 117 end
106 local msg = table.concat(reserrors, ", ") 118 local msg = table.concat(res_errors, ", ")
107 return nil, "API Failed: " .. msg 119 return nil, "API Failed: " .. msg
108 end 120 end
109 return res 121 return res
110end 122end
111 123
112function api.Api:raw_method(path, ...) 124function api.Api:raw_method(path, params, post_params)
113 self:check_version() 125 self:check_version()
114 local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. path 126 local extra_headers = nil
115 return self:request(url, ...) 127 if self.tfa_token then
128 extra_headers = {
129 ["X-TFA-Token"] = self.tfa_token,
130 }
131 end
132 local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. tostring(path)
133 return self:request(url, params, post_params, extra_headers)
116end 134end
117 135
118local function encode_query_string(t, sep) 136local function encode_query_string(t, sep)
@@ -144,10 +162,25 @@ local function redact_api_url(url)
144 return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or "" 162 return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or ""
145end 163end
146 164
165local function request_result(url, response, status)
166 local pok, ret = pcall(json.decode, response)
167 if pok and ret then
168 if ret.two_factor_required then
169 return nil, "API Failed: two-factor verification required"
170 end
171 return ret
172 end
173 if status then
174 return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
175 else
176 return nil, "API failed - " .. redact_api_url(url)
177 end
178end
179
147local ltn12_ok, ltn12 = pcall(require, "ltn12") 180local ltn12_ok, ltn12 = pcall(require, "ltn12")
148if not ltn12_ok then 181if not ltn12_ok then
149 182
150 api.Api.request = function(self, url, params, post_params) 183 api.Api.request = function(self, url, params, post_params, extra_headers)
151 local vars = cfg.variables 184 local vars = cfg.variables
152 185
153 if fs.which_tool("downloader") == "wget" then 186 if fs.which_tool("downloader") == "wget" then
@@ -166,17 +199,26 @@ if not ltn12_ok then
166 local method = "GET" 199 local method = "GET"
167 local out 200 local out
168 local tmpfile = fs.tmpname() 201 local tmpfile = fs.tmpname()
169 if post_params then 202 if post_params or extra_headers then
170 method = "POST" 203 method = "POST"
171 local curl_cmd = vars.CURL .. " " .. vars.CURLNOCERTFLAG .. " -f -L --silent --user-agent \"" .. cfg.user_agent .. " via curl\" " 204 local curl_cmd = vars.CURL .. " " .. vars.CURLNOCERTFLAG .. " -f -L --silent --user-agent \"" .. cfg.user_agent .. " via curl\" "
172 for k, v in pairs(post_params) do 205 if post_params then
173 local var 206 for k, v in pairs(post_params) do
174 if type(v) == "table" then 207 local var
175 var = "@" .. v.fname 208 if type(v) == "table" then
176 else 209 var = "@" .. v.fname
177 var = v 210 else
211 var = v
212 end
213 curl_cmd = curl_cmd .. "--form \"" .. k .. "=" .. var .. "\" "
214 end
215 end
216 if extra_headers then
217 for k, v in pairs(extra_headers) do
218 if type(v) == "string" then
219 curl_cmd = curl_cmd .. "--header \"" .. k .. ": " .. v .. "\" "
220 end
178 end 221 end
179 curl_cmd = curl_cmd .. "--form \"" .. k .. "=" .. var .. "\" "
180 end 222 end
181 if cfg.connection_timeout and cfg.connection_timeout > 0 then 223 if cfg.connection_timeout and cfg.connection_timeout > 0 then
182 curl_cmd = curl_cmd .. "--connect-timeout " .. tonumber(cfg.connection_timeout) .. " " 224 curl_cmd = curl_cmd .. "--connect-timeout " .. tonumber(cfg.connection_timeout) .. " "
@@ -205,14 +247,14 @@ if not ltn12_ok then
205 util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ") 247 util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ")
206 end 248 end
207 249
208 return json.decode(out) 250 return request_result(url, out)
209 end 251 end
210 252
211else 253else
212 254
213 local warned_luasec = false 255 local warned_luasec = false
214 256
215 api.Api.request = function(self, url, params, post_params) 257 api.Api.request = function(self, url, params, post_params, extra_headers)
216 local server = tostring(self.config.server) 258 local server = tostring(self.config.server)
217 259
218 local http_ok, http 260 local http_ok, http
@@ -251,6 +293,13 @@ else
251 headers["Content-length"] = tostring(#body) 293 headers["Content-length"] = tostring(#body)
252 headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary) 294 headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary)
253 end 295 end
296 if extra_headers then
297 for k, v in pairs(extra_headers) do
298 if type(v) == "string" then
299 headers[k] = v
300 end
301 end
302 end
254 local method = post_params and "POST" or "GET" 303 local method = post_params and "POST" or "GET"
255 if self.debug then 304 if self.debug then
256 util.printout("[" .. tostring(method) .. " via " .. via .. "] " .. redact_api_url(url) .. " ... ") 305 util.printout("[" .. tostring(method) .. " via " .. via .. "] " .. redact_api_url(url) .. " ... ")
@@ -266,13 +315,9 @@ else
266 if self.debug then 315 if self.debug then
267 util.printout(tostring(status)) 316 util.printout(tostring(status))
268 end 317 end
269 local pok, ret = pcall(json.decode, table.concat(out))
270 if pok and ret then
271 return ret
272 end
273 return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
274 end
275 318
319 return request_result(url, table.concat(out), status)
320 end
276end 321end
277 322
278function api.new(args) 323function api.new(args)
diff --git a/src/luarocks/upload/api.tl b/src/luarocks/upload/api.tl
index e89ecf6e..bcc200e3 100644
--- a/src/luarocks/upload/api.tl
+++ b/src/luarocks/upload/api.tl
@@ -6,12 +6,16 @@ local record api
6 end 6 end
7 7
8 record Api 8 record Api
9 code: string
10 tfa_token: string
11 on_tfa_required: function(Api): (boolean, string)
12
9 load_config: function(Api): Configuration 13 load_config: function(Api): Configuration
10 save_config: function(Api): boolean, string 14 save_config: function(Api): boolean, string
11 check_version: function(Api): boolean, string 15 check_version: function(Api): boolean, string
12 method: function(Api, string, ...:Parameters): {string : any}, string 16 method: function(Api, string, ...:Parameters): {string : any}, string
13 raw_method: function(Api, string, ...:Parameters): {string : any}, string 17 raw_method: function(Api, string, ?Parameters, ?Parameters): {string : any}, string
14 request: function(Api, string, ?Parameters, ?Parameters): {string : any}, string 18 request: function(Api, string, ?Parameters, ?Parameters, ?Parameters): {string : any}, string
15 config: Configuration 19 config: Configuration
16 debug: boolean 20 debug: boolean
17 _server_tool_version: string 21 _server_tool_version: string
@@ -55,9 +59,9 @@ function api.Api:save_config(): boolean, string
55 if not res then 59 if not res then
56 return nil, errraw 60 return nil, errraw
57 end 61 end
58 local reserrors = res.errors 62 local res_errors = res.errors
59 if reserrors is {string} then 63 if res_errors is {string} then
60 return nil, ("Server error: " .. tostring(reserrors[1])) 64 return nil, ("Server error: " .. tostring(res_errors[1]))
61 end 65 end
62 local upload_conf = upload_config_file() 66 local upload_conf = upload_config_file()
63 if not upload_conf then return nil end 67 if not upload_conf then return nil end
@@ -94,25 +98,39 @@ function api.Api:check_version(): boolean, string
94end 98end
95 99
96function api.Api:method(path: string, ...: Parameters): {string : any}, string 100function api.Api:method(path: string, ...: Parameters): {string : any}, string
97 local res, err = self:raw_method(path, ...) 101 local res = self:raw_method(path, ...)
98 if not res then 102 if res.two_factor_required then
99 return nil, err 103 assert(self.on_tfa_required, "Server requires two-factor verification but no handler is configured")
104 local ok, err = self:on_tfa_required()
105 if not ok then
106 return nil, err
107 end
108 res, err = self:raw_method(path, ...)
109 if err then
110 return nil, err
111 end
100 end 112 end
101 local reserrors = res.errors 113 local res_errors = res.errors
102 if reserrors is {string} then --! not checking the contents 114 if res_errors is {string} then
103 if reserrors[1] == "Invalid key" then 115 if res_errors[1] == "Invalid key" then
104 return nil, reserrors[1] .. " (use the --api-key flag to change)" 116 res_errors[1] = res_errors[1] .. " (use the --api-key flag to change)"
105 end 117 end
106 local msg = table.concat(reserrors, ", ") 118 local msg = table.concat(res_errors, ", ")
107 return nil, "API Failed: " .. msg 119 return nil, "API Failed: " .. msg
108 end 120 end
109 return res 121 return res
110end 122end
111 123
112function api.Api:raw_method(path: string, ...: Parameters): {string : any}, string 124function api.Api:raw_method(path: string, params?: Parameters, post_params?: Parameters): {string : any}, string
113 self:check_version() 125 self:check_version()
114 local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. path 126 local extra_headers: Parameters = nil
115 return self:request(url, ...) 127 if self.tfa_token then
128 extra_headers = {
129 ["X-TFA-Token"] = self.tfa_token
130 }
131 end
132 local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. tostring(path)
133 return self:request(url, params, post_params, extra_headers)
116end 134end
117 135
118local function encode_query_string(t: Parameters, sep?: string): string 136local function encode_query_string(t: Parameters, sep?: string): string
@@ -144,10 +162,25 @@ local function redact_api_url(url: any): string
144 return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or "" 162 return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or ""
145end 163end
146 164
165local function request_result(url: string, response: string, status?: integer|string): {string : any}, string
166 local pok, ret = pcall(json.decode, response)
167 if pok and ret then
168 if ret.two_factor_required then
169 return nil, "API Failed: two-factor verification required"
170 end
171 return ret
172 end
173 if status then
174 return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
175 else
176 return nil, "API failed - " .. redact_api_url(url)
177 end
178end
179
147local ltn12_ok, ltn12 = pcall(require, "ltn12") 180local ltn12_ok, ltn12 = pcall(require, "ltn12")
148if not ltn12_ok then -- If not using LuaSocket and/or LuaSec... 181if not ltn12_ok then -- If not using LuaSocket and/or LuaSec...
149 182
150 api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters): {string : any}, string 183 api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters, extra_headers?: Parameters): {string : any}, string
151 local vars = cfg.variables 184 local vars = cfg.variables
152 185
153 if fs.which_tool("downloader") == "wget" then 186 if fs.which_tool("downloader") == "wget" then
@@ -166,17 +199,26 @@ if not ltn12_ok then -- If not using LuaSocket and/or LuaSec...
166 local method = "GET" 199 local method = "GET"
167 local out: string 200 local out: string
168 local tmpfile = fs.tmpname() 201 local tmpfile = fs.tmpname()
169 if post_params then 202 if post_params or extra_headers then
170 method = "POST" 203 method = "POST"
171 local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" " 204 local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" "
172 for k,v in pairs(post_params) do 205 if post_params then
173 local var: string 206 for k,v in pairs(post_params) do
174 if v is File then 207 local var: string
175 var = "@"..v.fname 208 if v is File then
176 else 209 var = "@"..v.fname
177 var = v 210 else
211 var = v
212 end
213 curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" "
214 end
215 end
216 if extra_headers then
217 for k,v in pairs(extra_headers) do
218 if v is string then
219 curl_cmd = curl_cmd .. "--header \""..k..": "..v.."\" "
220 end
178 end 221 end
179 curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" "
180 end 222 end
181 if cfg.connection_timeout and cfg.connection_timeout > 0 then 223 if cfg.connection_timeout and cfg.connection_timeout > 0 then
182 curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " 224 curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." "
@@ -205,14 +247,14 @@ if not ltn12_ok then -- If not using LuaSocket and/or LuaSec...
205 util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ") 247 util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ")
206 end 248 end
207 249
208 return json.decode(out) 250 return request_result(url, out)
209 end 251 end
210 252
211else -- use LuaSocket and LuaSec 253else -- use LuaSocket and LuaSec
212 254
213 local warned_luasec = false 255 local warned_luasec = false
214 256
215 api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters): {string : any}, string 257 api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters, extra_headers?: Parameters): {string : any}, string
216 local server = tostring(self.config.server) 258 local server = tostring(self.config.server)
217 local type Http = require("socket.http") 259 local type Http = require("socket.http")
218 local http_ok, http: boolean, Http 260 local http_ok, http: boolean, Http
@@ -251,6 +293,13 @@ else -- use LuaSocket and LuaSec
251 headers["Content-length"] = tostring(#body) 293 headers["Content-length"] = tostring(#body)
252 headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary) 294 headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary)
253 end 295 end
296 if extra_headers then
297 for k, v in pairs(extra_headers) do
298 if v is string then
299 headers[k] = v
300 end
301 end
302 end
254 local method = post_params and "POST" or "GET" 303 local method = post_params and "POST" or "GET"
255 if self.debug then 304 if self.debug then
256 util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ") 305 util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ")
@@ -266,13 +315,9 @@ else -- use LuaSocket and LuaSec
266 if self.debug then 315 if self.debug then
267 util.printout(tostring(status)) 316 util.printout(tostring(status))
268 end 317 end
269 local pok, ret = pcall(json.decode, table.concat(out))
270 if pok and ret then
271 return ret
272 end
273 return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
274 end
275 318
319 return request_result(url, table.concat(out), status)
320 end
276end 321end
277 322
278function api.new(args: Args): Api, string 323function api.new(args: Args): Api, string