diff --git a/base/pkg/github.jl b/base/pkg/github.jl index 40a819e8b4186..28b9543a6d869 100644 --- a/base/pkg/github.jl +++ b/base/pkg/github.jl @@ -36,9 +36,14 @@ function curl(url::String, opts::Cmd=``) out, proc = open(`curl -i -s -S $opts $url`,"r") head = readline(out) status = int(split(head,r"\s+";limit=3)[2]) + header = (String=>String)[] for line in eachline(out) - ismatch(r"^\s*$",line) || continue - wait(proc); return status, readall(out) + if !ismatch(r"^\s*$",line) + (k,v) = split(line, r":\s*", 2) + header[k] = v + continue + end + wait(proc); return status, header, readall(out) end error("strangely formatted HTTP response") end @@ -55,25 +60,47 @@ end function token(user::String=user()) tokfile = Dir.path(".github","token") isfile(tokfile) && return strip(readchomp(tokfile)) - status, content = curl("https://api.github.com/authorizations",AUTH_DATA,`-u $user`) + status, header, content = curl("https://api.github.com/authorizations",AUTH_DATA,`-u $user`) + tfa = false + + # Check for two-factor authentication + if status == 401 && get(header, "X-GitHub-OTP", "") |> x->beginswith(x, "required") && isinteractive() + tfa = true + info("Two-factor authentication in use. Enter auth code. (You may have to re-enter your password.)") + print(STDERR, "Authentication code: ") + code = readline(STDIN) |> chomp + status, header, content = curl("https://api.github.com/authorizations",AUTH_DATA,`-H "X-GitHub-OTP: $code" -u $user`) + end + if status == 422 error_code = json().parse(content)["errors"][1]["code"] if error_code == "already_exists" - info("Retrieving existing GitHub token (you may have to enter you password again)") - status,content = curl("https://api.github.com/authorizations",`-u $user`) + if tfa + info("Retrieving existing GitHub token. (You may have to re-enter your password twice more.)") + status, header, content = curl("https://api.github.com/authorizations",AUTH_DATA,`-u $user`) + status != 401 && error("$status: $(json().parse(content)["message"])") + print(STDERR, "New authentication code: ") + code = readline(STDIN) |> chomp + status, header, content = curl("https://api.github.com/authorizations",`-H "X-GitHub-OTP: $code" -u $user`) + else + info("Retrieving existing GitHub token. (You may have to re-enter your password.)") + status, header, content = curl("https://api.github.com/authorizations", `-u $user`) + end (status >= 400) && error("$status: $(json().parse(content)["message"])") for entry in json().parse(content) if entry["note"] == AUTH_NOTE tok = entry["token"] + break end end else - error("GitHub returned validation error (422): $error_code") + error("GitHub returned validation error (422): $error_code: $(json().parse(content)["message"])") end else (status != 401 && status != 403) || error("$status: $(json().parse(content)["message"])") tok = json().parse(content)["token"] end + mkpath(dirname(tokfile)) open(io->println(io,tok),tokfile,"w") return tok @@ -81,7 +108,7 @@ end function req(resource::String, data, opts::Cmd=``) url = "https://api.github.com/$resource" - status, content = curl(url,data,`-u $(token()):x-oauth-basic $opts`) + status, header, content = curl(url,data,`-u $(token()):x-oauth-basic $opts`) response = json().parse(content) status, response end