Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ development source code and as such may not be routinely kept up to date.

# __NEXT__

## Improvements

* Several kinds of errors from `nextstrain login` and `nextstrain whoami`
related to their interactions with a remote server are now clearer.
([#347](https://github.com/nextstrain/cli/pull/347))


# 8.0.0 (18 January 2024)

Expand Down
13 changes: 10 additions & 3 deletions nextstrain/cli/authn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ def login(origin: Origin, credentials: Optional[Callable[[], Tuple[str, str]]] =

def renew(origin: Origin) -> Optional[User]:
"""
Renews existing tokens for *origin*, if possible.
Renews existing saved credentials for *origin*, if possible.

Returns a :class:`User` object with renewed information about the logged in
user when successful.

Raises a :class:`UserError` if authentication fails.
Returns ``None`` if there are no saved credentials or if they're unable to
be automatically renewed.
"""
assert origin

Expand Down Expand Up @@ -183,9 +184,15 @@ def current_user(origin: Origin) -> Optional[User]:
"""
assert origin

session = Session(origin)
tokens = _load_tokens(origin)

# Short-circuit if we don't have any tokens to speak of. Avoids trying to
# fetch authn metadata from the remote origin.
if all(token is None for token in tokens.values()):
return None

session = Session(origin)

try:
try:
session.verify_tokens(**tokens)
Expand Down
27 changes: 20 additions & 7 deletions nextstrain/cli/authn/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,31 @@ def openid_configuration(origin: Origin):
assert origin

with requests.Session() as http:
response = http.get(origin.rstrip("/") + "/.well-known/openid-configuration")
try:
response = http.get(origin.rstrip("/") + "/.well-known/openid-configuration")
response.raise_for_status()
return response.json()

if response.status_code == 404:
except requests.exceptions.ConnectionError as err:
raise UserError(f"""
Failed to retrieve authentication metadata for {origin}.
Could not connect to {origin} to retrieve
authentication metadata:

{type(err).__name__}: {err}

That remote may be invalid or you may be experiencing network
connectivity issues.
""") from err

except (requests.exceptions.HTTPError, requests.exceptions.JSONDecodeError) as err:
raise UserError(f"""
Failed to retrieve authentication metadata for {origin}:

{type(err).__name__}: {err}

That remote seems unlikely to be an alternate nextstrain.org
instance or an internal Nextstrain Groups Server instance.
""")

response.raise_for_status()
return response.json()
""") from err


def client_configuration(origin: Origin):
Expand Down