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
use reqwest::header::{ACCEPT, CONTENT_LENGTH};
use reqwest::Client;
use serde::Deserialize;
use std::error::Error;
use std::ops::Add;
use std::time::{Duration, Instant};
#[derive(Clone, PartialEq, Debug)]
pub struct AuthenticationInfo {
pub refresh_token: String,
pub access_token: String,
pub expires_at: Instant,
pub api_server: String,
pub is_demo: bool,
}
impl AuthenticationInfo {
pub async fn authenticate(
refresh_token: &str,
is_demo: bool,
client: &Client,
) -> Result<AuthenticationInfo, Box<dyn Error>> {
Self::refresh_access_token(refresh_token, is_demo, client).await
}
async fn refresh(&self, client: &Client) -> Result<AuthenticationInfo, Box<dyn Error>> {
Self::refresh_access_token(self.refresh_token.as_str(), self.is_demo, client).await
}
async fn refresh_access_token(
refresh_token: &str,
is_demo: bool,
client: &Client,
) -> Result<AuthenticationInfo, Box<dyn Error>> {
#[derive(Deserialize, Clone, PartialEq, Debug)]
pub struct AuthenticationInfoResponse {
pub refresh_token: String,
pub access_token: String,
pub expires_in: u64,
pub api_server: String,
}
let url = get_url(is_demo);
let response = client
.post(url)
.query(&[
("grant_type", "refresh_token"),
("refresh_token", refresh_token),
])
.header(CONTENT_LENGTH, 0)
.header(ACCEPT, "application/json")
.send()
.await?
.error_for_status()?
.json::<AuthenticationInfoResponse>()
.await?;
Ok(AuthenticationInfo {
refresh_token: response.refresh_token,
access_token: response.access_token,
expires_at: Instant::now().add(Duration::from_secs(response.expires_in)),
api_server: response.api_server.trim_end_matches('/').into(),
is_demo,
})
}
}
#[inline]
fn get_url(is_demo: bool) -> &'static str {
if is_demo {
"https://practicelogin.questrade.com/oauth2/token"
} else {
"https://login.questrade.com/oauth2/token"
}
}