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::other(
549                format!("failed to build hyper client: {}", err),
550            )
551        })?;
552
553        let storage = match storage_type {
554            StorageType::Memory => Storage::Memory {
555                tokens: Mutex::new(storage::JSONTokens::new()),
556            },
557            StorageType::Disk(path) => Storage::Disk(storage::DiskStorage::new(path).await?),
558            StorageType::Custom(custom_store) => Storage::Custom(custom_store),
559        };
560
561        Ok(Authenticator {
562            inner: Arc::new(InnerAuthenticator {
563                hyper_client,
564                storage,
565                auth_flow,
566            }),
567        })
568    }
569
570    fn new(auth_flow: F, hyper_client_builder: C) -> AuthenticatorBuilder<C, F> {
571        AuthenticatorBuilder {
572            hyper_client_builder,
573            storage_type: StorageType::Memory,
574            auth_flow,
575        }
576    }
577
578    /// Use the provided token storage mechanism
579    pub fn with_storage(self, storage: Box<dyn TokenStorage>) -> Self {
580        AuthenticatorBuilder {
581            storage_type: StorageType::Custom(storage),
582            ..self
583        }
584    }
585
586    /// Persist tokens to disk in the provided filename.
587    pub fn persist_tokens_to_disk<P: Into<PathBuf>>(self, path: P) -> AuthenticatorBuilder<C, F> {
588        AuthenticatorBuilder {
589            storage_type: StorageType::Disk(path.into()),
590            ..self
591        }
592    }
593}
594
595impl<C, F> AuthenticatorBuilder<C, F>
596where
597    C: HyperClientBuilder,
598{
599    /// Sets the duration after which a HTTP request times out
600    pub fn with_timeout(self, timeout: Duration) -> Self {
601        AuthenticatorBuilder {
602            hyper_client_builder: self.hyper_client_builder.with_timeout(timeout),
603            ..self
604        }
605    }
606}
607
608/// ## Methods available when building a device flow Authenticator.
609/// ```
610/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
611/// # async fn foo() {
612/// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultDeviceFlowDelegate;
613/// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap();
614///     let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret)
615///         .device_code_url("foo")
616///         .flow_delegate(Box::new(custom_flow_delegate))
617///         .grant_type("foo")
618///         .build()
619///         .await
620///         .expect("failed to create authenticator");
621/// # }
622/// ```
623impl<C> AuthenticatorBuilder<C, DeviceFlow> {
624    /// Use the provided device code url.
625    pub fn device_code_url(self, url: impl Into<Cow<'static, str>>) -> Self {
626        AuthenticatorBuilder {
627            auth_flow: DeviceFlow {
628                device_code_url: url.into(),
629                ..self.auth_flow
630            },
631            ..self
632        }
633    }
634
635    /// Use the provided DeviceFlowDelegate.
636    pub fn flow_delegate(self, flow_delegate: Box<dyn DeviceFlowDelegate>) -> Self {
637        AuthenticatorBuilder {
638            auth_flow: DeviceFlow {
639                flow_delegate,
640                ..self.auth_flow
641            },
642            ..self
643        }
644    }
645
646    /// Use the provided grant type.
647    pub fn grant_type(self, grant_type: impl Into<Cow<'static, str>>) -> Self {
648        AuthenticatorBuilder {
649            auth_flow: DeviceFlow {
650                grant_type: grant_type.into(),
651                ..self.auth_flow
652            },
653            ..self
654        }
655    }
656
657    /// Create the authenticator.
658    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
659    where
660        C: HyperClientBuilder,
661    {
662        Self::common_build(
663            self.hyper_client_builder,
664            self.storage_type,
665            AuthFlow::DeviceFlow(self.auth_flow),
666        )
667        .await
668    }
669}
670
671/// ## Methods available when building an installed flow Authenticator.
672/// ```
673/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
674/// # async fn foo() {
675/// # use yup_oauth2::InstalledFlowReturnMethod;
676/// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultInstalledFlowDelegate;
677/// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap();
678///     let authenticator = yup_oauth2::InstalledFlowAuthenticator::builder(
679///         app_secret,
680///         InstalledFlowReturnMethod::HTTPRedirect,
681///     )
682///     .flow_delegate(Box::new(custom_flow_delegate))
683///     .build()
684///     .await
685///     .expect("failed to create authenticator");
686/// # }
687/// ```
688impl<C> AuthenticatorBuilder<C, InstalledFlow> {
689    /// Use the provided InstalledFlowDelegate.
690    pub fn flow_delegate(self, flow_delegate: Box<dyn InstalledFlowDelegate>) -> Self {
691        AuthenticatorBuilder {
692            auth_flow: InstalledFlow {
693                flow_delegate,
694                ..self.auth_flow
695            },
696            ..self
697        }
698    }
699    /// Force the user to select an account on the initial request
700    pub fn force_account_selection(self, force: bool) -> Self {
701        AuthenticatorBuilder {
702            auth_flow: InstalledFlow {
703                force_account_selection: force,
704                ..self.auth_flow
705            },
706            ..self
707        }
708    }
709
710    /// Create the authenticator.
711    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
712    where
713        C: HyperClientBuilder,
714    {
715        Self::common_build(
716            self.hyper_client_builder,
717            self.storage_type,
718            AuthFlow::InstalledFlow(self.auth_flow),
719        )
720        .await
721    }
722}
723
724/// ## Methods available when building a service account authenticator.
725/// ```
726/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
727/// # async fn foo() {
728/// # let service_account_key = yup_oauth2::read_service_account_key("/tmp/foo").await.unwrap();
729///     let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(
730///         service_account_key,
731///     )
732///     .subject("mysubject")
733///     .build()
734///     .await
735///     .expect("failed to create authenticator");
736/// # }
737/// ```
738#[cfg(feature = "service-account")]
739impl<C> AuthenticatorBuilder<C, ServiceAccountFlowOpts> {
740    /// Use the provided subject.
741    pub fn subject(self, subject: impl Into<String>) -> Self {
742        AuthenticatorBuilder {
743            auth_flow: ServiceAccountFlowOpts {
744                subject: Some(subject.into()),
745                ..self.auth_flow
746            },
747            ..self
748        }
749    }
750
751    /// Create the authenticator.
752    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
753    where
754        C: HyperClientBuilder,
755    {
756        let service_account_auth_flow = ServiceAccountFlow::new(self.auth_flow).await?;
757        Self::common_build(
758            self.hyper_client_builder,
759            self.storage_type,
760            AuthFlow::ServiceAccountFlow(service_account_auth_flow),
761        )
762        .await
763    }
764}
765
766impl<C> AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts> {
767    /// Create the authenticator.
768    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
769    where
770        C: HyperClientBuilder,
771    {
772        let application_default_credential_flow =
773            ApplicationDefaultCredentialsFlow::new(self.auth_flow);
774        Self::common_build(
775            self.hyper_client_builder,
776            self.storage_type,
777            AuthFlow::ApplicationDefaultCredentialsFlow(application_default_credential_flow),
778        )
779        .await
780    }
781}
782
783/// ## Methods available when building an authorized user flow Authenticator.
784impl<C> AuthenticatorBuilder<C, AuthorizedUserFlow> {
785    /// Create the authenticator.
786    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
787    where
788        C: HyperClientBuilder,
789    {
790        Self::common_build(
791            self.hyper_client_builder,
792            self.storage_type,
793            AuthFlow::AuthorizedUserFlow(self.auth_flow),
794        )
795        .await
796    }
797}
798
799/// ## Methods available when building an external account flow Authenticator.
800impl<C> AuthenticatorBuilder<C, ExternalAccountFlow> {
801    /// Create the authenticator.
802    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
803    where
804        C: HyperClientBuilder,
805    {
806        Self::common_build(
807            self.hyper_client_builder,
808            self.storage_type,
809            AuthFlow::ExternalAccountFlow(self.auth_flow),
810        )
811        .await
812    }
813}
814
815/// ## Methods available when building a service account impersonation Authenticator.
816impl<C> AuthenticatorBuilder<C, ServiceAccountImpersonationFlow> {
817    /// Create the authenticator.
818    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
819    where
820        C: HyperClientBuilder,
821    {
822        Self::common_build(
823            self.hyper_client_builder,
824            self.storage_type,
825            AuthFlow::ServiceAccountImpersonationFlow(self.auth_flow),
826        )
827        .await
828    }
829
830    /// Configure this authenticator to impersonate an ID token (rather an an access token,
831    /// as is the default).
832    ///
833    /// For more on impersonating ID tokens, see [google's docs](https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-oidc).
834    pub fn request_id_token(mut self) -> Self {
835        self.auth_flow.access_token = false;
836        self
837    }
838}
839
840/// ## Methods available when building an access token flow Authenticator.
841impl<C> AuthenticatorBuilder<C, AccessTokenFlow> {
842    /// Create the authenticator.
843    pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
844    where
845        C: HyperClientBuilder,
846    {
847        Self::common_build(
848            self.hyper_client_builder,
849            self.storage_type,
850            AuthFlow::AccessTokenFlow(self.auth_flow),
851        )
852        .await
853    }
854}
855mod private {
856    use crate::access_token::AccessTokenFlow;
857    use crate::application_default_credentials::ApplicationDefaultCredentialsFlow;
858    use crate::authorized_user::AuthorizedUserFlow;
859    use crate::client::SendRequest;
860    use crate::device::DeviceFlow;
861    use crate::error::Error;
862    use crate::external_account::ExternalAccountFlow;
863    use crate::installed::InstalledFlow;
864    #[cfg(feature = "service-account")]
865    use crate::service_account::ServiceAccountFlow;
866    use crate::service_account_impersonator::ServiceAccountImpersonationFlow;
867    use crate::types::{ApplicationSecret, TokenInfo};
868
869    #[allow(clippy::enum_variant_names)]
870    pub enum AuthFlow {
871        DeviceFlow(DeviceFlow),
872        InstalledFlow(InstalledFlow),
873        #[cfg(feature = "service-account")]
874        ServiceAccountFlow(ServiceAccountFlow),
875        ServiceAccountImpersonationFlow(ServiceAccountImpersonationFlow),
876        ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow),
877        AuthorizedUserFlow(AuthorizedUserFlow),
878        ExternalAccountFlow(ExternalAccountFlow),
879        AccessTokenFlow(AccessTokenFlow),
880    }
881
882    impl AuthFlow {
883        pub(crate) fn app_secret(&self) -> Option<&ApplicationSecret> {
884            match self {
885                AuthFlow::DeviceFlow(device_flow) => Some(&device_flow.app_secret),
886                AuthFlow::InstalledFlow(installed_flow) => Some(&installed_flow.app_secret),
887                #[cfg(feature = "service-account")]
888                AuthFlow::ServiceAccountFlow(_) => None,
889                AuthFlow::ServiceAccountImpersonationFlow(_) => None,
890                AuthFlow::ApplicationDefaultCredentialsFlow(_) => None,
891                AuthFlow::AuthorizedUserFlow(_) => None,
892                AuthFlow::ExternalAccountFlow(_) => None,
893                AuthFlow::AccessTokenFlow(_) => None,
894            }
895        }
896
897        pub(crate) async fn token<'a, T>(
898            &'a self,
899            hyper_client: &'a impl SendRequest,
900            scopes: &'a [T],
901        ) -> Result<TokenInfo, Error>
902        where
903            T: AsRef<str>,
904        {
905            match self {
906                AuthFlow::DeviceFlow(device_flow) => device_flow.token(hyper_client, scopes).await,
907                AuthFlow::InstalledFlow(installed_flow) => {
908                    installed_flow.token(hyper_client, scopes).await
909                }
910                #[cfg(feature = "service-account")]
911                AuthFlow::ServiceAccountFlow(service_account_flow) => {
912                    service_account_flow.token(hyper_client, scopes).await
913                }
914                AuthFlow::ServiceAccountImpersonationFlow(service_account_impersonation_flow) => {
915                    service_account_impersonation_flow
916                        .token(hyper_client, scopes)
917                        .await
918                }
919                AuthFlow::ApplicationDefaultCredentialsFlow(adc_flow) => {
920                    adc_flow.token(hyper_client, scopes).await
921                }
922                AuthFlow::AuthorizedUserFlow(authorized_user_flow) => {
923                    authorized_user_flow.token(hyper_client, scopes).await
924                }
925                AuthFlow::ExternalAccountFlow(external_account_flow) => {
926                    external_account_flow.token(hyper_client, scopes).await
927                }
928                AuthFlow::AccessTokenFlow(access_token_flow) => {
929                    access_token_flow.token(hyper_client, scopes).await
930                }
931            }
932        }
933    }
934}
935
936#[cfg(feature = "hyper-rustls")]
937#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
938/// Default authenticator type
939pub type DefaultAuthenticator =
940    Authenticator<hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>>;
941
942#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
943#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
944/// Default authenticator type
945pub type DefaultAuthenticator =
946    Authenticator<hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>>;
947
948/// How should the acquired tokens be stored?
949enum StorageType {
950    /// Store tokens in memory (and always log in again to acquire a new token on startup)
951    Memory,
952    /// 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.
953    Disk(PathBuf),
954    /// Implement your own storage provider
955    Custom(Box<dyn TokenStorage>),
956}
957
958#[cfg(test)]
959mod tests {
960    #[test]
961    #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
962    fn ensure_send_sync() {
963        use super::*;
964        fn is_send_sync<T: Send + Sync>() {}
965        is_send_sync::<Authenticator<<DefaultHyperClientBuilder as HyperClientBuilder>::Connector>>(
966        )
967    }
968}