egg-mode 0.15.0

Library to interact with the Twitter API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Types and methods used to authenticate calls to Twitter.
//!
//! # Authenticating Requests With Twitter
//!
//! egg-mode uses a [`Token`] type to represent successfully authenticating with Twitter. The
//! process of obtaining a `Token` is somewhat complicated, and there are a few distinct routes you
//! can take to get one. Which route you take depends on whether you want to only access public
//! data versus wanting to act on behalf of a specific user, and whether you can open a web browser
//! and/or redirect web requests to and from Twitter. Twitter's [Authentication Overview][auth] has
//! the complete information.
//!
//! [`Token`]: enum.Token.html
//! [auth]: https://developer.twitter.com/en/docs/basics/authentication/overview
//!
//! Regardless of which route you take, you need to [register] with Twitter's Developer site and
//! [set up an app][apps] to get a set of keys that represent your code interacting with the
//! Twitter service. These are called the "consumer key" and "consumer secret", and you can store
//! them in a [`KeyPair`] to start the authentication process with egg-mode. At an HTTP request
//! level, these keys are sent with every API call regardless of how you choose to authenticate.
//! (There are other keys given when you register an app, but those will be mentioned below when
//! talking about Access tokens.)
//!
//! [register]: https://developer.twitter.com/en/apply-for-access
//! [apps]: https://developer.twitter.com/en/apps
//! [`KeyPair`]: struct.KeyPair.html
//!
//! There are two kinds of `Tokens` used within egg-mode, representing the two major ways to
//! interact with the Twitter API: Bearer tokens, for accessing public information on Twitter from
//! the point of view of your app itself, and Access tokens, for performing actions or requesting
//! data on behalf of a specific user.
//!
//! ## Bearer Tokens
//!
//! The simplest kind of `Token` you can get is called a "Bearer token".  Bearer tokens are for
//! when you want to perform requests on behalf of your app itself, instead of a specific user.
//! Bearer tokens are the API equivalent of viewing Twitter from a logged-out session. Anything
//! that's already public can be viewed, but things like protected users or the home timeline can't
//! be accessed with bearer tokens. On the other hand, because you don't need to authenticate a
//! user, obtaining a bearer token is relatively simple.
//!
//! If a Bearer token will work for your purposes, use the following steps to get a Token:
//!
//! 1. With the consumer key and secret from Twitter, ask Twitter for the current [Bearer token]
//!    for your application.
//!
//! [Bearer token]: fn.bearer_token.html
//!
//! And... that's it! This Bearer token can be cached and saved for future use. It will not expire
//! until you ask Twitter to [invalidate] the token for you. Otherwise, this token can be used the
//! same way as access tokens below, but with the restrictions mentioned earlier.
//!
//! [invalidate]: fn.invalidate_bearer.html
//!
//! ### Example (Bearer Token)
//!
//! ```rust,no_run
//! # #[tokio::main]
//! # async fn main() {
//! let con_token = egg_mode::KeyPair::new("consumer key", "consumer secret");
//! let token = egg_mode::auth::bearer_token(&con_token).await.unwrap();
//!
//! // token can be given to *most* egg_mode methods that ask for a token
//! // for restrictions, see docs for bearer_token
//! # }
//! ```
//!
//! ## Access Tokens
//!
//! Access tokens are for when you want to perform your requests on behalf of a specific user. This
//! could be for something like posting to their account, sending and receiving direct messages for
//! them, viewing protected accounts they follow, and other actions that only make sense from the
//! perspective from a specific user. Because of the two-fold nature of making sure your requests
//! are signed from your specific *app* and from that specific *user*, the authentication process
//! for access tokens is relatively complicated.
//!
//! The process to get an access token for a specific user (with this library) has three basic
//! steps:
//!
//! 1. Log your request with Twitter by getting a [request token][].
//! 2. Direct the user to grant permission to your application by sending them to an
//!    [authenticate][] or [authorize][] URL, depending on the nature of your app.
//! 3. Convert the verifier given by the permission request into an [access token][].
//!
//! [request token]: fn.request_token.html
//! [authorize]: fn.authorize_url.html
//! [authenticate]: fn.authenticate_url.html
//! [access token]: fn.access_token.html
//!
//! Before you get too deep into the authentication process, it helps to know a couple things about
//! the app you're writing:
//!
//! * Is your app in an environment where directing users to and from a web page is easy? (e.g. a
//!   website, or a mobile app)
//! * Are you using Twitter authentication as a substitute for user accounts in your own
//!   application, instead of wanting to interact with their Twitter account?
//!
//! Depending on your answer to the first question, you may need to use "PIN-Based Authorization",
//! where the user completes the authentication/authorization process in a separate window and
//! receives a numeric PIN in response that your app can use to complete the authentication
//! process. The alternative to that is the standard OAuth flow, where a web browser is directed to
//! Twitter to complete the login and authorization, then redirected back to your app to receive
//! the access token proper. The way to signal one method or another is by the `callback` parameter
//! to the [access token] request.
//!
//! The second question informs *where* you send the user to authorize your app. Using the "Sign In
//! With Twitter" flow, your app could be able to transparently request another access token
//! without the user needing to accept the connection every time. This is ideal for websites where
//! a "Sign In With Twitter" button could replace a regular login button, using a user's Twitter
//! account in place for regular username/password credentials. To be able to use the "Sign In With
//! Twitter" flow, you must first enable it for your app on Twitter's Application Manager.  Then,
//! for Step 2 of the authentication process, send the user to an [authenticate] URL. If you don't
//! need or want to use the "Sign In With Twitter" process, send the user to an [authorize] URL
//! instead.
//!
//! The primary difference between the different URLs for Step 2 is that an [authenticate] URL
//! allows the above behavior, whereas an [authorize] URL does not require the extra setting in the
//! app manager and always requires the user to re-authorize the app every time they're sent
//! through the authentication process. Since access tokens can be cached and reused indefinitely
//! until the app's access is revoked, you only really need to send the user through the
//! authentication process once.
//!
//! The end result of Step 2 is that your app receives a "verifier" to vouch for the user's
//! acceptance of your app. With PIN-Based Authorization, the user receives a PIN from Twitter that
//! acts as the verifier. With "Sign In With Twitter" and its counterpart, "3-Legged
//! Authorization", the verifier is given as a query parameter to the callback URL given back in
//! Step 1. With this verifier and the original request token, you can combine them with your app's
//! consumer token to get the [access token] that opens up the rest of the Twitter API.
//!
//! ### Example (Access Token)
//!
//! For "PIN-Based Authorization":
//!
//! ```rust,no_run
//! # #[tokio::main]
//! # async fn main() {
//! let con_token = egg_mode::KeyPair::new("consumer key", "consumer secret");
//! // "oob" is needed for PIN-based auth; see docs for `request_token` for more info
//! let request_token = egg_mode::auth::request_token(&con_token, "oob").await.unwrap();
//! let auth_url = egg_mode::auth::authorize_url(&request_token);
//!
//! // give auth_url to the user, they can sign in to Twitter and accept your app's permissions.
//! // they'll receive a PIN in return, they need to give this to your application
//!
//! let verifier = "123456"; //read the PIN from the user here
//!
//! // note this consumes con_token; if you want to sign in multiple accounts, clone it here
//! let (token, user_id, screen_name) =
//!     egg_mode::auth::access_token(con_token, &request_token, verifier).await.unwrap();
//!
//! // token can be given to any egg_mode method that asks for a token
//! // user_id and screen_name refer to the user who signed in
//! # }
//! ```
//!
//! **WARNING**: The consumer token and preset access token mentioned below are as privileged as
//! passwords! If your consumer key pair leaks or is visible to the public, anyone can impersonate
//! your app! If you use a fixed token for your app, it's recommended to set them in separate files
//! and use `include_str!()` (from the standard library), or save them in an environment file and
//! use a library like `dotenv` to load them in, so you can safely exclude them from source
//! control.
//!
//! ### Shortcut: Pre-Generated Access Token
//!
//! If you only want to sign in as yourself, there's a shortcut you can use to get an Access token.
//! When you sign up for an app and get your consumer token, a second key/secret pair are given to
//! you. This "access token" and "access token secret" act as authorization to access your own
//! account with your own code, and can be used to directly construct an egg-mode `Token`:
//!
//! ```rust
//! let con_token = egg_mode::KeyPair::new("consumer key", "consumer secret");
//! let access_token = egg_mode::KeyPair::new("access token key", "access token secret");
//! let token = egg_mode::Token::Access {
//!     consumer: con_token,
//!     access: access_token,
//! };
//!
//! // token can be given to any egg_mode method that asks for a token
//! ```
//!
//! For more information on the individual steps of the authentication process, see the
//! documentation for the functions in this module.

use std::borrow::Cow;

use hyper::Method;
use serde::{Serialize, Deserialize};
use serde_json;

use crate::common::*;
use crate::{
    error::{self, Result},
    links,
};

pub(crate) mod raw;

use raw::RequestBuilder;

/// A key/secret pair representing the app that is sending a request or an authorization from a user.
///
/// This type is used as part of the authentication process and to sign API requests afterward. For
/// the most part it's used internally as part of a [`Token`], but at the very beginning of the
/// authentication process, you'll need to manually create one to hold onto your "consumer token"
/// and request a [request token].
///
/// [`Token`]: enum.Token.html
/// [request token]: fn.request_token.html
///
/// For more information, see the [authentication documentation][auth].
///
/// [auth]: index.html
///
/// # Example
///
/// ```rust
/// let con_token = egg_mode::KeyPair::new("consumer key", "consumer token");
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyPair {
    ///A key used to identify an application or user.
    pub key: Cow<'static, str>,
    ///A private key used to sign messages from an application or user.
    pub secret: Cow<'static, str>,
}

impl KeyPair {
    /// Creates a KeyPair with the given key and secret.
    ///
    /// This can be called with either `&'static str` (a string literal) or `String` for either
    /// parameter.
    pub fn new<K, S>(key: K, secret: S) -> KeyPair
    where
        K: Into<Cow<'static, str>>,
        S: Into<Cow<'static, str>>,
    {
        KeyPair {
            key: key.into(),
            secret: secret.into(),
        }
    }

    /// Internal function to create an empty KeyPair. Not meant to be used from user code.
    fn empty() -> KeyPair {
        KeyPair {
            key: "".into(),
            secret: "".into(),
        }
    }
}

/// A token that can be used to sign requests to Twitter.
///
/// Conceptually, a Token represents your authorization to call the Twitter API. It can either be a
/// [Bearer token], representing a "logged-out" view of Twitter coming from your app itself; or an
/// [Access token], representing a combination of your app's "consumer" key with a specific user
/// granting access for your app to use the Twitter API on their behalf. For more information, see
/// the [authentication documentation][auth].
///
/// [Bearer token]: index.html#bearer-tokens
/// [Access token]: index.html#access-tokens
/// [auth]: index.html
///
/// Once you have obtained a Token of either kind, the keys within may be saved and reused in the
/// future, as long as the access has not been revoked. **Note** that the keys saved in this type
/// work just like a password, and they should be handled with care when you save them! If you
/// believe your keys have been compromised, you can generate a new consumer token in [Twitter's
/// Apps Dashboard][apps], and if you've been using a Bearer token, you should [invalidate] it and
/// generate a new one.
///
/// [apps]: https://developer.twitter.com/en/apps
/// [invalidate]: fn.invalidate_bearer.html
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Token {
    /// An OAuth Access token indicating the request is coming from a specific user.
    Access {
        /// A "consumer" key/secret that represents the application sending the request.
        consumer: KeyPair,
        /// An "access" key/secret that represents the user's authorization of the application.
        access: KeyPair,
    },
    /// An OAuth Bearer token indicating the request is coming from the application itself, not a
    /// particular user.
    Bearer(String),
}

/// With the given consumer KeyPair, ask Twitter for a request KeyPair that can be used to request
/// access to the user's account.
///
/// # Access Token Authentication
///
/// [Authentication overview](index.html)
///
/// 1. **Request Token**: Authenticate your application
/// 2. [Authorize]/[Authenticate]: Authenticate the user
/// 3. [Access Token]: Confirm the authentication with Twitter
///
/// [Authorize]: fn.authorize_url.html
/// [Authenticate]: fn.authenticate_url.html
/// [Access Token]: fn.access_token.html
///
/// # Request Token: Authenticate your application
///
/// To begin the authentication process, first log your request with Twitter by authenticating your
/// application by itself. This "request token" is used in later steps to match all the requests to
/// the same authentication attempt.
///
/// The parameter `callback` is used differently based on how your program is set up, and which
/// authentication process you'd like to use. For applications where directing users to and from
/// another web page is difficult, you can use the special value `"oob"` to indicate that you would
/// like to use PIN-Based Authentication.
///
/// Web-based applications and those that can handle web redirects transparently can instead supply
/// a callback URL for that parameter. When the user completes the sign-in and authentication
/// process, they will be directed to the provided URL with the information necessary to complete
/// the authentication process. Depending on which Step 2 URL you use and whether you've enabled it
/// for your app, this is called "Sign In With Twitter" or "3-Legged Authorization".
///
/// With this Request Token, you can assemble an [Authorize] or [Authenticate] URL that will allow
/// the user to log in with Twitter and allow your app access to their account. See the
/// Authentication Overview for more details, but the short version is that you want to use
/// [Authenticate] for "Sign In With Twitter" functionality, and [Authorize] if not.
///
/// # Examples
///
/// ```rust,no_run
/// # #[tokio::main]
/// # async fn main() {
/// let con_token = egg_mode::KeyPair::new("consumer key", "consumer token");
/// // for PIN-Based Auth
/// let req_token = egg_mode::auth::request_token(&con_token, "oob").await.unwrap();
/// // for Sign In With Twitter/3-Legged Auth
/// let req_token = egg_mode::auth::request_token(&con_token, "https://myapp.io/auth")
///     .await
///     .unwrap();
/// # }
/// ```
pub async fn request_token<S: Into<String>>(con_token: &KeyPair, callback: S) -> Result<KeyPair> {
    let request = RequestBuilder::new(Method::POST, links::auth::REQUEST_TOKEN)
        .oauth_callback(callback.into())
        .request_keys(con_token, None);

    let (_, body) = raw_request(request).await?;

    let body = std::str::from_utf8(&body).map_err(|_| {
        std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "stream did not contain valid UTF-8",
        )
    })?;
    let mut key: Option<String> = None;
    let mut secret: Option<String> = None;

    for elem in body.split('&') {
        let mut kv = elem.splitn(2, '=');
        match kv.next() {
            Some("oauth_token") => key = kv.next().map(|s| s.to_string()),
            Some("oauth_token_secret") => secret = kv.next().map(|s| s.to_string()),
            Some(_) => (),
            None => {
                return Err(error::Error::InvalidResponse(
                    "unexpected end of request_token response",
                    None,
                ))
            }
        }
    }

    Ok(KeyPair::new(
        key.ok_or(error::Error::MissingValue("oauth_token"))?,
        secret.ok_or(error::Error::MissingValue("oauth_token_secret"))?,
    ))
}

/// With the given request KeyPair, return a URL that a user can access to accept or reject an
/// authorization request.
///
/// # Access Token Authentication
///
/// [Authentication overview](index.html)
///
/// 1. [Request Token]: Authenticate your application
/// 2. **Authorize**/[Authenticate]: Authenticate the user
/// 3. [Access Token]: Confirm the authentication with Twitter
///
/// [Request Token]: fn.request_token.html
/// [Authenticate]: fn.authenticate_url.html
/// [Access Token]: fn.access_token.html
///
/// # Authorize: Authenticate the user
///
/// This function is part of the step of authenticating a user with Twitter so they can authorize
/// your application to access their account. This function generates a URL with the given request
/// token that you must give to the user. What happens with this URL depends on what you used as
/// the `callback` parameter for `request_token`.
///
/// If you gave a callback URL to `request_token`, Twitter will redirect the user to that URL after
/// they log in and accept your app's permissions. There will be two query string parameters added
/// to the URL for this redirect: `oauth_token`, which contains the `key` from the [request token]
/// used here, and `oauth_verifier`, which contains a verifier string that can be used to create
/// the final [access token]. Note that if this URL is used instead of [Authenticate], the user
/// will need to accept the app's connection each time, even if they have connected the app
/// previously and have not revoked the app's permissions. This process is called [3-legged
/// authorization]. If you would like the user to transparently be redirected without confirmation
/// if they've already accepted the connection, see the docs for [Authenticate] to read about "Sign
/// In With Twitter".
///
/// [3-legged authorization]: https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/obtaining-user-access-tokens
///
/// If you gave the special value `"oob"` to `request_token`, this URL can be directly shown to the
/// user, who can enter it into a separate web browser to complete the authorization. This is
/// called [PIN-based authorization] and it's required for applications that cannot be reached by
/// redirecting a URL from a web browser. When the user loads this URL, they can sign in with
/// Twitter and grant your app access to their account. If they grant this access, they are given a
/// numeric PIN that your app can use as the "verifier" to create the final [access token].
///
/// [Pin-Based authorization]: https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/pin-based-oauth
pub fn authorize_url(request_token: &KeyPair) -> String {
    format!(
        "{}?oauth_token={}",
        links::auth::AUTHORIZE,
        request_token.key
    )
}

/// With the given request KeyPair, return a URL to redirect a user to so they can accept or reject
/// an authorization request.
///
/// # Access Token Authentication
///
/// [Authentication overview](index.html)
///
/// 1. [Request Token]: Authenticate your application
/// 2. [Authorize]/ **Authenticate**: Authenticate the user
/// 3. [Access Token]: Confirm the authentication with Twitter
///
/// [Request Token]: fn.request_token.html
/// [Authorize]: fn.authorize_url.html
/// [Access Token]: fn.access_token.html
///
/// # Authenticate: Authenticate the user (with Sign In To Twitter)
///
/// This function is part of the step of authenticating a user with Twitter so they can authorize
/// your application to access their account. This function generates a URL with the given request
/// token that you must give to the user.
///
/// The URL returned by this function acts the same as the [Authorize] URL, with one exception: If
/// you have "[Sign In With Twitter]" enabled for your app, the user does not need to re-accept the
/// app's connection if they've accepted it previously. If they're already logged in to Twitter,
/// and have already accepted your app's access, they won't even see the redirect through Twitter.
/// Twitter will immediately redirect the user to the `callback` URL given to the [request token].
///
/// [Sign In With Twitter]: https://developer.twitter.com/en/docs/basics/authentication/guides/log-in-with-twitter
///
/// If the user is redirected to a callback URL, Twitter will add two query string parameters:
/// `oauth_token`, which contains the `key` from the [request token] used here, and
/// `oauth_verifier`, which contains a verifier string that can be used to create the final [access
/// token].
pub fn authenticate_url(request_token: &KeyPair) -> String {
    format!(
        "{}?oauth_token={}",
        links::auth::AUTHENTICATE,
        request_token.key
    )
}

/// With the given OAuth tokens and verifier, ask Twitter for an access KeyPair that can be used to
/// sign further requests to the Twitter API.
///
/// # Access Token Authentication
///
/// [Authentication overview](index.html)
///
/// 1. [Request Token]: Authenticate your application
/// 2. [Authorize]/[Authenticate]: Authenticate the user
/// 3. **Access Token**: Confirm the authentication with Twitter
///
/// [Request Token]: fn.request_token.html
/// [Authorize]: fn.authorize_url.html
/// [Authenticate]: fn.authenticate_url.html
///
/// # Access Token: Confirm the authentication with Twitter
///
/// This is the final step in authenticating a user account to use your app. With this method, you
/// combine the consumer `KeyPair` that represents your app, the [request token] that represents
/// the session, and the "verifier" that represents the user's credentials and their acceptance of
/// your app's access.
///
/// The `verifier` parameter comes from the Step 2 process you used. For PIN-Based Authorization,
/// the verifier is the PIN returned to the user after they sign in. For "Sign In With Twitter" and
/// 3-Legged Authorization, the verifier is the string passed by twitter to your app through the
/// `oauth_verifier` query string parameter. For more information, see the documentation for the
/// [Authorize] URL function.
///
/// Note that this function consumes `con_token`, because it is inserted into the `Token` that is
/// returned. If you would like to use the consumer token to authenticate multiple accounts in the
/// same session, clone the `KeyPair` when passing it into this function.
///
/// The `Future` returned by this function, on success, yields a tuple of three items: The
/// final access token, the ID of the authenticated user, and the screen name of the authenticated
/// user.
pub async fn access_token<S: Into<String>>(
    con_token: KeyPair,
    request_token: &KeyPair,
    verifier: S,
) -> Result<(Token, u64, String)> {
    let request = RequestBuilder::new(Method::POST, links::auth::ACCESS_TOKEN)
        .oauth_verifier(verifier.into())
        .request_keys(&con_token, Some(request_token));

    let (_headers, urlencoded) = raw_request(request).await?;
    let urlencoded = std::str::from_utf8(&urlencoded).map_err(|_| {
        std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "stream did not contain valid UTF-8",
        )
    })?;

    // TODO deserialize into a struct
    let mut key: Option<String> = None;
    let mut secret: Option<String> = None;
    let mut id: Option<u64> = None;
    let mut username: Option<String> = None;

    for elem in urlencoded.split('&') {
        let mut kv = elem.splitn(2, '=');
        match kv.next() {
            Some("oauth_token") => key = kv.next().map(|s| s.to_string()),
            Some("oauth_token_secret") => secret = kv.next().map(|s| s.to_string()),
            Some("user_id") => id = kv.next().and_then(|s| u64::from_str_radix(s, 10).ok()),
            Some("screen_name") => username = kv.next().map(|s| s.to_string()),
            Some(_) => (),
            None => {
                return Err(error::Error::InvalidResponse(
                    "unexpected end of response in access_token",
                    None,
                ))
            }
        }
    }

    let access_key = key.ok_or(error::Error::MissingValue("oauth_token"))?;
    let access_secret = secret.ok_or(error::Error::MissingValue("oauth_token_secret"))?;

    Ok((
        Token::Access {
            consumer: con_token,
            access: KeyPair::new(access_key, access_secret),
        },
        id.ok_or(error::Error::MissingValue("user_id"))?,
        username.ok_or(error::Error::MissingValue("screen_name"))?,
    ))
}

/// With the given consumer KeyPair, request the current Bearer token to perform Application-only
/// authentication.
///
/// If you don't need to use the Twitter API to perform actions on or with specific users, app-only
/// auth provides a much easier way to authenticate with the Twitter API. The Token given by this
/// function can be used to authenticate requests as if there were coming from your app itself.
/// This comes with an important restriction, though: any request that requires a user context -
/// direct messages, viewing protected user profiles, functions like `tweet::home_timeline` that
/// operate in terms of the authenticated user - will not work with just a Bearer token. Attempts
/// to perform those actions will return an authentication error.
///
/// Other things to note about Bearer tokens:
///
/// - Bearer tokens have a higher rate limit for the methods they can be used on, compared to
///   regular Access tokens.
/// - The bearer token returned by Twitter is the same token each time you call it. It can be
///   cached and reused as long as you need it.
/// - Since a Bearer token can be used to directly authenticate calls to Twitter, it should be
///   treated with the same sensitivity as a password. If you believe your Bearer token to be
///   compromised, call [`invalidate_bearer`] with your consumer KeyPair and the Bearer token you
///   need to invalidate.  This will cause Twitter to generate a new Bearer token for your
///   application, which will be returned the next time you call this function.
///
/// [`invalidate_bearer`]: fn.invalidate_bearer.html
///
/// For more information, see the Twitter documentation on [Application-only authentication][auth].
///
/// [auth]: https://developer.twitter.com/en/docs/basics/authentication/oauth-2-0/application-only
pub async fn bearer_token(con_token: &KeyPair) -> Result<Token> {
    let content = "application/x-www-form-urlencoded;charset=UTF-8";

    let request = RequestBuilder::new(Method::POST, links::auth::BEARER_TOKEN)
        .with_body("grant_type=client_credentials", content)
        .request_consumer_bearer(con_token);

    let decoded = request_with_json_response::<serde_json::Value>(request).await?;
    let result = decoded
        .get("access_token")
        .and_then(|s| s.as_str())
        .ok_or(error::Error::MissingValue("access_token"))?;

    Ok(Token::Bearer(result.to_owned()))
}

/// Invalidate the given Bearer token using the given consumer KeyPair. Upon success, the future
/// returned by this function yields the Token that was just invalidated.
///
/// For more information about Bearer tokens, see the [authentication overview][auth] and the
/// documentation for the [`bearer_token`] function.
///
/// [auth]: index.html#bearer-tokens
/// [`bearer_token`]: fn.bearer_token.html
///
/// # Panics
///
/// If this function is handed a `Token` that is not a Bearer token, this function will panic.
pub async fn invalidate_bearer(con_token: &KeyPair, token: &Token) -> Result<Token> {
    let token = if let Token::Bearer(ref token) = *token {
        token
    } else {
        panic!("non-bearer token passed to invalidate_bearer");
    };

    let content = "application/x-www-form-urlencoded;charset=UTF-8";

    let request = RequestBuilder::new(Method::POST, links::auth::INVALIDATE_BEARER)
        .with_body(format!("access_token={}", token), content)
        .request_consumer_bearer(con_token);

    let decoded = request_with_json_response::<serde_json::Value>(request).await?;
    let result = decoded
        .get("access_token")
        .and_then(|s| s.as_str())
        .ok_or(error::Error::MissingValue("access_token"))?;

    Ok(Token::Bearer(result.to_owned()))
}

/// If the given tokens are valid, return the user information for the authenticated user.
///
/// If you have cached access tokens, using this method is a convenient way to make sure they're
/// still valid. If the user has revoked access from your app, this function will return an error
/// from Twitter indicating that you don't have access to the user.
pub async fn verify_tokens(token: &Token) -> Result<Response<crate::user::TwitterUser>> {
    let req = get(links::auth::VERIFY_CREDENTIALS, token, None);
    request_with_json_response(req).await
}