Skip to content

Commit 9440d85

Browse files
authored
feat: interactive authentication for invalid token (#86)
1 parent b2558f8 commit 9440d85

File tree

3 files changed

+59
-7
lines changed

3 files changed

+59
-7
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
1111
### Changed
1212

1313
* The string `repr` of `DatasetVersion` (e.g. `dataset.versions`) is now valid Julia code. ([#84])
14+
* `JuliaHub.authenticate` will now fall back to force-authentication if the token in an existing `auth.toml` file is found to be invalid during authentication. ([#86])
1415

1516
### Fixed
1617

@@ -150,4 +151,5 @@ Initial package release.
150151
[#58]: https://github.com/JuliaComputing/JuliaHub.jl/issues/58
151152
[#74]: https://github.com/JuliaComputing/JuliaHub.jl/issues/74
152153
[#83]: https://github.com/JuliaComputing/JuliaHub.jl/issues/83
153-
[#84]: https://github.com/JuliaComputing/JuliaHub.jl/issues/84
154+
[#84]: https://github.com/JuliaComputing/JuliaHub.jl/issues/84
155+
[#86]: https://github.com/JuliaComputing/JuliaHub.jl/issues/86

src/authentication.jl

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,15 @@ as different authentication calls may clash.
198198
function authenticate end
199199

200200
function authenticate(server::AbstractString, token::Union{AbstractString, Secret})
201-
auth = _authentication(
202-
_juliahub_uri(server);
203-
token=isa(token, Secret) ? token : Secret(token),
204-
)
201+
auth = try
202+
_authentication(
203+
_juliahub_uri(server);
204+
token=isa(token, Secret) ? token : Secret(token),
205+
)
206+
catch e
207+
isa(e, InvalidAuthentication) || rethrow()
208+
throw(AuthenticationError("The authentication token is invalid"))
209+
end
205210
global __AUTH__[] = auth
206211
return auth
207212
end
@@ -259,7 +264,44 @@ function _authenticate(
259264
# _authenticate either returns a valid token, or throws
260265
auth_toml = _authenticate_retry(string(server_uri), 1; force, maxcount)
261266
# Note: _authentication may throw, which gets passed on to the user
262-
_authentication(server_uri; auth_toml...)
267+
try
268+
_authentication(server_uri; auth_toml...)
269+
catch e
270+
# If the token in auth.toml is invalid, but it hasn't expired,
271+
# PkgAuthentication won't catch that, and we attempt to use it (to get the
272+
# API version etc). If the token is invalid, that fails with a 401 and
273+
# _authentication() throws. In this case, we will go ahead and remove the token
274+
# and try again (which should lead to the interactive authentication flow).
275+
if !isa(e, InvalidAuthentication) || (maxcount <= 1)
276+
rethrow()
277+
end
278+
# We'll back up the old auth.toml though, because the user did not ask
279+
# us to remove it, so we don't want to delete the token for them either.
280+
# To avoid overwriting an existing backup, we generate a unique name
281+
# by hashing the file contents.
282+
backup_path = string(
283+
auth_toml.tokenpath,
284+
".",
285+
bytes2hex(open(SHA.sha1, auth_toml.tokenpath))[1:8],
286+
".backup",
287+
)
288+
mv(auth_toml.tokenpath, backup_path; force=true)
289+
@warn """
290+
Existing token for $(server_uri) appears invalid; forcing reauthentication.
291+
Existing auth.toml backed up in: $(backup_path)
292+
"""
293+
# We assume that _authenticate_retry immediately returned the token,
294+
# and didn't retry multiple times. So we just bump `count` by one here.
295+
auth_toml = _authenticate_retry(string(server_uri), 2; force=true, maxcount)
296+
try
297+
_authentication(server_uri; auth_toml...)
298+
catch e
299+
# If it again fails with InvalidAuthentication, we give up. But we
300+
# need to throw AuthenticationError.
301+
isa(e, InvalidAuthentication) || rethrow()
302+
throw(AuthenticationError("JuliaHub returned an invalid authentication token"))
303+
end
304+
end
263305
finally
264306
isnothing(hook) || PkgAuthentication.clear_open_browser_hook()
265307
end
@@ -340,6 +382,7 @@ function _authentication(
340382
api = try
341383
_get_api_information(string(server), token)
342384
catch e
385+
isa(e, InvalidAuthentication) && rethrow()
343386
errmsg = """
344387
Unable to determine JuliaHub API version.
345388
_get_api_information failed with an exception:

test/authentication.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ end
3030
# In the general authenticate() tests, we mock the call to JuliaHub._authenticate()
3131
# So here we call a lower level JuliaHub._authenticat**ion** implementation, with
3232
# the REST calls mocked.
33-
@testset "JuliaHub._authenticate()" begin
33+
@testset "JuliaHub._authentication()" begin
3434
empty!(MOCK_JULIAHUB_STATE)
3535
server = URIs.URI("https://juliahub.example.org")
3636
token = JuliaHub.Secret("")
@@ -172,5 +172,12 @@ end
172172
delete!(MOCK_JULIAHUB_STATE, :auth_v1_status)
173173
MOCK_JULIAHUB_STATE[:auth_v1_username] = nothing
174174
@test_throws JuliaHub.AuthenticationError JuliaHub.authenticate(server, token)
175+
176+
# Test that we handle InvalidAuthentication correctly in _authentication()
177+
empty!(MOCK_JULIAHUB_STATE)
178+
MOCK_JULIAHUB_STATE[:auth_v1_status] = 401
179+
@test_throws JuliaHub.AuthenticationError(
180+
"The authentication token is invalid"
181+
) JuliaHub.authenticate(server, token)
175182
end
176183
end

0 commit comments

Comments
 (0)