Skip to content

Commit 192157a

Browse files
committed
tests for device auth
1 parent a5901cd commit 192157a

File tree

3 files changed

+99
-1
lines changed

3 files changed

+99
-1
lines changed

src/PkgAuthentication.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ function step(state::ClaimToken)::Union{ClaimToken, HasNewToken, Failure}
504504
body = JSON.parse(String(take!(output)))
505505
body["client"] = "device"
506506
body["expires"] = body["expires_in"] + Int(floor(time()))
507+
body["expires_at"] = body["expires"]
507508
body["refresh_url"] = joinpath(state.server, "auth/renew/token.toml/device/") # Need to be careful with auth suffix, if set
508509
return HasNewToken(state.server, body)
509510
elseif response isa Downloads.Response && response.status in [401, 400] && is_device

test/authserver.jl

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function claimtoken_handler(req)
5959
@show payload
6060
@show challenge_response_map
6161
if haskey(challenge_response_map, payload["challenge"]) &&
62-
challenge_response_map[payload["challenge"]] == payload["response"]
62+
challenge_response_map[payload["challenge"]] == payload["response"]
6363

6464
delete!(challenge_response_map, payload["challenge"])
6565
delete!(response_challenge_map, payload["response"])
@@ -99,12 +99,81 @@ function check_validity(req)
9999
return HTTP.Response(200, payload == TOKEN[])
100100
end
101101

102+
103+
104+
# --------- Device auth methods -----------------
105+
106+
function openid_configuration(req)
107+
return HTTP.Response(
108+
200,
109+
""" {
110+
"device_authorization_endpoint": "http://localhost:8888/auth/device/code",
111+
"token_endpoint": "http://localhost:8888/auth/token"
112+
} """,
113+
)
114+
end
115+
116+
device_code_user_code_map = Dict{String, Any}()
117+
user_code_device_code_map = Dict{String, Any}()
118+
authenticated = Dict{String, Any}()
119+
function auth_device_code(req)
120+
device_code = randstring(64)
121+
user_code = randstring(8)
122+
device_code_user_code_map[device_code] = user_code
123+
user_code_device_code_map[user_code] = device_code
124+
return HTTP.Response(
125+
200,
126+
""" {
127+
"device_code": "$device_code",
128+
"user_code": "$user_code",
129+
"verification_uri_complete": "http://localhost:8888/auth/device?user_code=$user_code",
130+
"expires_in": $CHALLENGE_EXPIRY
131+
} """,
132+
)
133+
end
134+
135+
function auth_device(req)
136+
params = HTTP.queryparams(req)
137+
user_code = get(params, "user_code", "")
138+
device_code = get(user_code_device_code_map, user_code, nothing)
139+
if device_code === nothing
140+
return HTTP.Response(400)
141+
end
142+
authenticated[device_code] = true
143+
refresh_token = Random.randstring(10)
144+
TOKEN[]["access_token"] = "full-$ID_TOKEN"
145+
TOKEN[]["token_type"] = "bearer"
146+
TOKEN[]["expires_in"] = EXPIRY
147+
TOKEN[]["refresh_token"] = refresh_token
148+
TOKEN[]["id_token"] = "full-$ID_TOKEN"
149+
return HTTP.Response(200)
150+
end
151+
152+
function auth_token(req)
153+
p = split(String(req.body), "&")
154+
d = Dict{String, Any}()
155+
for l in p
156+
kv = split(String(l), "=")
157+
d[String(kv[1])] = String(kv[2])
158+
end
159+
device_code = get(d, "device_code", nothing)
160+
if device_code === nothing || !get(authenticated, device_code, false)
161+
return HTTP.Response(401)
162+
end
163+
return HTTP.Response(200, JSON.json(TOKEN[]))
164+
end
165+
102166
router = HTTP.Router()
103167
HTTP.register!(router, "POST", "/auth/challenge", challenge_handler)
104168
HTTP.register!(router, "GET", "/auth/response", response_handler)
105169
HTTP.register!(router, "POST", "/auth/claimtoken", claimtoken_handler)
106170
HTTP.register!(router, "GET", "/auth/renew/token.toml/v2", renew_handler)
107171
HTTP.register!(router, "POST", "/auth/isvalid", check_validity)
172+
HTTP.register!(router, "GET", "/.well-known/openid-configuration", openid_configuration)
173+
HTTP.register!(router, "POST", "/auth/device/code", auth_device_code)
174+
HTTP.register!(router, "GET", "/auth/device", auth_device)
175+
HTTP.register!(router, "POST", "/auth/token", auth_token)
176+
HTTP.register!(router, "GET", "/auth/renew/token.toml/device", renew_handler)
108177

109178
function run()
110179
println("starting server")

test/tests.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,34 @@ PkgAuthentication.register_open_browser_hook(url -> HTTP.get(url))
6161
@test startswith(success2.token["id_token"], "refresh-")
6262
end
6363

64+
ENV["JULIA_PKG_AUTHENTICATION_DEVICE_CLIENT_ID"] = "device"
65+
66+
@testset "auth with running server (device flow)" begin
67+
delete_token()
68+
69+
@info "testing inital auth"
70+
success = PkgAuthentication.authenticate(test_pkg_server)
71+
72+
@test success isa PkgAuthentication.Success
73+
@test success.token["expires_at"] > time()
74+
@test startswith(success.token["id_token"], "full-")
75+
@test !occursin("id_token", sprint(show, success))
76+
77+
sleeptimer = ceil(Int, success.token["expires_at"] - time() + 1)
78+
@info "sleep for $(sleeptimer)s (until refresh necessary)"
79+
sleep(sleeptimer)
80+
81+
@info "testing auth refresh"
82+
success2 = PkgAuthentication.authenticate(test_pkg_server)
83+
@test success2 isa PkgAuthentication.Success
84+
@test !occursin("id_token", sprint(show, success2))
85+
@test success2.token["expires_at"] > time()
86+
@test success2.token["refresh_token"] !== success.token["refresh_token"]
87+
@test startswith(success2.token["id_token"], "refresh-")
88+
end
89+
90+
ENV["JULIA_PKG_AUTHENTICATION_DEVICE_CLIENT_ID"] = ""
91+
6492
@testset "PkgAuthentication.install" begin
6593
delete_token()
6694

0 commit comments

Comments
 (0)