@@ -175,8 +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" )
180+ end
181+
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 )
180203end
181204
182205# Query the /auth/configuration endpoint to get the refresh url and
@@ -231,7 +254,14 @@ function step(state::NoAuthentication)::Union{RequestLogin, Failure}
231254 initiate_browser_challenge (state)
232255 end
233256 if success
234- 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+ )
235265 else
236266 return HttpError (body_or_response)
237267 end
@@ -242,7 +272,10 @@ function fetch_device_code(state::NoAuthentication, device_endpoint::AbstractStr
242272 response = Downloads. request (
243273 device_endpoint,
244274 method = " POST" ,
245- 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+ ),
246279 output = output,
247280 throw = false ,
248281 headers = Dict (" Accept" => " application/json" , " Content-Type" => " application/x-www-form-urlencoded" ),
@@ -422,9 +455,29 @@ function step(state::RequestLogin)::Union{ClaimToken, Failure}
422455 success = open_browser (url)
423456 if success && is_device
424457 # In case of device tokens, timeout for challenge is received in the initial request.
425- 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+ )
426472 elseif success
427- 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+ )
428481 else # this can only happen for the browser hook
429482 return GenericError (" Failed to execute open_browser hook." )
430483 end
@@ -472,7 +525,12 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure}
472525 response = Downloads. request (
473526 state. device_token_endpoint,
474527 method = " POST" ,
475- 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+ # scope = "openid profile offline_access",
533+ ),
476534 output = output,
477535 throw = false ,
478536 headers = Dict (" Accept" => " application/json" , " Content-Type" => " application/x-www-form-urlencoded" ),
@@ -495,15 +553,54 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure}
495553 body = try
496554 JSON. parse (String (take! (output)))
497555 catch err
498- 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)
556+ return ClaimToken (
557+ state. server,
558+ state. auth_suffix,
559+ state. challenge,
560+ state. response,
561+ state. expiry,
562+ state. start_time,
563+ state. timeout,
564+ state. poll_interval,
565+ state. failures + 1 ,
566+ state. max_failures,
567+ state. device_token_endpoint,
568+ state. device_token_refresh_url,
569+ )
499570 end
500571
501572 if haskey (body, " token" )
502573 return HasNewToken (state. server, body[" token" ])
503574 elseif haskey (body, " expiry" ) # time at which the response/challenge pair will expire on the server
504- 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)
575+ return ClaimToken (
576+ state. server,
577+ state. auth_suffix,
578+ state. challenge,
579+ state. response,
580+ body[" expiry" ],
581+ state. start_time,
582+ state. timeout,
583+ state. poll_interval,
584+ state. failures,
585+ state. max_failures,
586+ state. device_token_endpoint,
587+ state. device_token_refresh_url,
588+ )
505589 else
506- 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)
590+ return ClaimToken (
591+ state. server,
592+ state. auth_suffix,
593+ state. challenge,
594+ state. response,
595+ state. expiry,
596+ state. start_time,
597+ state. timeout,
598+ state. poll_interval,
599+ state. failures + 1 ,
600+ state. max_failures,
601+ state. device_token_endpoint,
602+ state. device_token_refresh_url
603+ )
507604 end
508605 elseif response isa Downloads. Response && response. status == 200
509606 body = JSON. parse (String (take! (output)))
@@ -512,7 +609,20 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure}
512609 body[" refresh_url" ] = state. device_token_refresh_url
513610 return HasNewToken (state. server, body)
514611 elseif response isa Downloads. Response && response. status in [401 , 400 ] && is_device
515- 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)
612+ return ClaimToken (
613+ state. server,
614+ state. auth_suffix,
615+ state. challenge,
616+ state. response,
617+ state. expiry,
618+ state. start_time,
619+ state. timeout,
620+ state. poll_interval,
621+ state. failures + 1 ,
622+ state. max_failures,
623+ state. device_token_endpoint,
624+ state. device_token_refresh_url,
625+ )
516626 else
517627 return HttpError (response)
518628 end
0 commit comments