@@ -175,12 +175,31 @@ struct NoAuthentication <: State
175175end
176176Base. show (io:: IO , s:: NoAuthentication ) = print (io, " NoAuthentication($(s. server) , $(s. auth_suffix) )" )
177177
178- function get_device_auth_client_id ()
179- return get (ENV , " JULIA_PKG_AUTHENTICATION_DEVICE_CLIENT_ID" , " " )
178+ function device_client_id ()
179+ return get (ENV , " JULIA_PKG_AUTHENTICATION_DEVICE_CLIENT_ID" , " device " )
180180end
181181
182- function should_use_device_auth ()
183- return ! isempty (get_device_auth_client_id ())
182+ # Constructs the body if the device authentication flow requests, in accordance with
183+ # the Sections 3.1 and 3.4 of RFC8628 (https://datatracker.ietf.org/doc/html/rfc8628).
184+ # Returns an IOBuffer() object that can be passed to Downloads.download(input=...).
185+ function device_token_request_body (;
186+ client_id:: AbstractString ,
187+ scope:: Union{AbstractString, Nothing} = nothing ,
188+ device_code:: Union{AbstractString, Nothing} = nothing ,
189+ grant_type:: Union{AbstractString, Nothing} = nothing ,
190+ )
191+ b = IOBuffer ()
192+ write (b, " client_id=" , client_id)
193+ if ! isnothing (scope)
194+ write (b, " &scope=" , scope)
195+ end
196+ if ! isnothing (device_code)
197+ write (b, " &device_code=" , device_code)
198+ end
199+ if ! isnothing (grant_type)
200+ write (b, " &grant_type=" , grant_type)
201+ end
202+ return seek (b, 0 )
184203end
185204
186205# Query the /auth/configuration endpoint to get the refresh url and
@@ -235,7 +254,14 @@ function step(state::NoAuthentication)::Union{RequestLogin, Failure}
235254 initiate_browser_challenge (state)
236255 end
237256 if success
238- return RequestLogin (state. server, state. auth_suffix, challenge, body_or_response, get (auth_config, " device_token_endpoint" , " " ), get (auth_config, " device_token_refresh_url" , " " ))
257+ return RequestLogin (
258+ state. server,
259+ state. auth_suffix,
260+ challenge,
261+ body_or_response,
262+ get (auth_config, " device_token_endpoint" , " " ),
263+ get (auth_config, " device_token_refresh_url" , " " ),
264+ )
239265 else
240266 return HttpError (body_or_response)
241267 end
@@ -246,7 +272,10 @@ function fetch_device_code(state::NoAuthentication, device_endpoint::AbstractStr
246272 response = Downloads. request (
247273 device_endpoint,
248274 method = " POST" ,
249- input = IOBuffer (" client_id=$(get (ENV , " JULIA_PKG_AUTHENTICATION_DEVICE_CLIENT_ID" , " device" )) &scope=openid email profile offline_access" ),
275+ input = device_token_request_body (
276+ client_id = device_client_id (),
277+ scope = " openid profile offline_access" ,
278+ ),
250279 output = output,
251280 throw = false ,
252281 headers = Dict (" Accept" => " application/json" , " Content-Type" => " application/x-www-form-urlencoded" ),
@@ -426,9 +455,29 @@ function step(state::RequestLogin)::Union{ClaimToken, Failure}
426455 success = open_browser (url)
427456 if success && is_device
428457 # In case of device tokens, timeout for challenge is received in the initial request.
429- return ClaimToken (state. server, state. auth_suffix, state. challenge, state. response, Inf , time (), state. response[" expires_in" ], 2 , 0 , 10 , state. device_token_endpoint, state. device_token_refresh_url)
458+ return ClaimToken (
459+ state. server,
460+ state. auth_suffix,
461+ state. challenge,
462+ state. response,
463+ Inf ,
464+ time (),
465+ state. response[" expires_in" ],
466+ 2 ,
467+ 0 ,
468+ 10 ,
469+ state. device_token_endpoint,
470+ state. device_token_refresh_url,
471+ )
430472 elseif success
431- return ClaimToken (state. server, state. auth_suffix, state. challenge, state. response, state. device_token_endpoint, state. device_token_refresh_url)
473+ return ClaimToken (
474+ state. server,
475+ state. auth_suffix,
476+ state. challenge,
477+ state. response,
478+ state. device_token_endpoint,
479+ state. device_token_refresh_url
480+ )
432481 else # this can only happen for the browser hook
433482 return GenericError (" Failed to execute open_browser hook." )
434483 end
@@ -476,7 +525,11 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure}
476525 response = Downloads. request (
477526 state. device_token_endpoint,
478527 method = " POST" ,
479- input = IOBuffer (" client_id=$(get (ENV , " JULIA_PKG_AUTHENTICATION_DEVICE_CLIENT_ID" , " device" )) &scope=openid profile offline_access&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=$(state. response[" device_code" ]) " ),
528+ input = device_token_request_body (
529+ client_id = device_client_id (),
530+ device_code = state. response[" device_code" ],
531+ grant_type = " urn:ietf:params:oauth:grant-type:device_code" ,
532+ ),
480533 output = output,
481534 throw = false ,
482535 headers = Dict (" Accept" => " application/json" , " Content-Type" => " application/x-www-form-urlencoded" ),
@@ -499,15 +552,54 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure}
499552 body = try
500553 JSON. parse (String (take! (output)))
501554 catch err
502- return ClaimToken (state. server, state. auth_suffix, state. challenge, state. response, state. expiry, state. start_time, state. timeout, state. poll_interval, state. failures + 1 , state. max_failures, state. device_token_endpoint, state. device_token_refresh_url)
555+ return ClaimToken (
556+ state. server,
557+ state. auth_suffix,
558+ state. challenge,
559+ state. response,
560+ state. expiry,
561+ state. start_time,
562+ state. timeout,
563+ state. poll_interval,
564+ state. failures + 1 ,
565+ state. max_failures,
566+ state. device_token_endpoint,
567+ state. device_token_refresh_url,
568+ )
503569 end
504570
505571 if haskey (body, " token" )
506572 return HasNewToken (state. server, body[" token" ])
507573 elseif haskey (body, " expiry" ) # time at which the response/challenge pair will expire on the server
508- return ClaimToken (state. server, state. auth_suffix, state. challenge, state. response, body[" expiry" ], state. start_time, state. timeout, state. poll_interval, state. failures, state. max_failures, state. device_token_endpoint, state. device_token_refresh_url)
574+ return ClaimToken (
575+ state. server,
576+ state. auth_suffix,
577+ state. challenge,
578+ state. response,
579+ body[" expiry" ],
580+ state. start_time,
581+ state. timeout,
582+ state. poll_interval,
583+ state. failures,
584+ state. max_failures,
585+ state. device_token_endpoint,
586+ state. device_token_refresh_url,
587+ )
509588 else
510- return ClaimToken (state. server, state. auth_suffix, state. challenge, state. response, state. expiry, state. start_time, state. timeout, state. poll_interval, state. failures + 1 , state. max_failures, state. device_token_endpoint, state. device_token_refresh_url)
589+ return ClaimToken (
590+ state. server,
591+ state. auth_suffix,
592+ state. challenge,
593+ state. response,
594+ state. expiry,
595+ state. start_time,
596+ state. timeout,
597+ state. poll_interval,
598+ state. failures + 1 ,
599+ state. max_failures,
600+ state. device_token_endpoint,
601+ state. device_token_refresh_url
602+ )
511603 end
512604 elseif response isa Downloads. Response && response. status == 200
513605 body = JSON. parse (String (take! (output)))
@@ -516,7 +608,20 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure}
516608 body[" refresh_url" ] = state. device_token_refresh_url
517609 return HasNewToken (state. server, body)
518610 elseif response isa Downloads. Response && response. status in [401 , 400 ] && is_device
519- return ClaimToken (state. server, state. auth_suffix, state. challenge, state. response, state. expiry, state. start_time, state. timeout, state. poll_interval, state. failures + 1 , state. max_failures, state. device_token_endpoint, state. device_token_refresh_url)
611+ return ClaimToken (
612+ state. server,
613+ state. auth_suffix,
614+ state. challenge,
615+ state. response,
616+ state. expiry,
617+ state. start_time,
618+ state. timeout,
619+ state. poll_interval,
620+ state. failures + 1 ,
621+ state. max_failures,
622+ state. device_token_endpoint,
623+ state. device_token_refresh_url,
624+ )
520625 else
521626 return HttpError (response)
522627 end
0 commit comments