diff --git a/src/authenticator.rs b/src/authenticator.rs index af43e14..e639f99 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,7 +1,5 @@ //! Module contianing the core functionality for OAuth2 Authentication. -use crate::authenticator_delegate::{ - AuthenticatorDelegate, DefaultAuthenticatorDelegate, DeviceFlowDelegate, InstalledFlowDelegate, -}; +use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; use crate::device::DeviceFlow; use crate::error::Error; use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; @@ -15,13 +13,11 @@ use std::borrow::Cow; use std::io; use std::path::PathBuf; use std::sync::Mutex; -use std::time::Duration; /// Authenticator is responsible for fetching tokens, handling refreshing tokens, /// and optionally persisting tokens to disk. pub struct Authenticator { hyper_client: hyper::Client, - auth_delegate: Box, storage: Storage, auth_flow: AuthFlow, } @@ -49,19 +45,9 @@ where Some(app_secret), ) => { // token is expired but has a refresh token. - let token = match RefreshFlow::refresh_token( - &self.hyper_client, - app_secret, - &refresh_token, - ) - .await - { - Err(err) => { - self.auth_delegate.token_refresh_failed(&err); - return Err(err.into()); - } - Ok(token) => token, - }; + let token = + RefreshFlow::refresh_token(&self.hyper_client, app_secret, &refresh_token) + .await?; self.storage.set(hashed_scopes, token.clone()).await; Ok(token) } @@ -78,7 +64,6 @@ where /// Configure an Authenticator using the builder pattern. pub struct AuthenticatorBuilder { hyper_client_builder: C, - auth_delegate: Box, storage_type: StorageType, auth_flow: F, } @@ -158,12 +143,10 @@ impl ServiceAccountAuthenticator { /// ``` /// # async fn foo() { /// # let custom_hyper_client = hyper::Client::new(); -/// # let custom_auth_delegate = yup_oauth2::authenticator_delegate::DefaultAuthenticatorDelegate; /// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret) /// .hyper_client(custom_hyper_client) /// .persist_tokens_to_disk("/tmp/tokenfile.json") -/// .auth_delegate(Box::new(custom_auth_delegate)) /// .build() /// .await /// .expect("failed to create authenticator"); @@ -173,7 +156,6 @@ impl AuthenticatorBuilder { async fn common_build( hyper_client_builder: C, storage_type: StorageType, - auth_delegate: Box, auth_flow: AuthFlow, ) -> io::Result> where @@ -190,7 +172,6 @@ impl AuthenticatorBuilder { Ok(Authenticator { hyper_client, storage, - auth_delegate, auth_flow, }) } @@ -198,7 +179,6 @@ impl AuthenticatorBuilder { fn with_auth_flow(auth_flow: F) -> AuthenticatorBuilder { AuthenticatorBuilder { hyper_client_builder: DefaultHyperClient, - auth_delegate: Box::new(DefaultAuthenticatorDelegate), storage_type: StorageType::Memory, auth_flow, } @@ -211,7 +191,6 @@ impl AuthenticatorBuilder { ) -> AuthenticatorBuilder, F> { AuthenticatorBuilder { hyper_client_builder: hyper_client, - auth_delegate: self.auth_delegate, storage_type: self.storage_type, auth_flow: self.auth_flow, } @@ -224,17 +203,6 @@ impl AuthenticatorBuilder { ..self } } - - /// Use the provided authenticator delegate. - pub fn auth_delegate( - self, - auth_delegate: Box, - ) -> AuthenticatorBuilder { - AuthenticatorBuilder { - auth_delegate, - ..self - } - } } /// ## Methods available when building a device flow Authenticator. @@ -245,7 +213,6 @@ impl AuthenticatorBuilder { /// let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret) /// .device_code_url("foo") /// .flow_delegate(Box::new(custom_flow_delegate)) -/// .wait_duration(std::time::Duration::from_secs(120)) /// .grant_type("foo") /// .build() /// .await @@ -275,17 +242,6 @@ impl AuthenticatorBuilder { } } - /// Use the provided wait duration. - pub fn wait_duration(self, wait_duration: Duration) -> Self { - AuthenticatorBuilder { - auth_flow: DeviceFlow { - wait_duration, - ..self.auth_flow - }, - ..self - } - } - /// Use the provided grant type. pub fn grant_type(self, grant_type: impl Into>) -> Self { AuthenticatorBuilder { @@ -305,7 +261,6 @@ impl AuthenticatorBuilder { Self::common_build( self.hyper_client_builder, self.storage_type, - self.auth_delegate, AuthFlow::DeviceFlow(self.auth_flow), ) .await @@ -348,7 +303,6 @@ impl AuthenticatorBuilder { Self::common_build( self.hyper_client_builder, self.storage_type, - self.auth_delegate, AuthFlow::InstalledFlow(self.auth_flow), ) .await @@ -389,7 +343,6 @@ impl AuthenticatorBuilder { Self::common_build( self.hyper_client_builder, self.storage_type, - self.auth_delegate, AuthFlow::ServiceAccountFlow(service_account_auth_flow), ) .await diff --git a/src/authenticator_delegate.rs b/src/authenticator_delegate.rs index 9907178..9374c1d 100644 --- a/src/authenticator_delegate.rs +++ b/src/authenticator_delegate.rs @@ -1,25 +1,11 @@ //! Module containing types related to delegates. -use crate::error::RefreshError; - -use std::fmt; use std::pin::Pin; use std::time::Duration; use chrono::{DateTime, Local, Utc}; use futures::prelude::*; -/// A utility type to indicate how operations DeviceFlowHelper operations should be retried -pub enum Retry { - /// Signal you don't want to retry - Abort, - /// Signals you want to retry after the given duration - After(Duration), - /// Instruct the caller to attempt to keep going, or choose an alternate path. - /// If this is not supported, it will have the same effect as `Abort` - Skip, -} - /// Contains state of pending authentication requests #[derive(Clone, Debug, PartialEq)] pub struct PollInformation { @@ -36,43 +22,9 @@ pub struct PollInformation { pub interval: Duration, } -impl fmt::Display for PollInformation { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - writeln!(f, "Proceed with polling until {}", self.expires_at) - } -} - -/// A partially implemented trait to interact with the `Authenticator` -/// -/// The only method that needs to be implemented manually is `present_user_code(...)`, -/// as no assumptions are made on how this presentation should happen. -pub trait AuthenticatorDelegate: Send + Sync { - /// Called if we could not acquire a refresh token for a reason possibly specified - /// by the server. - /// This call is made for the delegate's information only. - fn token_refresh_failed(&self, _: &RefreshError) {} -} - /// DeviceFlowDelegate methods are called when a device flow needs to ask the /// application what to do in certain cases. pub trait DeviceFlowDelegate: Send + Sync { - /// Called if the request code is expired. You will have to start over in this case. - /// This will be the last call the delegate receives. - /// Given `DateTime` is the expiration date - fn expired(&self, _: DateTime) {} - - /// Called if the user denied access. You would have to start over. - /// This will be the last call the delegate receives. - fn denied(&self) {} - - /// Called as long as we are waiting for the user to authorize us. - /// Can be used to print progress information, or decide to time-out. - /// - /// If the returned `Retry` variant is a duration. - fn pending(&self, _: &PollInformation) -> Retry { - Retry::After(Duration::from_secs(5)) - } - /// The server has returned a `user_code` which must be shown to the user, /// along with the `verification_url`. /// # Notes @@ -136,12 +88,6 @@ async fn present_user_url(url: &str, need_code: bool) -> Result } } -/// Uses all default implementations by AuthenticatorDelegate, and makes the trait's -/// implementation usable in the first place. -#[derive(Copy, Clone)] -pub struct DefaultAuthenticatorDelegate; -impl AuthenticatorDelegate for DefaultAuthenticatorDelegate {} - /// Uses all default implementations in the DeviceFlowDelegate trait. #[derive(Copy, Clone)] pub struct DefaultDeviceFlowDelegate; diff --git a/src/device.rs b/src/device.rs index 1ab9efb..f3beaa7 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,14 +1,13 @@ use crate::authenticator_delegate::{ - DefaultDeviceFlowDelegate, DeviceFlowDelegate, PollInformation, Retry, + DefaultDeviceFlowDelegate, DeviceFlowDelegate, PollInformation, }; -use crate::error::{Error, JsonErrorOr, PollError}; +use crate::error::{AuthError, AuthErrorOr, Error}; use crate::types::{ApplicationSecret, Token}; use std::borrow::Cow; use std::time::Duration; -use ::log::error; -use chrono::{DateTime, Utc}; +use chrono::Utc; use futures::prelude::*; use hyper::header; use serde::Deserialize; @@ -27,7 +26,6 @@ pub struct DeviceFlow { pub(crate) app_secret: ApplicationSecret, pub(crate) device_code_url: Cow<'static, str>, pub(crate) flow_delegate: Box, - pub(crate) wait_duration: Duration, pub(crate) grant_type: Cow<'static, str>, } @@ -39,7 +37,6 @@ impl DeviceFlow { app_secret, device_code_url: GOOGLE_DEVICE_CODE_URL.into(), flow_delegate: Box::new(DefaultDeviceFlowDelegate), - wait_duration: Duration::from_secs(120), grant_type: GOOGLE_GRANT_TYPE.into(), } } @@ -61,18 +58,14 @@ impl DeviceFlow { ) .await?; self.flow_delegate.present_user_code(&pollinf); - tokio::timer::Timeout::new( - self.wait_for_device_token( - hyper_client, - &self.app_secret, - &pollinf, - &device_code, - &self.grant_type, - ), - self.wait_duration, + self.wait_for_device_token( + hyper_client, + &self.app_secret, + &pollinf, + &device_code, + &self.grant_type, ) .await - .map_err(|_| Error::Poll(PollError::TimedOut))? } async fn wait_for_device_token( @@ -89,28 +82,19 @@ impl DeviceFlow { let mut interval = pollinf.interval; loop { tokio::timer::delay_for(interval).await; - interval = match Self::poll_token( - &app_secret, - hyper_client, - device_code, - grant_type, - pollinf.expires_at, - &*self.flow_delegate as &dyn DeviceFlowDelegate, - ) - .await + interval = match Self::poll_token(&app_secret, hyper_client, device_code, grant_type) + .await { - Ok(None) => match self.flow_delegate.pending(&pollinf) { - Retry::Abort | Retry::Skip => return Err(Error::Poll(PollError::TimedOut)), - Retry::After(d) => d, - }, - Ok(Some(tok)) => return Ok(tok), - Err(e @ PollError::AccessDenied) - | Err(e @ PollError::TimedOut) - | Err(e @ PollError::Expired(_)) => return Err(Error::Poll(e)), - Err(ref e) => { - error!("Unknown error from poll token api: {}", e); - pollinf.interval + Ok(token) => return Ok(token), + Err(Error::AuthError(AuthError { error, .. })) + if error.as_str() == "authorization_pending" => + { + interval } + Err(Error::AuthError(AuthError { error, .. })) if error.as_str() == "slow_down" => { + interval + Duration::from_secs(5) + } + Err(err) => return Err(err), } } } @@ -170,7 +154,7 @@ impl DeviceFlow { let json_bytes = resp.into_body().try_concat().await?; let decoded: JsonData = - serde_json::from_slice::>(&json_bytes)?.into_result()?; + serde_json::from_slice::>(&json_bytes)?.into_result()?; let expires_in = decoded.expires_in.unwrap_or(60 * 60); let pi = PollInformation { user_code: decoded.user_code, @@ -204,17 +188,10 @@ impl DeviceFlow { client: &hyper::Client, device_code: &str, grant_type: &str, - expires_at: DateTime, - flow_delegate: &dyn DeviceFlowDelegate, - ) -> Result, PollError> + ) -> Result where C: hyper::client::connect::Connect + 'static, { - if expires_at <= Utc::now() { - flow_delegate.expired(expires_at); - return Err(PollError::Expired(expires_at)); - } - // We should be ready for a new request let req = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[ @@ -229,44 +206,11 @@ impl DeviceFlow { .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") .body(hyper::Body::from(req)) .unwrap(); // TODO: Error checking - let res = client - .request(request) - .await - .map_err(PollError::HttpError)?; - let body = res - .into_body() - .try_concat() - .await - .map_err(PollError::HttpError)?; - #[derive(Deserialize)] - struct JsonError { - error: String, - } - - match serde_json::from_slice::(&body) { - Err(_) => {} // ignore, move on, it's not an error - Ok(res) => { - match res.error.as_ref() { - "access_denied" => { - flow_delegate.denied(); - return Err(PollError::AccessDenied); - } - "authorization_pending" => return Ok(None), - s => { - return Err(PollError::Other(format!( - "server message '{}' not understood", - s - ))) - } - }; - } - } - - // yes, we expect that ! - let mut t: Token = serde_json::from_slice(&body).unwrap(); + let res = client.request(request).await?; + let body = res.into_body().try_concat().await?; + let mut t = serde_json::from_slice::>(&body)?.into_result()?; t.set_expiry_absolute(); - - Ok(Some(t)) + Ok(t) } } @@ -307,7 +251,6 @@ mod tests { app_secret, device_code_url: device_code_url.into(), flow_delegate: Box::new(FD), - wait_duration: Duration::from_secs(5), grant_type: GOOGLE_GRANT_TYPE.into(), }; @@ -415,7 +358,7 @@ mod tests { .token(&client, &["https://www.googleapis.com/scope/1"]) .await; assert!(res.is_err()); - assert!(format!("{}", res.unwrap_err()).contains("Access denied by user")); + assert!(format!("{}", res.unwrap_err()).contains("access_denied")); _m.assert(); } } diff --git a/src/error.rs b/src/error.rs index 10b262f..0e132f5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,68 +1,142 @@ //! Module containing various error types. +use std::borrow::Cow; use std::error::Error as StdError; use std::fmt; use std::io; -use chrono::{DateTime, Utc}; use serde::Deserialize; +/// Error returned by the authorization server. +/// https://tools.ietf.org/html/rfc6749#section-5.2 +/// https://tools.ietf.org/html/rfc8628#section-3.5 #[derive(Deserialize, Debug)] -pub(crate) struct JsonError { - pub error: String, +pub struct AuthError { + /// Error code from the server. + pub error: AuthErrorCode, + /// Human-readable text providing additional information. pub error_description: Option, + /// A URI identifying a human-readable web page with information about the error. pub error_uri: Option, } -/// A helper type to deserialize either a JsonError or another piece of data. -#[derive(Deserialize, Debug)] -#[serde(untagged)] -pub(crate) enum JsonErrorOr { - Err(JsonError), - Data(T), -} - -impl JsonErrorOr { - pub(crate) fn into_result(self) -> Result { - match self { - JsonErrorOr::Err(err) => Result::Err(err), - JsonErrorOr::Data(value) => Result::Ok(value), +impl fmt::Display for AuthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", &self.error.as_str())?; + if let Some(desc) = &self.error_description { + write!(f, ": {}", desc)?; } + if let Some(uri) = &self.error_uri { + write!(f, "; See {} for more info", uri)?; + } + Ok(()) } } +impl StdError for AuthError {} -/// Encapsulates all possible results of a `poll_token(...)` operation in the Device flow. -#[derive(Debug)] -pub enum PollError { - /// Connection failure - retry if you think it's worth it - HttpError(hyper::Error), - /// Indicates we are expired, including the expiration date - Expired(DateTime), - /// Indicates that the user declined access. String is server response +/// The error code returned by the authorization server. +#[derive(Debug, Clone, Eq, PartialEq)] + +pub enum AuthErrorCode { + /// invalid_request + InvalidRequest, + /// invalid_client + InvalidClient, + /// invalid_grant + InvalidGrant, + /// unauthorized_client + UnauthorizedClient, + /// unsupported_grant_type + UnsupportedGrantType, + /// invalid_scope + InvalidScope, + /// access_denied AccessDenied, - /// Indicates that too many attempts failed. - TimedOut, - /// Other type of error. + /// expired_token + ExpiredToken, + /// other error Other(String), } -impl fmt::Display for PollError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - PollError::HttpError(ref err) => err.fmt(f), - PollError::Expired(ref date) => writeln!(f, "Authentication expired at {}", date), - PollError::AccessDenied => "Access denied by user".fmt(f), - PollError::TimedOut => "Timed out waiting for token".fmt(f), - PollError::Other(ref s) => format!("Unknown server error: {}", s).fmt(f), +impl AuthErrorCode { + /// The error code as a &str + pub fn as_str(&self) -> &str { + match self { + AuthErrorCode::InvalidRequest => "invalid_request", + AuthErrorCode::InvalidClient => "invalid_client", + AuthErrorCode::InvalidGrant => "invalid_grant", + AuthErrorCode::UnauthorizedClient => "unauthorized_client", + AuthErrorCode::UnsupportedGrantType => "unsupported_grant_type", + AuthErrorCode::InvalidScope => "invalid_scope", + AuthErrorCode::AccessDenied => "access_denied", + AuthErrorCode::ExpiredToken => "expired_token", + AuthErrorCode::Other(s) => s.as_str(), + } + } + + fn from_string<'a>(s: impl Into>) -> AuthErrorCode { + let s = s.into(); + match s.as_ref() { + "invalid_request" => AuthErrorCode::InvalidRequest, + "invalid_client" => AuthErrorCode::InvalidClient, + "invalid_grant" => AuthErrorCode::InvalidGrant, + "unauthorized_client" => AuthErrorCode::UnauthorizedClient, + "unsupported_grant_type" => AuthErrorCode::UnsupportedGrantType, + "invalid_scope" => AuthErrorCode::InvalidScope, + "access_denied" => AuthErrorCode::AccessDenied, + "expired_token" => AuthErrorCode::ExpiredToken, + _ => AuthErrorCode::Other(s.into_owned()), } } } -impl StdError for PollError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - match *self { - PollError::HttpError(ref e) => Some(e), - _ => None, +impl From for AuthErrorCode { + fn from(s: String) -> Self { + AuthErrorCode::from_string(s) + } +} + +impl<'a> From<&'a str> for AuthErrorCode { + fn from(s: &str) -> Self { + AuthErrorCode::from_string(s) + } +} + +impl<'de> Deserialize<'de> for AuthErrorCode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct V; + impl<'de> serde::de::Visitor<'de> for V { + type Value = AuthErrorCode; + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("any string") + } + fn visit_string(self, value: String) -> Result { + Ok(value.into()) + } + fn visit_str(self, value: &str) -> Result { + Ok(value.into()) + } + } + deserializer.deserialize_string(V) + } +} + +/// A helper type to deserialize either an AuthError or another piece of data. +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub(crate) enum AuthErrorOr { + AuthError(AuthError), + Data(T), +} + +impl AuthErrorOr { + pub(crate) fn into_result(self) -> Result { + match self { + AuthErrorOr::AuthError(err) => Result::Err(err), + AuthErrorOr::Data(value) => Result::Ok(value), } } } @@ -73,22 +147,13 @@ pub enum Error { /// Indicates connection failure HttpError(hyper::Error), /// The server returned an error. - NegativeServerResponse { - /// The error code - error: String, - /// Detailed description - error_description: Option, - }, + AuthError(AuthError), /// Error while decoding a JSON response. JSONError(serde_json::Error), /// Error within user input. UserError(String), /// A lower level IO error. LowLevelError(io::Error), - /// A poll error occurred in the DeviceFlow. - Poll(PollError), - /// An error occurred while refreshing tokens. - Refresh(RefreshError), } impl From for Error { @@ -97,12 +162,9 @@ impl From for Error { } } -impl From for Error { - fn from(value: JsonError) -> Error { - Error::NegativeServerResponse { - error: value.error, - error_description: value.error_description, - } +impl From for Error { + fn from(value: AuthError) -> Error { + Error::AuthError(value) } } @@ -112,35 +174,21 @@ impl From for Error { } } -impl From for Error { - fn from(value: RefreshError) -> Error { - Error::Refresh(value) - } -} - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { Error::HttpError(ref err) => err.fmt(f), - Error::NegativeServerResponse { - ref error, - ref error_description, - } => { - error.fmt(f)?; - if let Some(ref desc) = *error_description { - write!(f, ": {}", desc)?; - } - "\n".fmt(f) + Error::AuthError(ref err) => err.fmt(f), + Error::JSONError(ref e) => { + write!( + f, + "JSON Error; this might be a bug with unexpected server responses! {}", + e + )?; + Ok(()) } - Error::JSONError(ref e) => format!( - "JSON Error; this might be a bug with unexpected server responses! {}", - e - ) - .fmt(f), Error::UserError(ref s) => s.fmt(f), Error::LowLevelError(ref e) => e.fmt(f), - Error::Poll(ref pe) => pe.fmt(f), - Error::Refresh(ref rr) => format!("{:?}", rr).fmt(f), } } } @@ -149,39 +197,55 @@ impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match *self { Error::HttpError(ref err) => Some(err), - Error::LowLevelError(ref err) => Some(err), + Error::AuthError(ref err) => Some(err), Error::JSONError(ref err) => Some(err), + Error::LowLevelError(ref err) => Some(err), _ => None, } } } -/// All possible outcomes of the refresh flow -#[derive(Debug)] -pub enum RefreshError { - /// Indicates connection failure - HttpError(hyper::Error), - /// The server did not answer with a new token, providing the server message - ServerError(String, Option), -} +#[cfg(test)] +mod tests { + use super::*; -impl From for RefreshError { - fn from(value: hyper::Error) -> Self { - RefreshError::HttpError(value) - } -} - -impl From for RefreshError { - fn from(value: JsonError) -> Self { - RefreshError::ServerError(value.error, value.error_description) - } -} - -impl From for RefreshError { - fn from(_value: serde_json::Error) -> Self { - RefreshError::ServerError( - "failed to deserialize json token from refresh response".to_owned(), - None, - ) + #[test] + fn test_auth_error_code_deserialize() { + assert_eq!( + AuthErrorCode::InvalidRequest, + serde_json::from_str(r#""invalid_request""#).unwrap() + ); + assert_eq!( + AuthErrorCode::InvalidClient, + serde_json::from_str(r#""invalid_client""#).unwrap() + ); + assert_eq!( + AuthErrorCode::InvalidGrant, + serde_json::from_str(r#""invalid_grant""#).unwrap() + ); + assert_eq!( + AuthErrorCode::UnauthorizedClient, + serde_json::from_str(r#""unauthorized_client""#).unwrap() + ); + assert_eq!( + AuthErrorCode::UnsupportedGrantType, + serde_json::from_str(r#""unsupported_grant_type""#).unwrap() + ); + assert_eq!( + AuthErrorCode::InvalidScope, + serde_json::from_str(r#""invalid_scope""#).unwrap() + ); + assert_eq!( + AuthErrorCode::AccessDenied, + serde_json::from_str(r#""access_denied""#).unwrap() + ); + assert_eq!( + AuthErrorCode::ExpiredToken, + serde_json::from_str(r#""expired_token""#).unwrap() + ); + assert_eq!( + AuthErrorCode::Other("undefined".to_owned()), + serde_json::from_str(r#""undefined""#).unwrap() + ); } } diff --git a/src/installed.rs b/src/installed.rs index 54ae4de..5affa37 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -3,7 +3,7 @@ // Refer to the project root for licensing information. // use crate::authenticator_delegate::{DefaultInstalledFlowDelegate, InstalledFlowDelegate}; -use crate::error::{Error, JsonErrorOr}; +use crate::error::{AuthErrorOr, Error}; use crate::types::{ApplicationSecret, Token}; use std::convert::AsRef; @@ -201,7 +201,7 @@ impl InstalledFlow { refresh_token, token_type, expires_in, - } = serde_json::from_slice::>(&body)?.into_result()?; + } = serde_json::from_slice::>(&body)?.into_result()?; let mut token = Token { access_token, refresh_token, diff --git a/src/refresh.rs b/src/refresh.rs index e312287..ccf2d4d 100644 --- a/src/refresh.rs +++ b/src/refresh.rs @@ -1,4 +1,4 @@ -use crate::error::{JsonErrorOr, RefreshError}; +use crate::error::{AuthErrorOr, Error}; use crate::types::{ApplicationSecret, Token}; use chrono::Utc; @@ -33,7 +33,7 @@ impl RefreshFlow { client: &hyper::Client, client_secret: &ApplicationSecret, refresh_token: &str, - ) -> Result { + ) -> Result { let req = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[ ("client_id", client_secret.client_id.as_str()), @@ -46,7 +46,7 @@ impl RefreshFlow { let request = hyper::Request::post(&client_secret.token_uri) .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") .body(hyper::Body::from(req)) - .unwrap(); // TODO: error handling + .unwrap(); let resp = client.request(request).await?; let body = resp.into_body().try_concat().await?; @@ -62,7 +62,7 @@ impl RefreshFlow { access_token, token_type, expires_in, - } = serde_json::from_slice::>(&body)?.into_result()?; + } = serde_json::from_slice::>(&body)?.into_result()?; Ok(Token { access_token, token_type, @@ -116,13 +116,16 @@ mod tests { .match_body( mockito::Matcher::Regex(".*client_id=902216714886-k2v9uei3p1dk6h686jbsn9mo96tnbvto.apps.googleusercontent.com.*refresh_token=my-refresh-token.*".to_string())) .with_status(400) - .with_body(r#"{"error": "invalid_token"}"#) + .with_body(r#"{"error": "invalid_request"}"#) .create(); let rr = RefreshFlow::refresh_token(&client, &app_secret, refresh_token).await; match rr { - Err(RefreshError::ServerError(e, None)) => { - assert_eq!(e, "invalid_token"); + Err(Error::AuthError(auth_error)) => { + assert_eq!( + auth_error.error, + crate::error::AuthErrorCode::InvalidRequest + ); } _ => panic!(format!("unexpected RefreshResult {:?}", rr)), } diff --git a/src/service_account.rs b/src/service_account.rs index 71ac5c2..89140bd 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -11,7 +11,7 @@ //! Copyright (c) 2016 Google Inc (lewinb@google.com). //! -use crate::error::{Error, JsonErrorOr}; +use crate::error::{AuthErrorOr, Error}; use crate::types::Token; use std::io; @@ -215,7 +215,7 @@ impl ServiceAccountFlow { access_token, token_type, expires_in, - } = serde_json::from_slice::>(&body)?.into_result()?; + } = serde_json::from_slice::>(&body)?.into_result()?; let expires_ts = chrono::Utc::now().timestamp() + expires_in; Ok(Token { access_token,