From c6b69c890410e9fbabacef6f50ddc91ad92511e6 Mon Sep 17 00:00:00 2001 From: Neeladri Das Date: Mon, 7 Jul 2025 10:43:43 +0000 Subject: [PATCH] increased authentication timeout to 100 s --- src/PkgAuthentication.jl | 183 ++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 90 deletions(-) diff --git a/src/PkgAuthentication.jl b/src/PkgAuthentication.jl index 811bcd0..e9d88d3 100644 --- a/src/PkgAuthentication.jl +++ b/src/PkgAuthentication.jl @@ -18,7 +18,7 @@ step(state::State) = throw(ArgumentError("no step function defined for this state: `$(state)`")) struct Success <: State - token::Dict{String, Any} + token::Dict{String,Any} end Base.show(io::IO, ::Success) = print(io, "Success()") @@ -54,11 +54,11 @@ julia> PkgAuthentication.authenticate("my-pkg-server.example.com") """ function authenticate( server::AbstractString; - auth_suffix::Union{String, Nothing} = nothing, - force::Union{Bool, Nothing} = nothing, - tries::Union{Integer, Nothing} = nothing, - modify_environment::Bool = true, -)::Union{Success, Failure} + auth_suffix::Union{String,Nothing}=nothing, + force::Union{Bool,Nothing}=nothing, + tries::Union{Integer,Nothing}=nothing, + modify_environment::Bool=true, +)::Union{Success,Failure} if modify_environment ENV[pkg_server_env_var_name] = server end @@ -66,9 +66,9 @@ function authenticate( # variable for the duration of the `authenticate` call. withenv(pkg_server_env_var_name => server) do authenticate(; - auth_suffix = auth_suffix, - force = force, - tries = tries, + auth_suffix=auth_suffix, + force=force, + tries=tries, ) end end @@ -89,10 +89,10 @@ julia> PkgAuthentication.authenticate() ``` """ function authenticate(; - auth_suffix::Union{String, Nothing} = nothing, - force::Union{Bool, Nothing} = nothing, - tries::Union{Integer, Nothing} = nothing, -)::Union{Success, Failure} + auth_suffix::Union{String,Nothing}=nothing, + force::Union{Bool,Nothing}=nothing, + tries::Union{Integer,Nothing}=nothing, +)::Union{Success,Failure} if auth_suffix === nothing # If the user does not provide the `auth_suffix` kwarg, we will append # "/auth" at the end of the Pkg server URL. @@ -151,7 +151,7 @@ struct NeedAuthentication <: State end Base.show(io::IO, s::NeedAuthentication) = print(io, "NeedAuthentication($(s.server), $(s.auth_suffix))") -function step(state::NeedAuthentication)::Union{HasToken, NoAuthentication} +function step(state::NeedAuthentication)::Union{HasToken,NoAuthentication} path = token_path(state.server) if isfile(path) toml = TOML.parsefile(path) @@ -184,9 +184,9 @@ end # Returns an IOBuffer() object that can be passed to Downloads.download(input=...). function device_token_request_body(; client_id::AbstractString, - scope::Union{AbstractString, Nothing} = nothing, - device_code::Union{AbstractString, Nothing} = nothing, - grant_type::Union{AbstractString, Nothing} = nothing, + scope::Union{AbstractString,Nothing}=nothing, + device_code::Union{AbstractString,Nothing}=nothing, + grant_type::Union{AbstractString,Nothing}=nothing, ) b = IOBuffer() write(b, "client_id=", client_id) @@ -221,10 +221,10 @@ function get_auth_configuration(state::NoAuthentication) auth_suffix = isempty(state.auth_suffix) ? "auth" : state.auth_suffix response = Downloads.request( "$(state.server)/$(auth_suffix)/configuration", - method = "GET", - output = output, - throw = false, - headers = ["Accept" => "application/json"], + method="GET", + output=output, + throw=false, + headers=["Accept" => "application/json"], ) if response isa Downloads.Response && response.status == 200 @@ -234,19 +234,19 @@ function get_auth_configuration(state::NoAuthentication) body = JSON.parse(content) catch ex @debug "Request for well known configuration returned: ", content - return Dict{String, Any}() + return Dict{String,Any}() end if body !== nothing - @assert !haskey(body, "auth_flows") || !("device" in body["auth_flows"]) || (haskey(body, "device_authorization_endpoint") && haskey(body, "device_token_endpoint") && haskey(body, "device_token_refresh_url")) + @assert !haskey(body, "auth_flows") || !("device" in body["auth_flows"]) || (haskey(body, "device_authorization_endpoint") && haskey(body, "device_token_endpoint") && haskey(body, "device_token_refresh_url")) return body end end - return Dict{String, Any}() + return Dict{String,Any}() end -function step(state::NoAuthentication)::Union{RequestLogin, Failure} +function step(state::NoAuthentication)::Union{RequestLogin,Failure} auth_config = get_auth_configuration(state) scope = get(auth_config, "device_token_scope", nothing) success, challenge, body_or_response = if "device" in get(auth_config, "auth_flows", []) @@ -268,18 +268,18 @@ function step(state::NoAuthentication)::Union{RequestLogin, Failure} end end -function fetch_device_code(state::NoAuthentication, device_endpoint::AbstractString, device_scope::Union{AbstractString, Nothing}) +function fetch_device_code(state::NoAuthentication, device_endpoint::AbstractString, device_scope::Union{AbstractString,Nothing}) output = IOBuffer() response = Downloads.request( device_endpoint, - method = "POST", - input = device_token_request_body( - client_id = device_client_id(), - scope = device_scope, + method="POST", + input=device_token_request_body( + client_id=device_client_id(), + scope=device_scope, ), - output = output, - throw = false, - headers = Dict("Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded"), + output=output, + throw=false, + headers=Dict("Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded"), ) if response isa Downloads.Response && response.status == 200 body = nothing @@ -303,10 +303,10 @@ function initiate_browser_challenge(state::NoAuthentication) challenge = Random.randstring(32) response = Downloads.request( "$(state.server)/$(state.auth_suffix)/challenge", - method = "POST", - input = IOBuffer(challenge), - output = output, - throw = false, + method="POST", + input=IOBuffer(challenge), + output=output, + throw=false, ) if response isa Downloads.Response && response.status == 200 return true, challenge, String(take!(output)) @@ -325,11 +325,11 @@ struct HasToken <: State server::String auth_suffix::String mtime::Float64 - token::Dict{String, Any} + token::Dict{String,Any} end Base.show(io::IO, s::HasToken) = print(io, "HasToken($(s.server), $(s.auth_suffix), $(s.mtime), )") -function step(state::HasToken)::Union{NeedRefresh, Success} +function step(state::HasToken)::Union{NeedRefresh,Success} expiry = get(state.token, "expires_at", get(state.token, "expires", 0)) expires_in = get(state.token, "expires_in", Inf) if min(expiry, expires_in + state.mtime) < time() @@ -347,19 +347,19 @@ fails. struct NeedRefresh <: State server::String auth_suffix::String - token::Dict{String, Any} + token::Dict{String,Any} end Base.show(io::IO, s::NeedRefresh) = print(io, "NeedRefresh($(s.server), $(s.auth_suffix), )") -function step(state::NeedRefresh)::Union{HasNewToken, NoAuthentication} +function step(state::NeedRefresh)::Union{HasNewToken,NoAuthentication} refresh_token = state.token["refresh_token"] output = IOBuffer() response = Downloads.request( state.token["refresh_url"], - method = "GET", - headers = ["Authorization" => "Bearer $refresh_token"], - output = output, - throw = false, + method="GET", + headers=["Authorization" => "Bearer $refresh_token"], + output=output, + throw=false, ) # errors are recoverable by just getting a new token: if response isa Downloads.Response && response.status == 200 @@ -370,10 +370,10 @@ function step(state::NeedRefresh)::Union{HasNewToken, NoAuthentication} assert_dict_keys(body, "expires_in"; msg=msg) assert_dict_keys(body, "expires", "expires_at"; msg=msg) end - @info("Successfully refreshed token") + @info("Successfully refreshed token") return HasNewToken(state.server, body) catch err - @debug "invalid body received while refreshing token" exception=(err, catch_backtrace()) + @debug "invalid body received while refreshing token" exception = (err, catch_backtrace()) end @info "Did not refresh token, could not json parse ", response return NoAuthentication(state.server, state.auth_suffix) @@ -403,13 +403,13 @@ unexpected failure. """ struct HasNewToken <: State server::String - token::Dict{String, Any} + token::Dict{String,Any} tries::Int end Base.show(io::IO, s::HasNewToken) = print(io, "HasNewToken($(s.server), , $(s.tries))") HasNewToken(server, token) = HasNewToken(server, token, 0) -function step(state::HasNewToken)::Union{HasNewToken, Success, Failure} +function step(state::HasNewToken)::Union{HasNewToken,Success,Failure} if state.tries >= 3 return GenericError("Failed to write token.") end @@ -425,7 +425,7 @@ function step(state::HasNewToken)::Union{HasNewToken, Success, Failure} return HasNewToken(state.server, state.token, 0) end catch err - @debug "failed to write token" exception=(err, catch_backtrace()) + @debug "failed to write token" exception = (err, catch_backtrace()) return GenericError("Failed to write token.") end end @@ -439,13 +439,13 @@ struct RequestLogin <: State server::String auth_suffix::String challenge::String - response::Union{String, Dict{String, Any}} + response::Union{String,Dict{String,Any}} device_token_endpoint::String device_token_refresh_url::String end Base.show(io::IO, s::RequestLogin) = print(io, "RequestLogin($(s.server), $(s.auth_suffix), , $(s.response), $(s.device_token_endpoint), $(s.device_token_refresh_url))") -function step(state::RequestLogin)::Union{ClaimToken, Failure} +function step(state::RequestLogin)::Union{ClaimToken,Failure} is_device = !isempty(state.device_token_endpoint) url = if is_device string(state.response["verification_uri_complete"]) @@ -492,8 +492,8 @@ token, or to Failure if the polling times out, or there is an unexpected error. struct ClaimToken <: State server::String auth_suffix::String - challenge::Union{Nothing, String} - response::Union{String, Dict{String, Any}} + challenge::Union{Nothing,String} + response::Union{String,Dict{String,Any}} expiry::Float64 start_time::Float64 timeout::Float64 @@ -505,11 +505,11 @@ struct ClaimToken <: State end Base.show(io::IO, s::ClaimToken) = print(io, "ClaimToken($(s.server), $(s.auth_suffix), , $(s.response), $(s.expiry), $(s.start_time), $(s.timeout), $(s.poll_interval), $(s.failures), $(s.max_failures), $(s.device_token_endpoint), $(s.device_token_refresh_url))") -ClaimToken(server, auth_suffix, challenge, response, device_token_endpoint, device_token_refresh_url, expiry = Inf, failures = 0) = - ClaimToken(server, auth_suffix, challenge, response, expiry, time(), 180, 2, failures, 10, device_token_endpoint, device_token_refresh_url) +ClaimToken(server, auth_suffix, challenge, response, device_token_endpoint, device_token_refresh_url, expiry=Inf, failures=0) = + ClaimToken(server, auth_suffix, challenge, response, expiry, time(), 180, 5, failures, 20, device_token_endpoint, device_token_refresh_url) -function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure} - if time() > state.expiry || (time() - state.start_time)/1e6 > state.timeout # server-side or client-side timeout +function step(state::ClaimToken)::Union{ClaimToken,HasNewToken,Failure} + if time() > state.expiry || (time() - state.start_time) / 1e6 > state.timeout # server-side or client-side timeout return GenericError("Timeout waiting for user to authenticate in browser.") end @@ -525,15 +525,15 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure} output = IOBuffer() response = Downloads.request( state.device_token_endpoint, - method = "POST", - input = device_token_request_body( - client_id = device_client_id(), - device_code = state.response["device_code"], - grant_type = "urn:ietf:params:oauth:grant-type:device_code", + method="POST", + input=device_token_request_body( + client_id=device_client_id(), + device_code=state.response["device_code"], + grant_type="urn:ietf:params:oauth:grant-type:device_code", ), - output = output, - throw = false, - headers = Dict("Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded"), + output=output, + throw=false, + headers=Dict("Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded"), ) else data = JSON.json(Dict( @@ -542,10 +542,10 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure} )) response = Downloads.request( "$(state.server)/$(state.auth_suffix)/claimtoken", - method = "POST", - input = IOBuffer(data), - output = output, - throw = false, + method="POST", + input=IOBuffer(data), + output=output, + throw=false, ) end @@ -675,8 +675,8 @@ is_token_valid(toml) = get(toml, "id_token", nothing) isa AbstractString && get(toml, "refresh_token", nothing) isa AbstractString && get(toml, "refresh_url", nothing) isa AbstractString && - (get(toml, "expires_at", nothing) isa Union{Integer, AbstractFloat} || - get(toml, "expires", nothing) isa Union{Integer, AbstractFloat}) + (get(toml, "expires_at", nothing) isa Union{Integer,AbstractFloat} || + get(toml, "expires", nothing) isa Union{Integer,AbstractFloat}) @static if Base.VERSION >= v"1.4-" const pkg_server = Pkg.pkg_server @@ -694,9 +694,9 @@ end const _get_server_dir = Pkg.PlatformEngines.get_server_dir else function _get_server_dir( - url::AbstractString, - server::AbstractString, - ) + url::AbstractString, + server::AbstractString, + ) server === nothing && return url == server || startswith(url, "$server/") || return m = match(r"^\w+://(?:[^\\/@]+@)?([^\\/:]+)(?:$|/|:)", server) @@ -709,9 +709,9 @@ else end function get_server_dir( - url::AbstractString, - server::Union{AbstractString, Nothing} = pkg_server(), - ) + url::AbstractString, + server::Union{AbstractString,Nothing}=pkg_server(), +) server_dir_pkgauth = _get_server_dir(url, server) server_dir_pkg = Pkg.PlatformEngines.get_server_dir(url, server) if server_dir_pkgauth != server_dir_pkg @@ -734,7 +734,7 @@ function token_path(url::AbstractString) return get(ENV, "JULIA_PKG_TOKEN_PATH", default) end -const OPEN_BROWSER_HOOK = Ref{Union{Base.Callable, Nothing}}(nothing) +const OPEN_BROWSER_HOOK = Ref{Union{Base.Callable,Nothing}}(nothing) function register_open_browser_hook(f::Base.Callable) if !hasmethod(f, Tuple{AbstractString}) @@ -749,18 +749,21 @@ end function open_browser(url::AbstractString) @debug "opening auth in browser" - printstyled(color = :yellow, bold = true, + printstyled(color=:yellow, bold=true, "Authentication required: please authenticate in browser.\n") - printstyled(color = :yellow, """ - The authentication page should open in your browser automatically, but you may need to switch to the opened window or tab. If the authentication page is not automatically opened, you can authenticate by manually opening the following URL: """) - printstyled(color = :light_blue, "$url\n") + printstyled( + color=:yellow, + """ +The authentication page should open in your browser automatically, but you may need to switch to the opened window or tab. If the authentication page is not automatically opened, you can authenticate by manually opening the following URL: """ + ) + printstyled(color=:light_blue, "$url\n") try if OPEN_BROWSER_HOOK[] !== nothing try OPEN_BROWSER_HOOK[](url) return true catch err - @info "error executing browser hook" exception=(err, catch_backtrace()) + @info "error executing browser hook" exception = (err, catch_backtrace()) return false end elseif Sys.iswindows() || detectwsl() @@ -798,9 +801,9 @@ julia> PkgAuthentication.install("my-pkg-server.example.com") julia> PkgAuthentication.install("my-pkg-server.example.com"; maxcount = 5) ``` """ -function install(server::AbstractString; maxcount::Integer = 3) +function install(server::AbstractString; maxcount::Integer=3) ENV[pkg_server_env_var_name] = server - return install(; maxcount = maxcount) + return install(; maxcount=maxcount) end """ @@ -826,7 +829,7 @@ julia> PkgAuthentication.install() julia> PkgAuthentication.install(; maxcount = 5) ``` """ -function install(; maxcount::Integer = 3) +function install(; maxcount::Integer=3) if maxcount < 1 throw(ArgumentError("`maxcount` must be greater than or equal to one")) end @@ -844,17 +847,17 @@ end function generate_auth_handler(maxcount::Integer) auth_handler = (url, server, err) -> begin failed_auth_count = 0 - ret = authenticate(server; tries = 2) + ret = authenticate(server; tries=2) if ret isa Success failed_auth_count = 0 @debug "Authentication successful." else failed_auth_count += 1 if failed_auth_count >= maxcount - printstyled(color = :red, bold = true, "\nAuthentication failed.\n\n") + printstyled(color=:red, bold=true, "\nAuthentication failed.\n\n") return true, false # handled, but Pkg shouldn't try again else - printstyled(color = :yellow, bold = true, "\nAuthentication failed. Retrying...\n\n") + printstyled(color=:yellow, bold=true, "\nAuthentication failed. Retrying...\n\n") end end return true, true # handled, and Pkg should try again now