diff --git a/CHANGES.md b/CHANGES.md index 31ca336f..65060f77 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) diff --git a/nextstrain/cli/authn/__init__.py b/nextstrain/cli/authn/__init__.py index aef7b76d..20367146 100644 --- a/nextstrain/cli/authn/__init__.py +++ b/nextstrain/cli/authn/__init__.py @@ -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 @@ -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) diff --git a/nextstrain/cli/authn/configuration.py b/nextstrain/cli/authn/configuration.py index cf270eb2..4fce9c4c 100644 --- a/nextstrain/cli/authn/configuration.py +++ b/nextstrain/cli/authn/configuration.py @@ -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):