Skip to main content

oauth1_client/
flow.rs

1//! # Token Acquisition Flow Module
2//! This module assists with acquisition of an [AccessToken] every time a user authorizes your app.
3//!
4//! You must already have [ConsumerCredentials], see your service provider's documentation for more
5//! information.
6//!
7//! ## The Flow
8//! The typical token acquisition flow consists of following steps:
9//! 1. Acquiring a request token ([create_request_token_request]).
10//!    If the server uses `application/x-www-form-urlencoded` content type, you can use the
11//!    [get_request_token_from_response] helper function.
12//! 2. Redirecting the user to the service provider's authentication page ([create_request_uri]).
13//!    You will receive the `oauth_verifier`, either via the callback or a code displayed to the user.
14//! 3. Exchanging the [RequestToken] and `oauth_verifier` for an [AccessToken]
15//!    ([create_token_exchange_request]).
16//!    If the server uses `application/x-www-form-urlencoded` content type, you can use the
17//!    [get_access_token_from_response] helper function.
18
19use crate::*;
20use std::collections::HashMap;
21
22/// Internally the same as AccessToken, but the distinction is made for easier understanding
23pub type RequestToken = AccessToken;
24
25/// Creates an [OAuthRequest] to acquire a request token
26///
27/// If callback is none, sets `oauth_callback` to `oob`
28pub fn create_request_token_request(
29    uri: Uri,
30    callback: Option<Uri>,
31    consumer: ConsumerCredentials,
32    extra_params: Box<[Parameter]>,
33) -> Result<OAuthRequest, Box<dyn std::error::Error>> {
34    OAuthRequest::builder(
35        uri,
36        AuthenticationLevel::Consumer(consumer),
37        Box::new(signature::HmacSha1Signature),
38    )
39    .add_auth_parameters(&[Parameter::new(
40        "oauth_callback",
41        callback
42            .as_ref()
43            .map(Uri::to_string)
44            .unwrap_or("oob".to_string()),
45    )])
46    .add_auth_parameters(&extra_params)
47    .build()
48}
49
50/// Returns the [RequestToken] and any additional parameters
51pub fn get_request_token_from_response(
52    response: &str,
53) -> Result<(RequestToken, HashMap<String, String>), Box<dyn std::error::Error>> {
54    let mut parameters = HashMap::new();
55
56    for parameter in response.split('&') {
57        let (key, value) = parameter
58            .split_once('=')
59            .ok_or(OAuthError::InvalidResponse)?;
60        let key = percent_encoding::decode_string(key)?;
61        let value = percent_encoding::decode_string(value)?;
62
63        parameters.insert(key, value);
64    }
65
66    match parameters.remove("oauth_callback_confirmed") {
67        Some(c) if c == "true" => Ok(()),
68        None | Some(_) => Err(OAuthError::CallbackNotConfirmed),
69    }?;
70    let token = parameters
71        .remove("oauth_token")
72        .ok_or(OAuthError::InvalidResponse)?;
73    let secret = parameters
74        .remove("oauth_token_secret")
75        .ok_or(OAuthError::InvalidResponse)?;
76
77    Ok((RequestToken::new(&token, &secret), parameters))
78}
79
80/// Creates the request [Uri] you should present to your user
81pub fn create_request_uri(
82    uri: Uri,
83    token: RequestToken,
84    extra_params: Vec<Parameter>,
85) -> Result<Uri, Box<dyn std::error::Error>> {
86    // TODO: half of this is literally copied from a the request builder, that's like, bad
87    let mut params = extra_params;
88
89    params.push(Parameter::new("oauth_token", token.token));
90
91    params.sort();
92
93    let mut uri = uri.into_parts();
94
95    let params = params
96        .iter()
97        .map(|p| p.encoded())
98        .intersperse(String::from('&'))
99        .collect::<String>();
100
101    match uri.path_and_query {
102        None => uri.path_and_query = Some(PathAndQuery::from_str(&(String::from("?") + &params))?),
103        Some(pq) => match pq.query() {
104            None => {
105                uri.path_and_query = Some(PathAndQuery::from_str(&format!(
106                    "{}?{}",
107                    pq.path(),
108                    params
109                ))?)
110            }
111            Some(q) => {
112                uri.path_and_query = Some(PathAndQuery::from_str(&format!(
113                    "{}?{}&{}",
114                    pq.path(),
115                    q,
116                    params
117                ))?)
118            }
119        },
120    };
121
122    Ok(Uri::from_parts(uri)?)
123}
124
125/// Creates the token exchange request to exchange the [RequestToken] and verifier string for an [AccessToken]
126pub fn create_token_exchange_request(
127    uri: Uri,
128    verifier: String,
129    request_token: RequestToken,
130    consumer: ConsumerCredentials,
131    extra_params: Box<[Parameter]>,
132) -> Result<OAuthRequest, Box<dyn std::error::Error>> {
133    OAuthRequest::builder(
134        uri,
135        AuthenticationLevel::Token(consumer, request_token),
136        Box::new(HmacSha1Signature),
137    )
138    .add_auth_parameters(&[Parameter::new("oauth_verifier", verifier)])
139    .add_auth_parameters(&extra_params)
140    .build()
141}
142
143/// Returns the [AccessToken] and any additional parameters
144pub fn get_access_token_from_response(
145    response: &str,
146) -> Result<(AccessToken, HashMap<String, String>), Box<dyn std::error::Error>> {
147    let mut parameters = HashMap::new();
148
149    for parameter in response.split('&') {
150        let (key, value) = parameter
151            .split_once('=')
152            .ok_or(OAuthError::InvalidResponse)?;
153        let key = percent_encoding::decode_string(key)?;
154        let value = percent_encoding::decode_string(value)?;
155
156        parameters.insert(key, value);
157    }
158
159    let token = parameters
160        .remove("oauth_token")
161        .ok_or(OAuthError::InvalidResponse)?;
162    let secret = parameters
163        .remove("oauth_token_secret")
164        .ok_or(OAuthError::InvalidResponse)?;
165
166    Ok((AccessToken::new(&token, &secret), parameters))
167}