diff options
| -rw-r--r-- | src/luarocks/cmd/upload.lua | 37 | ||||
| -rw-r--r-- | src/luarocks/cmd/upload.tl | 35 | ||||
| -rw-r--r-- | src/luarocks/core/types/args.d.tl | 183 | ||||
| -rw-r--r-- | src/luarocks/upload/api.lua | 109 | ||||
| -rw-r--r-- | src/luarocks/upload/api.tl | 111 |
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 @@ | |||
| 1 | local _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 | 1 | local _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 |
| 2 | local upload = { Response = { version = {} } } | 2 | local 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") |
| 53 | end | 55 | end |
| 54 | 56 | ||
| 57 | local 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 | ||
| 86 | end | ||
| 87 | |||
| 55 | function upload.command(args) | 88 | function 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") |
| 53 | end | 55 | end |
| 54 | 56 | ||
| 57 | local 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 | ||
| 86 | end | ||
| 87 | |||
| 55 | function upload.command(args: Args): boolean, string, string | 88 | function 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 @@ | |||
| 1 | local record args | 1 | local 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 |
| 110 | end | 111 | end |
| 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 @@ | |||
| 1 | local _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 = {} } | 1 | local _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() | |||
| 94 | end | 98 | end |
| 95 | 99 | ||
| 96 | function api.Api:method(path, ...) | 100 | function 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 |
| 110 | end | 122 | end |
| 111 | 123 | ||
| 112 | function api.Api:raw_method(path, ...) | 124 | function 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) | ||
| 116 | end | 134 | end |
| 117 | 135 | ||
| 118 | local function encode_query_string(t, sep) | 136 | local 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 "" |
| 145 | end | 163 | end |
| 146 | 164 | ||
| 165 | local 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 | ||
| 178 | end | ||
| 179 | |||
| 147 | local ltn12_ok, ltn12 = pcall(require, "ltn12") | 180 | local ltn12_ok, ltn12 = pcall(require, "ltn12") |
| 148 | if not ltn12_ok then | 181 | if 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 | ||
| 211 | else | 253 | else |
| 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 | ||
| 276 | end | 321 | end |
| 277 | 322 | ||
| 278 | function api.new(args) | 323 | function 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 | |||
| 94 | end | 98 | end |
| 95 | 99 | ||
| 96 | function api.Api:method(path: string, ...: Parameters): {string : any}, string | 100 | function 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 |
| 110 | end | 122 | end |
| 111 | 123 | ||
| 112 | function api.Api:raw_method(path: string, ...: Parameters): {string : any}, string | 124 | function 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) | ||
| 116 | end | 134 | end |
| 117 | 135 | ||
| 118 | local function encode_query_string(t: Parameters, sep?: string): string | 136 | local 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 "" |
| 145 | end | 163 | end |
| 146 | 164 | ||
| 165 | local 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 | ||
| 178 | end | ||
| 179 | |||
| 147 | local ltn12_ok, ltn12 = pcall(require, "ltn12") | 180 | local ltn12_ok, ltn12 = pcall(require, "ltn12") |
| 148 | if not ltn12_ok then -- If not using LuaSocket and/or LuaSec... | 181 | if 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 | ||
| 211 | else -- use LuaSocket and LuaSec | 253 | else -- 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 | ||
| 276 | end | 321 | end |
| 277 | 322 | ||
| 278 | function api.new(args: Args): Api, string | 323 | function api.new(args: Args): Api, string |
