yup_oauth2/
authenticator.rs

1//! Module containing the core functionality for OAuth2 Authentication.
2use crate::application_default_credentials::{
3    ApplicationDefaultCredentialsFlow, ApplicationDefaultCredentialsFlowOpts,
4};
5use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate};
6use crate::authorized_user::{AuthorizedUserFlow, AuthorizedUserSecret};
7#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
8use crate::client::DefaultHyperClientBuilder;
9use crate::client::{HttpClient, HyperClientBuilder};
10use crate::device::DeviceFlow;
11use crate::error::Error;
12use crate::external_account::{ExternalAccountFlow, ExternalAccountSecret};
13use crate::installed::{InstalledFlow, InstalledFlowReturnMethod};
14use crate::refresh::RefreshFlow;
15use crate::service_account_impersonator::ServiceAccountImpersonationFlow;
16
17#[cfg(feature = "service-account")]
18use crate::service_account::{self, ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey};
19use crate::storage::{self, Storage, TokenStorage};
20use crate::types::{AccessToken, ApplicationSecret, TokenInfo};
21use private::AuthFlow;
22
23use crate::access_token::AccessTokenFlow;
24
25use hyper_util::client::legacy::connect::Connect;
26use std::borrow::Cow;
27use std::fmt;
28use std::io;
29use std::path::PathBuf;
30use std::sync::Arc;
31use std::time::Duration;
32use tokio::sync::Mutex;
33
34struct InnerAuthenticator<C>
35where
36    C: Connect + Clone + Send + Sync + 'static,
37{
38    hyper_client: HttpClient<C>,
39    storage: Storage,
40    auth_flow: AuthFlow,
41}
42
43/// Authenticator is responsible for fetching tokens, handling refreshing tokens,
44/// and optionally persisting tokens to disk.
45#[derive(Clone)]
46pub struct Authenticator<C>
47where
48    C: Connect + Clone + Send + Sync + 'static,
49{
50    inner: Arc<InnerAuthenticator<C>>,
51}
52
53struct DisplayScopes<'a, T>(&'a [T]);
54impl<T> fmt::Display for DisplayScopes<'_, T>
55where
56    T: AsRef<str>,
57{
58    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59        f.write_str("[")?;
60        let mut iter = self.0.iter();
61        if let Some(first) = iter.next() {
62            f.write_str(first.as_ref())?;
63            for scope in iter {
64                f.write_str(", ")?;
65                f.write_str(scope.as_ref())?;
66            }
67        }
68        f.write_str("]")
69    }
70}
71
72impl<C> Authenticator<C>
73where
74    C: Connect + Clone + Send + Sync + 'static,
75{
76    /// Return the current token for the provided scopes.
77    pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result<AccessToken, Error>
78    where
79        T: AsRef<str>,
80    {
81        self.find_token_info(scopes, /* force_refresh = */ false)
82            .await
83            .map(|info| info.into())
84    }
85
86    /// Return a token for the provided scopes, but don't reuse cached tokens. Instead,
87    /// always fetch a new token from the OAuth server.
88    pub async fn force_refreshed_token<'a, T>(
89        &'a self,
90        scopes: &'a [T],
91    ) -> Result<AccessToken, Error>
92    where
93        T: AsRef<str>,
94    {
95        self.find_token_info(scopes, /* force_refresh = */ true)
96            .await
97            .map(|info| info.into())
98    }
99
100    /// Return the current ID token for the provided scopes, if any
101    pub async fn id_token<'a, T>(&'a self, scopes: &'a [T]) -> Result<Option<String>, Error>
102    where
103        T: AsRef<str>,
104    {
105        self.find_token_info(scopes, /* force_refresh = */ false)
106            .await
107            .map(|info| info.id_token)
108    }
109
110    /// Return a cached token or fetch a new one from the server.
111    async fn find_token_info<'a, T>(
112        &'a self,
113        scopes: &'a [T],
114        force_refresh: bool,
115    ) -> Result<TokenInfo, Error>
116    where
117        T: AsRef<str>,
118    {
119        log::debug!(
120            "access token requested for scopes: {}",
121            DisplayScopes(scopes)
122        );
123        let hashed_scopes = storage::ScopeSet::from(scopes);
124        match (
125            self.inner.storage.get(hashed_scopes).await,
126            self.inner.auth_flow.app_secret(),
127        ) {
128            (Some(t), _) if !t.is_expired() && !force_refresh => {
129                // unexpired token found
130                log::debug!("found valid token in cache: {:?}", t);
131                Ok(t)
132            }
133            (
134                Some(TokenInfo {
135                    refresh_token: Some(refresh_token),
136                    ..
137                }),
138                Some(app_secret),
139            ) => {
140                // token is expired but has a refresh token.
141                let token_info_result = RefreshFlow::refresh_token(
142                    &self.inner.hyper_client,
143                    app_secret,
144                    &refresh_token,
145                )
146                .await;
147                let token_info = if let Ok(token_info) = token_info_result {
148                    token_info
149                } else {
150                    // token refresh failed.
151                    self.inner
152                        .auth_flow
153                        .token(&self.inner.hyper_client, scopes)
154                        .await?
155                };
156                self.inner
157                    .storage
158                    .set(hashed_scopes, token_info.clone())
159                    .await?;
160                Ok(token_info)
161            }
162            _ => {
163                // no token in the cache or the token returned can't be refreshed.
164                let token_info = self
165                    .inner
166                    .auth_flow
167                    .token(&self.inner.hyper_client, scopes)
168                    .await?;
169                self.inner
170                    .storage
171                    .set(hashed_scopes, token_info.clone())
172                    .await?;
173                Ok(token_info)
174            }
175        }
176    }
177}
178
179/// Configure an Authenticator using the builder pattern.
180pub struct AuthenticatorBuilder<C, F> {
181    hyper_client_builder: C,
182    storage_type: StorageType,
183    auth_flow: F,
184}
185
186/// Create an authenticator that uses the installed flow.
187/// ```
188/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
189/// # async fn foo() {
190/// # use yup_oauth2::InstalledFlowReturnMethod;
191/// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultInstalledFlowDelegate;
192/// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap();
193///     let authenticator = yup_oauth2::InstalledFlowAuthenticator::builder(
194///         app_secret,
195///         InstalledFlowReturnMethod::HTTPRedirect,
196///     )
197///     .build()
198///     .await
199///     .expect("failed to create authenticator");
200/// # }
201/// ```
202pub struct InstalledFlowAuthenticator;
203impl InstalledFlowAuthenticator {
204    /// Use the builder pattern to create an Authenticator that uses the installed flow.
205    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
206    #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
207    pub fn builder(
208        app_secret: ApplicationSecret,
209        method: InstalledFlowReturnMethod,
210    ) -> AuthenticatorBuilder<DefaultHyperClientBuilder, InstalledFlow> {
211        Self::with_client(app_secret, method, DefaultHyperClientBuilder::default())
212    }
213
214    /// Construct a new Authenticator that uses the installed flow and the provided http client.
215    pub fn with_client<C>(
216        app_secret: ApplicationSecret,
217        method: InstalledFlowReturnMethod,
218        client: C,
219    ) -> AuthenticatorBuilder<C, InstalledFlow> {
220        AuthenticatorBuilder::new(InstalledFlow::new(app_secret, method), client)
221    }
222}
223
224/// Create an authenticator that uses the device flow.
225/// ```
226/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
227/// # async fn foo() {
228/// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap();
229///     let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret)
230///         .build()
231///         .await
232///         .expect("failed to create authenticator");
233/// # }
234/// ```
235pub struct DeviceFlowAuthenticator;
236impl DeviceFlowAuthenticator {
237    /// Use the builder pattern to create an Authenticator that uses the device flow.
238    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
239    #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
240    pub fn builder(
241        app_secret: ApplicationSecret,
242    ) -> AuthenticatorBuilder<DefaultHyperClientBuilder, DeviceFlow> {
243        Self::with_client(app_secret, DefaultHyperClientBuilder::default())
244    }
245
246    /// Construct a new Authenticator that uses the installed flow and the provided http client.
247    pub fn with_client<C>(
248        app_secret: ApplicationSecret,
249        client: C,
250    ) -> AuthenticatorBuilder<C, DeviceFlow> {
251        AuthenticatorBuilder::new(DeviceFlow::new(app_secret), client)
252    }
253}
254
255/// Create an authenticator that uses a service account.
256/// ```
257/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
258/// # async fn foo() {
259/// # let service_account_key = yup_oauth2::read_service_account_key("/tmp/foo").await.unwrap();
260///     let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(service_account_key)
261///         .build()
262///         .await
263///         .expect("failed to create authenticator");
264/// # }
265/// ```
266#[cfg(feature = "service-account")]
267pub struct ServiceAccountAuthenticator;
268
269#[cfg(feature = "service-account")]
270impl ServiceAccountAuthenticator {
271    /// Use the builder pattern to create an Authenticator that uses a service account.
272    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
273    #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
274    pub fn builder(
275        service_account_key: ServiceAccountKey,
276    ) -> AuthenticatorBuilder<DefaultHyperClientBuilder, ServiceAccountFlowOpts> {
277        Self::with_client(service_account_key, DefaultHyperClientBuilder::default())
278    }
279
280    /// Construct a new Authenticator that uses the installed flow and the provided http client.
281    pub fn with_client<C>(
282        service_account_key: ServiceAccountKey,
283        client: C,
284    ) -> AuthenticatorBuilder<C, ServiceAccountFlowOpts> {
285        AuthenticatorBuilder::new(
286            ServiceAccountFlowOpts {
287                key: service_account::FlowOptsKey::Key(Box::new(service_account_key)),
288                subject: None,
289            },
290            client,
291        )
292    }
293}
294
295/// Create an authenticator that uses a application default credentials.
296/// ```
297/// # #[cfg(all(any(feature = "hyper-rustls", feature = "hyper-tls"), feature = "service-account"))]
298/// # async fn foo() {
299/// #    use yup_oauth2::ApplicationDefaultCredentialsAuthenticator;
300/// #    use yup_oauth2::ApplicationDefaultCredentialsFlowOpts;
301/// #    use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes;
302///
303///     let opts = ApplicationDefaultCredentialsFlowOpts::default();
304///     let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await {
305///         ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth
306///             .build()
307///             .await
308///             .expect("Unable to create instance metadata authenticator"),
309///         ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => auth
310///             .build()
311///             .await
312///             .expect("Unable to create service account authenticator"),
313///     };
314/// # }
315/// ```
316pub struct ApplicationDefaultCredentialsAuthenticator;
317impl ApplicationDefaultCredentialsAuthenticator {
318    /// Try to build ServiceAccountFlowOpts from the environment
319    #[cfg(feature = "service-account")]
320    pub async fn from_environment() -> Result<ServiceAccountFlowOpts, std::env::VarError> {
321        let key_path = std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?;
322
323        Ok(ServiceAccountFlowOpts {
324            key: service_account::FlowOptsKey::Path(key_path.into()),
325            subject: None,
326        })
327    }
328
329    /// Use the builder pattern to deduce which model of authenticator should be used:
330    /// Service account one or GCE instance metadata kind
331    #[cfg(feature = "service-account")]
332    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
333    #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
334    pub async fn builder(
335        opts: ApplicationDefaultCredentialsFlowOpts,
336    ) -> ApplicationDefaultCredentialsTypes<DefaultHyperClientBuilder> {
337        Self::with_client(opts, DefaultHyperClientBuilder::default()).await
338    }
339
340    /// Use the builder pattern to deduce which model of authenticator should be used and allow providing a hyper client
341    #[cfg(feature = "service-account")]
342    pub async fn with_client<C>(
343        opts: ApplicationDefaultCredentialsFlowOpts,
344        client: C,
345    ) -> ApplicationDefaultCredentialsTypes<C>
346    where
347        C: HyperClientBuilder,
348    {
349        match ApplicationDefaultCredentialsAuthenticator::from_environment().await {
350            Ok(flow_opts) => {
351                let builder = AuthenticatorBuilder::new(flow_opts, client);
352
353                ApplicationDefaultCredentialsTypes::ServiceAccount(builder)
354            }
355            Err(_) => ApplicationDefaultCredentialsTypes::InstanceMetadata(
356                AuthenticatorBuilder::new(opts, client),
357            ),
358        }
359    }
360}
361/// Types of authenticators provided by ApplicationDefaultCredentialsAuthenticator
362pub enum ApplicationDefaultCredentialsTypes<C>
363where
364    C: HyperClientBuilder,
365{
366    /// Service account based authenticator signature
367    #[cfg(feature = "service-account")]
368    ServiceAccount(AuthenticatorBuilder<C, ServiceAccountFlowOpts>),
369    /// GCE Instance Metadata based authenticator signature
370    InstanceMetadata(AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts>),
371}
372
373/// Create an authenticator that uses an authorized user credentials.
374/// ```
375/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
376/// # async fn foo() {
377/// # use yup_oauth2::authenticator::AuthorizedUserAuthenticator;
378/// # let secret = yup_oauth2::read_authorized_user_secret("/tmp/foo").await.unwrap();
379///     let authenticator = yup_oauth2::AuthorizedUserAuthenticator::builder(secret)
380///         .build()
381///         .await
382///         .expect("failed to create authenticator");
383/// # }
384/// ```
385pub struct AuthorizedUserAuthenticator;
386impl AuthorizedUserAuthenticator {
387    /// Use the builder pattern to create an Authenticator that uses an authorized user.
388    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
389    #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
390    pub fn builder(
391        authorized_user_secret: AuthorizedUserSecret,
392    ) -> AuthenticatorBuilder<DefaultHyperClientBuilder, AuthorizedUserFlow> {
393        Self::with_client(authorized_user_secret, DefaultHyperClientBuilder::default())
394    }
395
396    /// Construct a new Authenticator that uses the installed flow and the provided http client.
397    pub fn with_client<C>(
398        authorized_user_secret: AuthorizedUserSecret,
399        client: C,
400    ) -> AuthenticatorBuilder<C, AuthorizedUserFlow> {
401        AuthenticatorBuilder::new(
402            AuthorizedUserFlow {
403                secret: authorized_user_secret,
404            },
405            client,
406        )
407    }
408}
409
410/// Create an authenticator that uses an external account credentials.
411/// ```
412/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
413/// # async fn foo() {
414/// # use yup_oauth2::authenticator::ExternalAccountAuthenticator;
415/// # let secret = yup_oauth2::read_external_account_secret("/tmp/foo").await.unwrap();
416///     let authenticator = yup_oauth2::ExternalAccountAuthenticator::builder(secret)
417///         .build()
418///         .await
419///         .expect("failed to create authenticator");
420/// # }
421/// ```
422pub struct ExternalAccountAuthenticator;
423impl ExternalAccountAuthenticator {
424    /// Use the builder pattern to create an Authenticator that uses an external account.
425    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
426    #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
427    pub fn builder(
428        external_account_secret: ExternalAccountSecret,
429    ) -> AuthenticatorBuilder<DefaultHyperClientBuilder, ExternalAccountFlow> {
430        Self::with_client(
431            external_account_secret,
432            DefaultHyperClientBuilder::default(),
433        )
434    }
435
436    /// Construct a new Authenticator that uses the installed flow and the provided http client.
437    pub fn with_client<C>(
438        external_account_secret: ExternalAccountSecret,
439        client: C,
440    ) -> AuthenticatorBuilder<C, ExternalAccountFlow> {
441        AuthenticatorBuilder::new(
442            ExternalAccountFlow {
443                secret: external_account_secret,
444            },
445            client,
446        )
447    }
448}
449
450/// Create a access token authenticator for use with pre-generated
451/// access tokens
452/// ```
453/// # async fn foo() {
454/// #   use yup_oauth2::authenticator::AccessTokenAuthenticator;
455/// #   let authenticator = yup_oauth2::AccessTokenAuthenticator::builder("TOKEN".to_string())
456/// #     .build()
457/// #     .await
458/// #     .expect("failed to create authenticator");
459/// # }
460/// ```
461#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
462pub struct AccessTokenAuthenticator;
463
464#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
465impl AccessTokenAuthenticator {
466    /// the builder pattern for the authenticator
467    pub fn builder(
468        access_token: String,
469    ) -> AuthenticatorBuilder<DefaultHyperClientBuilder, AccessTokenFlow> {
470        Self::with_client(access_token, DefaultHyperClientBuilder::default())
471    }
472    /// Construct a new Authenticator that uses the installed flow and the provided http client.
473    /// the client itself is not used
474    pub fn with_client<C>(
475        access_token: String,
476        client: C,
477    ) -> AuthenticatorBuilder<C, AccessTokenFlow> {
478        AuthenticatorBuilder::new(AccessTokenFlow { access_token }, client)
479    }
480}
481
482/// Create a access token authenticator that uses user secrets to impersonate
483/// a service account.
484///
485/// ```
486/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
487/// # async fn foo() {
488/// # use yup_oauth2::authenticator::AuthorizedUserAuthenticator;
489/// # let secret = yup_oauth2::read_authorized_user_secret("/tmp/foo").await.unwrap();
490/// # let email = "my-test-account@my-test-project.iam.gserviceaccount.com";
491///     let authenticator = yup_oauth2::ServiceAccountImpersonationAuthenticator::builder(secret, email)
492///         .build()
493///         .await
494///         .expect("failed to create authenticator");
495/// # }
496/// ```
497pub struct ServiceAccountImpersonationAuthenticator;
498impl ServiceAccountImpersonationAuthenticator {
499    /// Use the builder pattern to create an Authenticator that uses the device flow.
500    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
501    #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
502    pub fn builder(
503        authorized_user_secret: AuthorizedUserSecret,
504        service_account_email: &str,
505    ) -> AuthenticatorBuilder<DefaultHyperClientBuilder, ServiceAccountImpersonationFlow> {
506        Self::with_client(
507            authorized_user_secret,
508            service_account_email,
509            DefaultHyperClientBuilder::default(),
510        )
511    }
512
513    /// Construct a new Authenticator that uses the installed flow and the provided http client.
514    pub fn with_client<C>(
515        authorized_user_secret: AuthorizedUserSecret,
516        service_account_email: &str,
517        client: C,
518    ) -> AuthenticatorBuilder<C, ServiceAccountImpersonationFlow> {
519        AuthenticatorBuilder::new(
520            ServiceAccountImpersonationFlow::new(authorized_user_secret, service_account_email),
521            client,
522        )
523    }
524}
525
526/// ## Methods available when building any Authenticator.
527/// ```
528/// # async fn foo() {
529/// # let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build_http::<String>();
530/// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap();
531///     let authenticator = yup_oauth2::DeviceFlowAuthenticator::with_client(app_secret, yup_oauth2::CustomHyperClientBuilder::from(client))
532///         .persist_tokens_to_disk("/tmp/tokenfile.json")
533///         .build()
534///         .await
535///         .expect("failed to create authenticator");
536/// # }
537/// ```
538impl<C, F> AuthenticatorBuilder<C, F> {
539    async fn common_build(
540        hyper_client_builder: C,
541        storage_type: StorageType,
542        auth_flow: AuthFlow,
543    ) -> io::Result<Authenticator<C::Connector>>
544    where
545        C: HyperClientBuilder,
546    {
547        let hyper_client = hyper_client_builder.build_hyper_client().map_err(|err| {
548            io::Error::new(
549                io::ErrorKind::Other,
550                format!("failed to build hyper client: {}", err),
551            )
552        })?;
553
554        let storage = match storage_type {
555            StorageType::Memory => Storage::Memory {
556                tokens: Mutex::new(storage::JSONTokens::new()),
557            },
558            StorageType::Disk(path) => Storage::Disk(storage::DiskStorage::new(path).await?),
559            StorageType::Custom(custom_store) => Storage::Custom(custom_store),
560        };
561
562        Ok(Authenticator {
563            inner: Arc::new(InnerAuthenticator {
564                hyper_client,
565                storage,
566                auth_flow,
567            }),
568        })
569    }
570
571    fn new(auth_flow: F, hyper_client_builder: C) -> AuthenticatorBuilder<C, F> {
572        AuthenticatorBuilder {
573            hyper_client_builder,
574            storage_type: StorageType::Memory,
575            auth_flow,
576        }
577    }
578
579    /// Use the provided token storage mechanism
580    pub fn with_storage(self, storage: Box<dyn TokenStorage>) -> Self {
581        AuthenticatorBuilder {
582            storage_type: StorageType::Custom(storage),
583            ..self
584        }
585    }
586
587    /// Persist tokens to disk in the provided filename.
588    pub fn persist_tokens_to_disk<P: Into<PathBuf>>(self, path: P) -> AuthenticatorBuilder<C, F> {
589        AuthenticatorBuilder {
590            storage_type: StorageType::Disk(path.into()),
591            ..self
592        }
593    }
594}
595
596impl<C, F> AuthenticatorBuilder<C, F>
597where
598    C: HyperClientBuilder,
599{
600    /// Sets the duration after which a HTTP request times out
601    pub fn with_timeout(self, timeout: Duration) -> Self {
602        AuthenticatorBuilder {
603            hyper_client_builder: self.hyper_client_builder.with_timeout(timeout),
604            ..self
605        }
606    }
607}
608
609/// ## Methods available when building a device flow Authenticator.
610/// ```
611/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
612/// # async fn foo() {
613/// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultDeviceFlowDelegate;
614/// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap();
615///     let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret)
616///         .device_code_url("foo")
617///         .flow_delegate(Box::new(custom_flow_delegate))
618///         .grant_type("foo")
619///         .build()
620///         .await
621///         .expect("failed to create authenticator");
622/// # }
623/// ```
624impl<C> AuthenticatorBuilder<C, DeviceFlow> {
625    /// Use the provided device code url.
626    pub fn device_code_url(self, url: impl Into<Cow<'static, str>>) -> Self {
627        AuthenticatorBuilder {
628            auth_flow: DeviceFlow {
629                device_code_url: url.into(),
630                ..self.auth_flow
631            },
632            ..self
633        }
634    }
635
636    /// Use the provided DeviceFlowDelegate.
637    pub fn flow_delegate(self, flow_delegate: Box<dyn DeviceFlowDelegate>) -> Self {
638        AuthenticatorBuilder {
639            auth_flow: DeviceFlow {
640                flow_delegate,
641                ..self.auth_flow
642            },
643            ..self
644        }
645    }
646
647    /// Use the provided grant type.
648    pub fn grant_type(self, grant_type: impl Into<Cow<'static, str>>) -> Self {
649        AuthenticatorBuilder {
650            auth_flow: DeviceFlow {
651                grant_type: grant_type.into(),
652                ..self.auth_flow
653            },
654            ..self
655        }
656    }
657
658    /// Create the authenticator.
659    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
660    where
661        C: HyperClientBuilder,
662    {
663        Self::common_build(
664            self.hyper_client_builder,
665            self.storage_type,
666            AuthFlow::DeviceFlow(self.auth_flow),
667        )
668        .await
669    }
670}
671
672/// ## Methods available when building an installed flow Authenticator.
673/// ```
674/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
675/// # async fn foo() {
676/// # use yup_oauth2::InstalledFlowReturnMethod;
677/// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultInstalledFlowDelegate;
678/// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap();
679///     let authenticator = yup_oauth2::InstalledFlowAuthenticator::builder(
680///         app_secret,
681///         InstalledFlowReturnMethod::HTTPRedirect,
682///     )
683///     .flow_delegate(Box::new(custom_flow_delegate))
684///     .build()
685///     .await
686///     .expect("failed to create authenticator");
687/// # }
688/// ```
689impl<C> AuthenticatorBuilder<C, InstalledFlow> {
690    /// Use the provided InstalledFlowDelegate.
691    pub fn flow_delegate(self, flow_delegate: Box<dyn InstalledFlowDelegate>) -> Self {
692        AuthenticatorBuilder {
693            auth_flow: InstalledFlow {
694                flow_delegate,
695                ..self.auth_flow
696            },
697            ..self
698        }
699    }
700    /// Force the user to select an account on the initial request
701    pub fn force_account_selection(self, force: bool) -> Self {
702        AuthenticatorBuilder {
703            auth_flow: InstalledFlow {
704                force_account_selection: force,
705                ..self.auth_flow
706            },
707            ..self
708        }
709    }
710
711    /// Create the authenticator.
712    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
713    where
714        C: HyperClientBuilder,
715    {
716        Self::common_build(
717            self.hyper_client_builder,
718            self.storage_type,
719            AuthFlow::InstalledFlow(self.auth_flow),
720        )
721        .await
722    }
723}
724
725/// ## Methods available when building a service account authenticator.
726/// ```
727/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
728/// # async fn foo() {
729/// # let service_account_key = yup_oauth2::read_service_account_key("/tmp/foo").await.unwrap();
730///     let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(
731///         service_account_key,
732///     )
733///     .subject("mysubject")
734///     .build()
735///     .await
736///     .expect("failed to create authenticator");
737/// # }
738/// ```
739#[cfg(feature = "service-account")]
740impl<C> AuthenticatorBuilder<C, ServiceAccountFlowOpts> {
741    /// Use the provided subject.
742    pub fn subject(self, subject: impl Into<String>) -> Self {
743        AuthenticatorBuilder {
744            auth_flow: ServiceAccountFlowOpts {
745                subject: Some(subject.into()),
746                ..self.auth_flow
747            },
748            ..self
749        }
750    }
751
752    /// Create the authenticator.
753    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
754    where
755        C: HyperClientBuilder,
756    {
757        let service_account_auth_flow = ServiceAccountFlow::new(self.auth_flow).await?;
758        Self::common_build(
759            self.hyper_client_builder,
760            self.storage_type,
761            AuthFlow::ServiceAccountFlow(service_account_auth_flow),
762        )
763        .await
764    }
765}
766
767impl<C> AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts> {
768    /// Create the authenticator.
769    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
770    where
771        C: HyperClientBuilder,
772    {
773        let application_default_credential_flow =
774            ApplicationDefaultCredentialsFlow::new(self.auth_flow);
775        Self::common_build(
776            self.hyper_client_builder,
777            self.storage_type,
778            AuthFlow::ApplicationDefaultCredentialsFlow(application_default_credential_flow),
779        )
780        .await
781    }
782}
783
784/// ## Methods available when building an authorized user flow Authenticator.
785impl<C> AuthenticatorBuilder<C, AuthorizedUserFlow> {
786    /// Create the authenticator.
787    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
788    where
789        C: HyperClientBuilder,
790    {
791        Self::common_build(
792            self.hyper_client_builder,
793            self.storage_type,
794            AuthFlow::AuthorizedUserFlow(self.auth_flow),
795        )
796        .await
797    }
798}
799
800/// ## Methods available when building an external account flow Authenticator.
801impl<C> AuthenticatorBuilder<C, ExternalAccountFlow> {
802    /// Create the authenticator.
803    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
804    where
805        C: HyperClientBuilder,
806    {
807        Self::common_build(
808            self.hyper_client_builder,
809            self.storage_type,
810            AuthFlow::ExternalAccountFlow(self.auth_flow),
811        )
812        .await
813    }
814}
815
816/// ## Methods available when building a service account impersonation Authenticator.
817impl<C> AuthenticatorBuilder<C, ServiceAccountImpersonationFlow> {
818    /// Create the authenticator.
819    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
820    where
821        C: HyperClientBuilder,
822    {
823        Self::common_build(
824            self.hyper_client_builder,
825            self.storage_type,
826            AuthFlow::ServiceAccountImpersonationFlow(self.auth_flow),
827        )
828        .await
829    }
830
831    /// Configure this authenticator to impersonate an ID token (rather an an access token,
832    /// as is the default).
833    ///
834    /// For more on impersonating ID tokens, see [google's docs](https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-oidc).
835    pub fn request_id_token(mut self) -> Self {
836        self.auth_flow.access_token = false;
837        self
838    }
839}
840
841/// ## Methods available when building an access token flow Authenticator.
842impl<C> AuthenticatorBuilder<C, AccessTokenFlow> {
843    /// Create the authenticator.
844    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
845    where
846        C: HyperClientBuilder,
847    {
848        Self::common_build(
849            self.hyper_client_builder,
850            self.storage_type,
851            AuthFlow::AccessTokenFlow(self.auth_flow),
852        )
853        .await
854    }
855}
856mod private {
857    use crate::access_token::AccessTokenFlow;
858    use crate::application_default_credentials::ApplicationDefaultCredentialsFlow;
859    use crate::authorized_user::AuthorizedUserFlow;
860    use crate::client::SendRequest;
861    use crate::device::DeviceFlow;
862    use crate::error::Error;
863    use crate::external_account::ExternalAccountFlow;
864    use crate::installed::InstalledFlow;
865    #[cfg(feature = "service-account")]
866    use crate::service_account::ServiceAccountFlow;
867    use crate::service_account_impersonator::ServiceAccountImpersonationFlow;
868    use crate::types::{ApplicationSecret, TokenInfo};
869
870    #[allow(clippy::enum_variant_names)]
871    pub enum AuthFlow {
872        DeviceFlow(DeviceFlow),
873        InstalledFlow(InstalledFlow),
874        #[cfg(feature = "service-account")]
875        ServiceAccountFlow(ServiceAccountFlow),
876        ServiceAccountImpersonationFlow(ServiceAccountImpersonationFlow),
877        ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow),
878        AuthorizedUserFlow(AuthorizedUserFlow),
879        ExternalAccountFlow(ExternalAccountFlow),
880        AccessTokenFlow(AccessTokenFlow),
881    }
882
883    impl AuthFlow {
884        pub(crate) fn app_secret(&self) -> Option<&ApplicationSecret> {
885            match self {
886                AuthFlow::DeviceFlow(device_flow) => Some(&device_flow.app_secret),
887                AuthFlow::InstalledFlow(installed_flow) => Some(&installed_flow.app_secret),
888                #[cfg(feature = "service-account")]
889                AuthFlow::ServiceAccountFlow(_) => None,
890                AuthFlow::ServiceAccountImpersonationFlow(_) => None,
891                AuthFlow::ApplicationDefaultCredentialsFlow(_) => None,
892                AuthFlow::AuthorizedUserFlow(_) => None,
893                AuthFlow::ExternalAccountFlow(_) => None,
894                AuthFlow::AccessTokenFlow(_) => None,
895            }
896        }
897
898        pub(crate) async fn token<'a, T>(
899            &'a self,
900            hyper_client: &'a impl SendRequest,
901            scopes: &'a [T],
902        ) -> Result<TokenInfo, Error>
903        where
904            T: AsRef<str>,
905        {
906            match self {
907                AuthFlow::DeviceFlow(device_flow) => device_flow.token(hyper_client, scopes).await,
908                AuthFlow::InstalledFlow(installed_flow) => {
909                    installed_flow.token(hyper_client, scopes).await
910                }
911                #[cfg(feature = "service-account")]
912                AuthFlow::ServiceAccountFlow(service_account_flow) => {
913                    service_account_flow.token(hyper_client, scopes).await
914                }
915                AuthFlow::ServiceAccountImpersonationFlow(service_account_impersonation_flow) => {
916                    service_account_impersonation_flow
917                        .token(hyper_client, scopes)
918                        .await
919                }
920                AuthFlow::ApplicationDefaultCredentialsFlow(adc_flow) => {
921                    adc_flow.token(hyper_client, scopes).await
922                }
923                AuthFlow::AuthorizedUserFlow(authorized_user_flow) => {
924                    authorized_user_flow.token(hyper_client, scopes).await
925                }
926                AuthFlow::ExternalAccountFlow(external_account_flow) => {
927                    external_account_flow.token(hyper_client, scopes).await
928                }
929                AuthFlow::AccessTokenFlow(access_token_flow) => {
930                    access_token_flow.token(hyper_client, scopes).await
931                }
932            }
933        }
934    }
935}
936
937#[cfg(feature = "hyper-rustls")]
938#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
939/// Default authenticator type
940pub type DefaultAuthenticator =
941    Authenticator<hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>>;
942
943#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
944#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
945/// Default authenticator type
946pub type DefaultAuthenticator =
947    Authenticator<hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>>;
948
949/// How should the acquired tokens be stored?
950enum StorageType {
951    /// Store tokens in memory (and always log in again to acquire a new token on startup)
952    Memory,
953    /// Store tokens to disk in the given file. Warning, this may be insecure unless you configure your operating system to restrict read access to the file.
954    Disk(PathBuf),
955    /// Implement your own storage provider
956    Custom(Box<dyn TokenStorage>),
957}
958
959#[cfg(test)]
960mod tests {
961    #[test]
962    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
963    fn ensure_send_sync() {
964        use super::*;
965        fn is_send_sync<T: Send + Sync>() {}
966        is_send_sync::<Authenticator<<DefaultHyperClientBuilder as HyperClientBuilder>::Connector>>(
967        )
968    }
969}