cli: print oauth errors better, respect slow_down (#169164)

For diagnosing https://github.com/microsoft/vscode/issues/169066
This commit is contained in:
Connor Peet 2022-12-14 11:06:24 -08:00 committed by GitHub
parent 391acf8149
commit 82b239204c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 11 deletions

View file

@ -9,7 +9,9 @@ use crate::{
state::{LauncherPaths, PersistedState},
trace,
util::{
errors::{wrap, AnyError, RefreshTokenNotAvailableError, StatusError, WrappedError},
errors::{
wrap, AnyError, OAuthError, RefreshTokenNotAvailableError, StatusError, WrappedError,
},
input::prompt_options,
},
warning,
@ -41,6 +43,12 @@ struct AuthenticationResponse {
expires_in: Option<i64>,
}
#[derive(Deserialize)]
struct AuthenticationError {
error: String,
error_description: Option<String>,
}
#[derive(clap::ArgEnum, Serialize, Deserialize, Debug, Clone, Copy)]
pub enum AuthProvider {
Microsoft,
@ -480,12 +488,26 @@ impl Auth {
.send()
.await?;
if !response.status().is_success() {
return Err(StatusError::from_res(response).await?.into());
let status_code = response.status().as_u16();
let body = response.bytes().await?;
if let Ok(body) = serde_json::from_slice::<AuthenticationResponse>(&body) {
return Ok(StoredCredential::from_response(body, provider));
}
let body = response.json::<AuthenticationResponse>().await?;
Ok(StoredCredential::from_response(body, provider))
if let Ok(res) = serde_json::from_slice::<AuthenticationError>(&body) {
return Err(OAuthError {
error: res.error,
error_description: res.error_description,
}
.into());
}
return Err(StatusError {
body: String::from_utf8_lossy(&body).to_string(),
status_code,
url: provider.grant_uri().to_string(),
}
.into());
}
/// Implements the device code flow, returning the credentials upon success.
@ -540,16 +562,21 @@ impl Auth {
};
let body = format!(
"client_id={}&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={}",
provider.client_id(),
init_code_json.device_code
);
"client_id={}&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={}",
provider.client_id(),
init_code_json.device_code
);
let mut interval_s = 5;
while Utc::now() < expires_at {
sleep(std::time::Duration::from_secs(5)).await;
sleep(std::time::Duration::from_secs(interval_s)).await;
match self.do_grant(provider, body.clone()).await {
Ok(creds) => return Ok(creds),
Err(AnyError::OAuthError(e)) if e.error == "slow_down" => {
interval_s += 5; // https://www.rfc-editor.org/rfc/rfc8628#section-3.5
trace!(self.log, "refresh poll failed, slowing down");
}
Err(e) => {
trace!(self.log, "refresh poll failed, retrying: {}", e);
}

View file

@ -401,6 +401,23 @@ impl std::fmt::Display for MissingHomeDirectory {
}
}
#[derive(Debug)]
pub struct OAuthError {
pub error: String,
pub error_description: Option<String>,
}
impl std::fmt::Display for OAuthError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Error getting authorization: {} {}",
self.error,
self.error_description.as_deref().unwrap_or("")
)
}
}
#[derive(Debug)]
pub struct CommandFailed {
pub output: std::process::Output,
@ -487,7 +504,8 @@ makeAnyError!(
UpdatesNotConfigured,
CorruptDownload,
MissingHomeDirectory,
CommandFailed
CommandFailed,
OAuthError
);
impl From<reqwest::Error> for AnyError {