Skip to content

Commit 115f14e

Browse files
mortenpipfitzseb
andauthored
feat: add method to auth with a token directly (#58)
* feat: add method to auth with a token directly * fix tests * fix formatting * fix docstring --------- Co-authored-by: Sebastian Pfitzner <[email protected]>
1 parent a54b672 commit 115f14e

File tree

4 files changed

+103
-27
lines changed

4 files changed

+103
-27
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

5+
## Unreleased
6+
7+
### Added
8+
9+
* The `JuliaHub.authenticate` function now supports a two-argument form, where you can pass the JuliaHub token in directly, bypassing interactive authentication. (??)
10+
511
## Version v0.1.10 - 2024-05-31
612

713
### Changed

src/authentication.jl

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -157,23 +157,34 @@ function _authheaders(token::Secret; hasura=false)
157157
end
158158

159159
"""
160-
JuliaHub.authenticate(server = Pkg.pkg_server(); force::Bool = false, maxcount::Integer = $(_DEFAULT_authenticate_maxcount), [hook::Base.Callable])
160+
JuliaHub.authenticate(
161+
server::AbstractString = Pkg.pkg_server();
162+
force::Bool = false,
163+
maxcount::Integer = $(_DEFAULT_authenticate_maxcount),
164+
[hook::Base.Callable]
165+
) -> JuliaHub.Authentication
166+
JuliaHub.authenticate(server::AbstractString, token::Union{AbstractString, JuliaHub.Secret}) -> JuliaHub.Authentication
167+
168+
Authenticates with a JuliaHub server, returning a [`JuliaHub.Authentication`](@ref) object and
169+
setting the global authentication session (see [`JuliaHub.current_authentication`](@ref)).
170+
May throw an [`AuthenticationError`](@ref) if the authentication fails (e.g. expired token).
171+
172+
The zero- and one-argument methods will attempt to read the token from the current Julia depot.
173+
If a valid authentication token does not exist in the Julia depot, a new token is acquired via an
174+
interactive browser based prompt. By default, it attemps to connect to the currently configured Julia
175+
package server URL (configured e.g. via the `JULIA_PKG_SERVER` environment variable), but this
176+
can be overridden by passing the `server` argument.
177+
178+
The two-argument method can be used when you do not want to read the token from the `auth.toml`
179+
file (e.g. when using a long-term token via an environment variable). In this case, you also have
180+
to explicitly set the server URL and `JULIA_PKG_SERVER` is ignored.
161181
162-
Authenticates with a JuliaHub server. If a valid authentication token does not exist in
163-
the Julia depot, a new token is acquired via an interactive browser based prompt.
164-
Returns an [`Authentication`](@ref) object if the authentication was successful, or throws an
165-
[`AuthenticationError`](@ref) if authentication fails.
182+
# Extended help
166183
167184
The interactive prompts tries to authenticate for a maximum of `maxcount` times.
168185
If `force` is set to `true`, an existing authentication token is first deleted. This can be
169186
useful when the existing authentication token is causing the authentication to fail.
170187
171-
# Extended help
172-
173-
By default, it attemps to connect to the currently configured Julia package server URL
174-
(configured e.g. via the `JULIA_PKG_SERVER` environment variable). However, this can
175-
be overridden by passing the `server` argument.
176-
177188
`hook` can be set to a function taking a single string-type argument, and will be passed the
178189
authorization URL the user should interact with in the browser. This can be used to override the default
179190
behavior coming from [PkgAuthentication](https://github.com/JuliaComputing/PkgAuthentication.jl).
@@ -183,6 +194,17 @@ cached authentications), making it unnecessary to pass the returned object manua
183194
function calls. This is useful for interactive use, but should not be used in library code,
184195
as different authentication calls may clash.
185196
"""
197+
function authenticate end
198+
199+
function authenticate(server::AbstractString, token::Union{AbstractString, Secret})
200+
auth = _authentication(
201+
_juliahub_uri(server);
202+
token=isa(token, Secret) ? token : Secret(token),
203+
)
204+
global __AUTH__[] = auth
205+
return auth
206+
end
207+
186208
function authenticate(
187209
server::Union{AbstractString, Nothing}=nothing;
188210
force::Bool=false,
@@ -197,6 +219,13 @@ function authenticate(
197219
),
198220
)
199221
end
222+
server_uri = _juliahub_uri(server)
223+
auth = Mocking.@mock _authenticate(server_uri; force, maxcount, hook)
224+
global __AUTH__[] = auth
225+
return auth
226+
end
227+
228+
function _juliahub_uri(server::Union{AbstractString, Nothing})
200229
# PkgAuthentication.token_path can not handle server values that do not
201230
# prepend `https://`, so we use Pkg.pkg_server() to normalize it, just in case.
202231
server_uri_string = if isnothing(server)
@@ -217,9 +246,8 @@ function authenticate(
217246
isnothing(server) ? ("Pkg.pkg_server()", Pkg.pkg_server()) : ("server", server)
218247
throw(AuthenticationError("Invalid $name value '$value' ($error_msg)"))
219248
end
220-
auth = Mocking.@mock _authenticate(server_uri; force, maxcount, hook)
221-
global __AUTH__[] = auth
222-
return auth
249+
250+
return server_uri
223251
end
224252

225253
function _authenticate(

test/authentication.jl

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ end
107107
expires=1234,
108108
109109
)
110-
# Missing username in /api/v1 -- succes, but with a warning
110+
# Missing username in /api/v1 -- success, but with a warning
111111
delete!(MOCK_JULIAHUB_STATE, :auth_v1_status)
112112
MOCK_JULIAHUB_STATE[:auth_v1_username] = nothing
113113
let a = @test_logs (:warn,) JuliaHub._authentication(
@@ -124,3 +124,53 @@ end
124124
end
125125
end
126126
end
127+
128+
# The two-argument JuliaHub.authenticate does not trigger PkgAuthentication, but
129+
# it does do the REST calls, like JuliaHub._authentication() above
130+
@testset "JuliaHub.authenticate(server, token)" begin
131+
empty!(MOCK_JULIAHUB_STATE)
132+
server = "https://juliahub.example.org"
133+
token = JuliaHub.Secret("")
134+
Mocking.apply(mocking_patch) do
135+
let a = JuliaHub.authenticate(server, token)
136+
@test a isa JuliaHub.Authentication
137+
@test a.server == URIs.URI(server)
138+
@test a.username == MOCK_USERNAME
139+
@test a.token == token
140+
@test a._api_version == v"0.0.1"
141+
@test a._email === nothing
142+
@test a._expires === nothing
143+
end
144+
# On old instances, we handle if /api/v1 404s
145+
MOCK_JULIAHUB_STATE[:auth_v1_status] = 404
146+
let a = JuliaHub.authenticate(server, token)
147+
@test a isa JuliaHub.Authentication
148+
@test a.server == URIs.URI(server)
149+
@test a.username == MOCK_USERNAME
150+
@test a._api_version == JuliaHub._MISSING_API_VERSION
151+
@test a._email === "[email protected]"
152+
@test a._expires === nothing
153+
end
154+
# .. but on a 500, it will actually throw
155+
MOCK_JULIAHUB_STATE[:auth_v1_status] = 500
156+
@test_throws JuliaHub.AuthenticationError JuliaHub.authenticate(server, token)
157+
# Testing the fallback to legacy GQL endpoint
158+
MOCK_JULIAHUB_STATE[:auth_v1_status] = 404
159+
let a = JuliaHub.authenticate(server, token)
160+
@test a isa JuliaHub.Authentication
161+
@test a.server == URIs.URI(server)
162+
@test a.username == MOCK_USERNAME
163+
@test a._api_version == JuliaHub._MISSING_API_VERSION
164+
@test a._email === "[email protected]"
165+
@test a._expires === nothing
166+
end
167+
# Error when the fallback also 500s
168+
MOCK_JULIAHUB_STATE[:auth_gql_fail] = true
169+
@test_throws JuliaHub.AuthenticationError JuliaHub.authenticate(server, token)
170+
# Missing username in /api/v1 -- throws an AuthenticationError, since there is
171+
# no auth.toml file to fall back to.
172+
delete!(MOCK_JULIAHUB_STATE, :auth_v1_status)
173+
MOCK_JULIAHUB_STATE[:auth_v1_username] = nothing
174+
@test_throws JuliaHub.AuthenticationError JuliaHub.authenticate(server, token)
175+
end
176+
end

test/runtests-live.jl

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,15 @@
33
TESTID = Random.randstring(8)
44

55
# Authenticate the test session
6-
JULIAHUB_SERVER = let
7-
juliahub_server = get(ENV, "JULIAHUB_SERVER") do
8-
error("JULIAHUB_SERVER environment variable must be set for these tests to work")
9-
end
10-
JuliaHub._sanitize_juliahub_uri(juliahub_server) do _, msg
11-
error("JULIAHUB_SERVER is invalid: $msg")
12-
end
6+
JULIAHUB_SERVER = get(ENV, "JULIAHUB_SERVER") do
7+
error("JULIAHUB_SERVER environment variable must be set for these tests to work")
138
end
149
auth = if haskey(ENV, "JULIAHUB_TOKEN")
15-
JULIAHUB_TOKEN = JuliaHub.Secret(ENV["JULIAHUB_TOKEN"])
16-
JuliaHub._authentication(JULIAHUB_SERVER; token=JULIAHUB_TOKEN)
10+
JuliaHub.authenticate(JULIAHUB_SERVER, ENV["JULIAHUB_TOKEN"])
1711
else
1812
@warn "JULIAHUB_TOKEN not set, attempting interactive authentication."
19-
@show JuliaHub.authenticate(string(JULIAHUB_SERVER))
13+
@show JuliaHub.authenticate(JULIAHUB_SERVER)
2014
end
21-
# manually set global auth ref
22-
JuliaHub.__AUTH__[] = auth
2315
@info "Authentication / API version: $(auth._api_version)"
2416

2517
@testset "JuliaHub.jl LIVE tests" begin

0 commit comments

Comments
 (0)