questrade_rs/
auth.rs

1use reqwest::header::{ACCEPT, CONTENT_LENGTH};
2use reqwest::Client;
3use serde::Deserialize;
4use std::error::Error;
5use std::ops::Add;
6use std::time::{Duration, Instant};
7
8/// Authentication token information.
9#[derive(Clone, PartialEq, Debug)]
10pub struct AuthenticationInfo {
11    /// Token used to refresh access token.
12    pub refresh_token: String,
13
14    /// Token to use for queries.
15    pub access_token: String,
16
17    /// Timestamp when access token expires.
18    pub expires_at: Instant,
19
20    /// API server to connect to for queries.
21    pub api_server: String,
22
23    /// Flag to indicate a practice account is in used.
24    pub is_demo: bool,
25}
26
27impl AuthenticationInfo {
28    /// Authenticates using the specified token and client
29    pub async fn authenticate(
30        refresh_token: &str,
31        is_demo: bool,
32        client: &Client,
33    ) -> Result<AuthenticationInfo, Box<dyn Error>> {
34        Self::refresh_access_token(refresh_token, is_demo, client).await
35    }
36
37    async fn refresh(&self, client: &Client) -> Result<AuthenticationInfo, Box<dyn Error>> {
38        Self::refresh_access_token(self.refresh_token.as_str(), self.is_demo, client).await
39    }
40
41    async fn refresh_access_token(
42        refresh_token: &str,
43        is_demo: bool,
44        client: &Client,
45    ) -> Result<AuthenticationInfo, Box<dyn Error>> {
46        #[derive(Deserialize, Clone, PartialEq, Debug)]
47        pub struct AuthenticationInfoResponse {
48            pub refresh_token: String,
49            pub access_token: String,
50            pub expires_in: u64,
51            pub api_server: String,
52        }
53
54        let url = get_url(is_demo);
55
56        let response = client
57            .post(url)
58            .query(&[
59                ("grant_type", "refresh_token"),
60                ("refresh_token", refresh_token),
61            ])
62            .header(CONTENT_LENGTH, 0)
63            .header(ACCEPT, "application/json")
64            .send()
65            .await?
66            .error_for_status()?
67            .json::<AuthenticationInfoResponse>()
68            .await?;
69
70        Ok(AuthenticationInfo {
71            refresh_token: response.refresh_token,
72            access_token: response.access_token,
73            expires_at: Instant::now().add(Duration::from_secs(response.expires_in)),
74            api_server: response.api_server.trim_end_matches('/').into(),
75            is_demo,
76        })
77    }
78}
79
80/// Gets the authentication url
81#[inline]
82fn get_url(is_demo: bool) -> &'static str {
83    if is_demo {
84        "https://practicelogin.questrade.com/oauth2/token"
85    } else {
86        "https://login.questrade.com/oauth2/token"
87    }
88}