dropbox_sdk/
oauth2.rs

1// Copyright (c) 2019-2025 Dropbox, Inc.
2
3//! Helpers for requesting OAuth2 tokens.
4//!
5//! OAuth2 has a few possible ways to authenticate, and the right choice depends on how your app
6//! operates and is deployed.
7//!
8//! For an overview, see the [Dropbox OAuth Guide].
9//!
10//! For quick recommendations based on the type of app you have, see the [OAuth types summary].
11//!
12//! [Dropbox OAuth Guide]: https://developers.dropbox.com/oauth-guide
13//! [OAuth types summary]: https://developers.dropbox.com/oauth-guide#summary
14
15use crate::async_client_trait::NoauthClient;
16use crate::client_helpers::{parse_response, prepare_request};
17use crate::client_trait_common::{Endpoint, ParamsType, Style};
18use crate::Error;
19use async_lock::RwLock;
20use base64::engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD};
21use base64::Engine;
22use ring::rand::{SecureRandom, SystemRandom};
23use std::env;
24use std::io::{self, IsTerminal, Write};
25use std::sync::Arc;
26use url::form_urlencoded::Serializer as UrlEncoder;
27use url::Url;
28
29/// Which type of OAuth2 flow to use.
30#[derive(Debug, Clone)]
31pub enum Oauth2Type {
32    /// The Authorization Code flow yields a temporary authorization code which must be turned into
33    /// an OAuth2 token by making another call. The authorization page can do a web redirect back to
34    /// your app with the code (if it is a server-side app), or can be used without a redirect URI,
35    /// in which case the authorization page displays the authorization code to the user and they
36    /// must then input the code manually into the program.
37    AuthorizationCode {
38        /// Client secret
39        client_secret: String,
40    },
41
42    /// The PKCE flow is an extension of the Authorization Code flow which uses dynamically
43    /// generated codes instead of an app secret to perform the OAuth exchange. This both avoids
44    /// having a hardcoded secret in the app (useful for client-side / mobile apps) and also ensures
45    /// that the authorization code can only be used by the client.
46    PKCE(PkceCode),
47
48    /// In Implicit Grant flow, the authorization page directly includes an OAuth2 token when it
49    /// redirects the user's web browser back to your program, and no separate call to generate a
50    /// token is needed. This can ONLY be used with a redirect URI.
51    ///
52    /// This flow is considered "legacy" and is not as secure as the other flows.
53    ImplicitGrant,
54}
55
56impl Oauth2Type {
57    /// The value to put in the "response_type" parameter to request the given token type.
58    pub(crate) fn response_type_str(&self) -> &'static str {
59        match self {
60            Oauth2Type::AuthorizationCode { .. } | Oauth2Type::PKCE { .. } => "code",
61            Oauth2Type::ImplicitGrant => "token",
62        }
63    }
64}
65
66/// What type of access token is requested? If unsure, ShortLivedAndRefresh is probably what you
67/// want.
68#[derive(Debug, Copy, Clone)]
69pub enum TokenType {
70    /// Return a short-lived bearer token and a long-lived refresh token that can be used to
71    /// generate new bearer tokens in the future (as long as a user's approval remains valid).
72    /// This is the default type for this SDK.
73    ShortLivedAndRefresh,
74
75    /// Return just the short-lived bearer token, without refresh token. The app will have to start
76    /// the authorization flow again to obtain a new token.
77    ShortLived,
78
79    /// Return a long-lived bearer token. The app must be allowed to do this in the Dropbox app
80    /// console. This capability will be removed in the future.
81    #[deprecated]
82    LongLived,
83}
84
85impl TokenType {
86    /// The value to put in the `token_access_type` parameter. If `None`, the parameter is omitted
87    /// entirely.
88    pub(crate) fn token_access_type_str(self) -> Option<&'static str> {
89        match self {
90            TokenType::ShortLivedAndRefresh => Some("offline"),
91            TokenType::ShortLived => Some("online"),
92            #[allow(deprecated)]
93            TokenType::LongLived => None,
94        }
95    }
96}
97
98/// A proof key for OAuth2 PKCE ("Proof Key for Code Exchange") flow.
99#[derive(Debug, Clone)]
100pub struct PkceCode {
101    /// The value of the code key.
102    pub code: String,
103}
104
105impl PkceCode {
106    /// Generate a new random code string.
107    #[allow(clippy::new_without_default)]
108    pub fn new() -> Self {
109        // Spec lets us use [a-zA-Z0-9._~-] as the alphabet, and a length between 43 and 128.
110        // A 93-byte input ends up as 125 base64 characters, so let's do that.
111        let mut bytes = [0u8; 93];
112        // not expecting this to ever actually fail:
113        SystemRandom::new()
114            .fill(&mut bytes)
115            .expect("failed to get random bytes for PKCE");
116        let code = URL_SAFE.encode(bytes);
117        Self { code }
118    }
119
120    /// Get the SHA-256 hash as a base64-encoded string.
121    pub fn s256(&self) -> String {
122        let digest = ring::digest::digest(&ring::digest::SHA256, self.code.as_bytes());
123        URL_SAFE_NO_PAD.encode(digest.as_ref())
124    }
125}
126
127/// Builds a URL that can be given to the user to visit to have Dropbox authorize your app.
128///
129/// If this app is a server-side app, you should redirect the user's browser to this URL to begin
130/// the authorization, and set the redirect_uri to bring the user back to your site when done.
131///
132/// If this app is a client-side app, you should open a web browser with this URL to begin the
133/// authorization, and set the redirect_uri to bring the user back to your app.
134///
135/// As a special case, if your app is a command-line application, you can skip setting the
136/// redirect_uri and print this URL and instruct the user to open it in a browser. When they
137/// complete the authorization, they will be given an auth code to input back into your app.
138///
139/// If you are using the deprecated Implicit Grant flow, the redirect after authentication will
140/// provide you an OAuth2 token. In all other cases, you will have an authorization code, and you
141/// must call make another call to obtain a token. See [`Authorization`], which is used to do this.
142#[derive(Debug)]
143pub struct AuthorizeUrlBuilder<'a> {
144    client_id: &'a str,
145    flow_type: &'a Oauth2Type,
146    token_type: TokenType,
147    force_reapprove: bool,
148    force_reauthentication: bool,
149    disable_signup: bool,
150    redirect_uri: Option<&'a str>,
151    state: Option<&'a str>,
152    require_role: Option<&'a str>,
153    locale: Option<&'a str>,
154    scope: Option<&'a str>,
155}
156
157impl<'a> AuthorizeUrlBuilder<'a> {
158    /// Return a new builder for the given client ID and auth flow type, with all fields set to
159    /// defaults.
160    pub fn new(client_id: &'a str, flow_type: &'a Oauth2Type) -> Self {
161        Self {
162            client_id,
163            flow_type,
164            token_type: TokenType::ShortLivedAndRefresh,
165            force_reapprove: false,
166            force_reauthentication: false,
167            disable_signup: false,
168            redirect_uri: None,
169            state: None,
170            require_role: None,
171            locale: None,
172            scope: None,
173        }
174    }
175
176    /// Set whether the user should be prompted to approve the request regardless of whether they
177    /// have approved it before.
178    pub fn force_reapprove(mut self, value: bool) -> Self {
179        self.force_reapprove = value;
180        self
181    }
182
183    /// Set whether the user should have to re-login when approving the request.
184    pub fn force_reauthentication(mut self, value: bool) -> Self {
185        self.force_reauthentication = value;
186        self
187    }
188
189    /// Set whether new user signups should be allowed or not while approving the request.
190    pub fn disable_signup(mut self, value: bool) -> Self {
191        self.disable_signup = value;
192        self
193    }
194
195    /// Set the URI the approve request should redirect the user to when completed.
196    /// If no redirect URI is specified, the user will be shown the code directly and will have to
197    /// manually input it into your app.
198    pub fn redirect_uri(mut self, value: &'a str) -> Self {
199        self.redirect_uri = Some(value);
200        self
201    }
202
203    /// Up to 500 bytes of arbitrary data that will be passed back to your redirect URI. This
204    /// parameter should be used to protect against cross-site request forgery (CSRF).
205    pub fn state(mut self, value: &'a str) -> Self {
206        self.state = Some(value);
207        self
208    }
209
210    /// If this parameter is specified, the user will be asked to authorize with a particular type
211    /// of Dropbox account, either `work` for a team account or `personal` for a personal account.
212    /// Your app should still verify the type of Dropbox account after authorization since the user
213    /// could modify or remove the require_role parameter.
214    pub fn require_role(mut self, value: &'a str) -> Self {
215        self.require_role = Some(value);
216        self
217    }
218
219    /// Force a specific locale when prompting the user, instead of the locale indicated by their
220    /// browser.
221    pub fn locale(mut self, value: &'a str) -> Self {
222        self.locale = Some(value);
223        self
224    }
225
226    /// What type of token should be requested. Defaults to [`TokenType::ShortLivedAndRefresh`].
227    pub fn token_type(mut self, value: TokenType) -> Self {
228        self.token_type = value;
229        self
230    }
231
232    /// This parameter allows your user to authorize a subset of the scopes selected in the
233    /// App Console. Multiple scopes are separated by a space. If this parameter is omitted, the
234    /// authorization page will request all scopes selected on the Permissions tab.
235    pub fn scope(mut self, value: &'a str) -> Self {
236        self.scope = Some(value);
237        self
238    }
239
240    /// Build the OAuth2 authorization URL from the previously given parameters.
241    pub fn build(self) -> Url {
242        let mut url = Url::parse("https://www.dropbox.com/oauth2/authorize").unwrap();
243        {
244            let mut params = url.query_pairs_mut();
245            params.append_pair("response_type", self.flow_type.response_type_str());
246            params.append_pair("client_id", self.client_id);
247            if let Some(val) = self.token_type.token_access_type_str() {
248                params.append_pair("token_access_type", val);
249            }
250            if self.force_reapprove {
251                params.append_pair("force_reapprove", "true");
252            }
253            if self.force_reauthentication {
254                params.append_pair("force_reauthentication", "true");
255            }
256            if self.disable_signup {
257                params.append_pair("disable_signup", "true");
258            }
259            if let Some(value) = self.redirect_uri {
260                params.append_pair("redirect_uri", value);
261            }
262            if let Some(value) = self.state {
263                params.append_pair("state", value);
264            }
265            if let Some(value) = self.require_role {
266                params.append_pair("require_role", value);
267            }
268            if let Some(value) = self.locale {
269                params.append_pair("locale", value);
270            }
271            if let Some(value) = self.scope {
272                params.append_pair("scope", value);
273            }
274            if let Oauth2Type::PKCE(code) = self.flow_type {
275                params.append_pair("code_challenge", &code.s256());
276                params.append_pair("code_challenge_method", "S256");
277            }
278        }
279        url
280    }
281}
282
283/// [`Authorization`] is a state-machine.
284///
285/// Every flow starts with the `InitialAuth` state, which is just after the user authorizes the app
286/// and gets redirected back. It then proceeds to either the `Refresh` or `AccessToken` state
287/// depending on whether a long-lived token was requested.
288///
289/// `Refresh` contains the refresh token necessary to obtain updated short-lived access tokens.
290///
291/// `AccessToken` contains just the access token itself, which is either a long-lived access token
292/// not expected to expire, or a short-lived token which, if it expires, cannot be refreshed except
293/// by starting the authorization flow over again.
294#[derive(Debug, Clone)]
295enum AuthorizationState {
296    InitialAuth {
297        flow_type: Oauth2Type,
298        auth_code: String,
299        redirect_uri: Option<String>,
300    },
301    Refresh {
302        refresh_token: String,
303        client_secret: Option<String>,
304    },
305    AccessToken {
306        client_secret: Option<String>,
307        token: String,
308    },
309}
310
311/// Provides for continuing authorization of the app.
312#[derive(Debug, Clone)]
313pub struct Authorization {
314    /// Dropbox app key
315    pub client_id: String,
316    state: AuthorizationState,
317}
318
319impl Authorization {
320    /// Get the client ID for this authorization.
321    pub fn client_id(&self) -> &str {
322        &self.client_id
323    }
324
325    /// Create a new instance using the authorization code provided upon redirect back to your app
326    /// (or via manual user entry if not using a redirect URI) after the user logs in.
327    ///
328    /// Requires the client ID; the type of OAuth2 flow being used (including the client secret or
329    /// the PKCE challenge); the authorization code; and the redirect URI used for the original
330    /// authorization request, if any.
331    pub fn from_auth_code(
332        client_id: String,
333        flow_type: Oauth2Type,
334        auth_code: String,
335        redirect_uri: Option<String>,
336    ) -> Self {
337        Self {
338            client_id,
339            state: AuthorizationState::InitialAuth {
340                flow_type,
341                auth_code,
342                redirect_uri,
343            },
344        }
345    }
346
347    /// Save the authorization state to a string which can be reloaded later.
348    ///
349    /// Returns `None` if the state cannot be saved (e.g. authorization has not completed getting a
350    /// token yet).
351    pub fn save(&self) -> Option<String> {
352        match &self.state {
353            AuthorizationState::AccessToken {
354                token,
355                client_secret,
356            } if client_secret.is_none() => {
357                // Legacy long-lived access token.
358                Some(format!("1&{}", token))
359            }
360            AuthorizationState::Refresh { refresh_token, .. } => {
361                Some(format!("2&{}", refresh_token))
362            }
363            _ => None,
364        }
365    }
366
367    /// Reload a saved authorization state produced by [`save`](Authorization::save).
368    ///
369    /// Returns `None` if the string could not be recognized. In this case, you should start the
370    /// authorization procedure from scratch.
371    ///
372    /// Note that a loaded authorization state is not necessarily still valid and may produce
373    /// [`Authentication`](crate::Error::Authentication) errors. In such a case you should also
374    /// start the authorization procedure from scratch.
375    pub fn load(client_id: String, saved: &str) -> Option<Self> {
376        Some(match saved.get(0..2) {
377            Some("1&") =>
378            {
379                #[allow(deprecated)]
380                Self::from_long_lived_access_token(saved[2..].to_owned())
381            }
382            Some("2&") => Self::from_refresh_token(client_id, saved[2..].to_owned()),
383            _ => {
384                error!("unrecognized saved Authorization representation: {saved:?}");
385                return None;
386            }
387        })
388    }
389
390    /// Recreate the authorization from a refresh token obtained using the [`Oauth2Type::PKCE`]
391    /// flow.
392    pub fn from_refresh_token(client_id: String, refresh_token: String) -> Self {
393        Self {
394            client_id,
395            state: AuthorizationState::Refresh {
396                refresh_token,
397                client_secret: None,
398            },
399        }
400    }
401
402    /// Recreate the authorization from a refresh token obtained using the
403    /// [`Oauth2Type::AuthorizationCode`] flow. This requires the client secret as well.
404    pub fn from_client_secret_refresh_token(
405        client_id: String,
406        client_secret: String,
407        refresh_token: String,
408    ) -> Self {
409        Self {
410            client_id,
411            state: AuthorizationState::Refresh {
412                refresh_token,
413                client_secret: Some(client_secret),
414            },
415        }
416    }
417
418    /// Recreate the authorization from a long-lived access token. This token cannot be refreshed;
419    /// any call to [`obtain_access_token_async`](Authorization::obtain_access_token_async) will
420    /// simply return the given token. Therefore this requires neither client ID or client secret.
421    ///
422    /// Long-lived tokens are deprecated and the ability to generate them will be removed in the
423    /// future.
424    #[deprecated]
425    pub fn from_long_lived_access_token(access_token: String) -> Self {
426        Self {
427            client_id: String::new(),
428            state: AuthorizationState::AccessToken {
429                token: access_token,
430                client_secret: None,
431            },
432        }
433    }
434
435    if_feature! { "sync_routes",
436        /// Compatibility shim for working with sync HTTP clients.
437        pub fn obtain_access_token(
438            &mut self,
439            sync_client: impl crate::client_trait::NoauthClient
440        ) -> Result<String, Error> {
441            use futures::FutureExt;
442            self.obtain_access_token_async(sync_client)
443                .now_or_never()
444                .expect("sync client future should resolve immediately")
445        }
446    }
447
448    /// Obtain an access token. Use this to complete the authorization process, or to obtain an
449    /// updated token when a short-lived access token has expired.
450    pub async fn obtain_access_token_async(
451        &mut self,
452        client: impl NoauthClient,
453    ) -> Result<String, Error> {
454        let mut redirect_uri = None;
455        let mut client_secret = None;
456        let mut pkce_code = None;
457        let mut refresh_token = None;
458        let mut auth_code = None;
459
460        match self.state.clone() {
461            AuthorizationState::AccessToken {
462                token,
463                client_secret: secret,
464            } => {
465                match secret {
466                    None => {
467                        // Long-lived token which cannot be refreshed
468                        return Ok(token);
469                    }
470                    Some(secret) => {
471                        client_secret = Some(secret);
472                    }
473                }
474            }
475            AuthorizationState::InitialAuth {
476                flow_type,
477                auth_code: code,
478                redirect_uri: uri,
479            } => {
480                match flow_type {
481                    Oauth2Type::ImplicitGrant => {
482                        self.state = AuthorizationState::AccessToken {
483                            client_secret: None,
484                            token: code.clone(),
485                        };
486                        return Ok(code);
487                    }
488                    Oauth2Type::AuthorizationCode {
489                        client_secret: secret,
490                    } => {
491                        client_secret = Some(secret);
492                    }
493                    Oauth2Type::PKCE(pkce) => {
494                        pkce_code = Some(pkce.code.clone());
495                    }
496                }
497                auth_code = Some(code);
498                redirect_uri = uri;
499            }
500            AuthorizationState::Refresh {
501                refresh_token: refresh,
502                client_secret: secret,
503            } => {
504                refresh_token = Some(refresh);
505                if let Some(secret) = secret {
506                    client_secret = Some(secret);
507                }
508            }
509        }
510
511        let params = {
512            let mut params = UrlEncoder::new(String::new());
513
514            if let Some(refresh) = &refresh_token {
515                params.append_pair("grant_type", "refresh_token");
516                params.append_pair("refresh_token", refresh);
517            } else {
518                params.append_pair("grant_type", "authorization_code");
519                params.append_pair("code", &auth_code.unwrap());
520            }
521
522            params.append_pair("client_id", &self.client_id);
523
524            if let Some(client_secret) = client_secret.as_deref() {
525                params.append_pair("client_secret", client_secret);
526            }
527
528            if let Some(pkce) = &pkce_code {
529                params.append_pair("code_verifier", pkce);
530            }
531
532            if refresh_token.is_none() {
533                if let Some(pkce) = pkce_code {
534                    params.append_pair("code_verifier", &pkce);
535                } else {
536                    params.append_pair(
537                        "client_secret",
538                        client_secret
539                            .as_ref()
540                            .expect("need either PKCE code or client secret"),
541                    );
542                }
543            }
544
545            if let Some(value) = redirect_uri {
546                params.append_pair("redirect_uri", &value);
547            }
548
549            params.finish()
550        };
551
552        let (req, body) = prepare_request(
553            &client,
554            Endpoint::OAuth2,
555            Style::Rpc,
556            "oauth2/token",
557            params,
558            ParamsType::Form,
559            None,
560            None,
561            None,
562            None,
563            None,
564        );
565        let body = body.unwrap_or_default();
566
567        debug!("Requesting OAuth2 token");
568        let resp = client.execute(req, body).await?;
569        let (result_json, _, _) = parse_response(resp, Style::Rpc).await?;
570        let result_value = serde_json::from_str(&result_json)?;
571
572        debug!("OAuth2 response: {:?}", result_value);
573
574        let access_token: String;
575        let refresh_token: Option<String>;
576
577        match result_value {
578            serde_json::Value::Object(mut map) => {
579                match map.remove("access_token") {
580                    Some(serde_json::Value::String(token)) => access_token = token,
581                    _ => {
582                        return Err(Error::UnexpectedResponse(
583                            "no access token in response!".to_owned(),
584                        ))
585                    }
586                }
587                match map.remove("refresh_token") {
588                    Some(serde_json::Value::String(refresh)) => refresh_token = Some(refresh),
589                    Some(_) => {
590                        return Err(Error::UnexpectedResponse(
591                            "refresh token is not a string!".to_owned(),
592                        ));
593                    }
594                    None => refresh_token = None,
595                }
596            }
597            _ => {
598                return Err(Error::UnexpectedResponse(
599                    "response is not a JSON object".to_owned(),
600                ))
601            }
602        }
603
604        match refresh_token {
605            Some(refresh) => {
606                self.state = AuthorizationState::Refresh {
607                    refresh_token: refresh,
608                    client_secret,
609                };
610            }
611            None if !matches!(self.state, AuthorizationState::Refresh { .. }) => {
612                self.state = AuthorizationState::AccessToken {
613                    token: access_token.clone(),
614                    client_secret,
615                };
616            }
617            _ => (),
618        }
619
620        Ok(access_token)
621    }
622}
623
624/// `TokenCache` provides the current OAuth2 token and a means to refresh it in a thread-safe way.
625pub struct TokenCache {
626    auth: RwLock<(Authorization, Arc<String>)>,
627}
628
629impl TokenCache {
630    /// Make a new token cache, using the given [`Authorization`] as a source of tokens.
631    pub fn new(auth: Authorization) -> Self {
632        Self {
633            auth: RwLock::new((auth, Arc::new(String::new()))),
634        }
635    }
636
637    /// Get the current token, unless no cached token is set yet.
638    pub fn get_token(&self) -> Option<Arc<String>> {
639        let read = self.auth.read_blocking();
640        if read.1.is_empty() {
641            None
642        } else {
643            Some(Arc::clone(&read.1))
644        }
645    }
646
647    /// Forces an update to the token, for when it is detected that the token is expired.
648    ///
649    /// To avoid double-updating the token in a race, requires the token which is being replaced.
650    /// For the case where no token is currently present, use the empty string as the token.
651    pub async fn update_token(
652        &self,
653        client: impl NoauthClient,
654        old_token: Arc<String>,
655    ) -> Result<Arc<String>, Error> {
656        let mut write = self.auth.write().await;
657        // Check if the token changed while we were unlocked; only update it if it
658        // didn't.
659        if write.1 == old_token {
660            write.1 = Arc::new(write.0.obtain_access_token_async(client).await?);
661        }
662        Ok(Arc::clone(&write.1))
663    }
664
665    /// Set the current short-lived token to a specific provided value. Normally it should not be
666    /// necessary to call this function; the token should be obtained automatically using the
667    /// refresh token.
668    pub fn set_access_token(&self, access_token: String) {
669        let mut write = self.auth.write_blocking();
670        write.1 = Arc::new(access_token);
671    }
672}
673
674/// Get an [`Authorization`] instance from environment variables `DBX_CLIENT_ID` and `DBX_OAUTH`
675/// (containing a refresh token) or `DBX_OAUTH_TOKEN` (containing a legacy long-lived token).
676///
677/// If environment variables are not set, and stdin is a terminal, prompt interactively for
678/// authorization.
679///
680/// If environment variables are not set, and stdin is not a terminal, panics.
681///
682/// This is a helper function intended only for tests and example code. Use in production code is
683/// strongly discouraged; you should write something more customized to your needs instead.
684///
685/// In particular, in real production code, you probably don't want to use environment variables.
686/// The client ID should be a hard-coded constant, or specified in configuration somewhere. It is
687/// not something that will change often, or maybe ever.
688/// The refresh token should only be stored somewhere safe like a file or database with restricted
689/// access permissions.
690pub fn get_auth_from_env_or_prompt() -> Authorization {
691    if let Ok(long_lived) = env::var("DBX_OAUTH_TOKEN") {
692        // Used to provide a legacy long-lived token.
693        #[allow(deprecated)]
694        return Authorization::from_long_lived_access_token(long_lived);
695    }
696
697    if let (Ok(client_id), Ok(saved)) = (env::var("DBX_CLIENT_ID"), env::var("DBX_OAUTH"))
698    // important! see the above warning about using environment variables for this
699    {
700        match Authorization::load(client_id, &saved) {
701            Some(auth) => return auth,
702            None => {
703                eprintln!("saved authorization in DBX_CLIENT_ID and DBX_OAUTH are invalid");
704                // and fall back to prompting
705            }
706        }
707    }
708
709    if !io::stdin().is_terminal() {
710        panic!("DBX_CLIENT_ID and/or DBX_OAUTH not set, and stdin not a TTY; cannot authorize");
711    }
712
713    fn prompt(msg: &str) -> String {
714        eprint!("{}: ", msg);
715        io::stderr().flush().unwrap();
716        let mut input = String::new();
717        io::stdin().read_line(&mut input).unwrap();
718        input.trim().to_owned()
719    }
720
721    let client_id = prompt("Give me a Dropbox API app key");
722
723    let oauth2_flow = Oauth2Type::PKCE(PkceCode::new());
724    let url = AuthorizeUrlBuilder::new(&client_id, &oauth2_flow).build();
725    eprintln!("Open this URL in your browser:");
726    eprintln!("{}", url);
727    eprintln!();
728    let auth_code = prompt("Then paste the code here");
729
730    Authorization::from_auth_code(client_id, oauth2_flow, auth_code.trim().to_owned(), None)
731}