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
use crate::client::endpoint::Endpoint;
use crate::client::request::{HttpRequestHeaders, RequestStrategy, RetryCount};
use chrono::{DateTime, Utc};
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::borrow::Cow;
use std::ops::Sub;

#[derive(Debug, Clone, Serialize)]
pub struct AuthRequestBody {
    grant_type: String,
}

#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Default)]
pub struct AuthResponse {
    /// Which scopes are granted. Comma seperated URLS as String.
    pub scope: Option<String>,

    /// Access token.
    pub access_token: String,

    /// Refresh token.
    pub refresh_token: Option<String>,

    /// Token type (Bearer, Basic, etc.).
    pub token_type: String,

    /// The clients App ID.
    pub app_id: String,

    /// Number of seconds until the access_token expires
    pub expires_in: i32,

    /// Authentication Nonce.
    pub nonce: String,
}

#[derive(Debug)]
pub struct Authenticate {
    pub authorization: String,
}

impl Authenticate {
    pub fn new(authorization: String) -> Self {
        Self { authorization }
    }
}

impl Endpoint for Authenticate {
    type QueryParams = ();
    type RequestBody = AuthRequestBody;
    type ResponseBody = AuthResponse;

    fn path(&self) -> Cow<str> {
        Cow::Borrowed("v1/oauth2/token")
    }

    fn headers(&self) -> HttpRequestHeaders {
        HttpRequestHeaders {
            authorization: self.authorization.clone(),
            content_type: "application/x-www-form-urlencoded".to_string(),
            ..Default::default()
        }
    }

    fn request_body(&self) -> Option<Self::RequestBody> {
        Some(AuthRequestBody {
            grant_type: "client_credentials".to_string(),
        })
    }

    fn request_method(&self) -> Method {
        Method::POST
    }

    fn request_strategy(&self) -> RequestStrategy {
        RequestStrategy::Retry(RetryCount::from(3))
    }

    fn auth_strategy(&self) -> AuthStrategy {
        AuthStrategy::NoTokenRefresh
    }
}

#[derive(Debug, Copy, Clone)]
/// Whether to check the validity of the access token and refresh it if necessary or not.
pub enum AuthStrategy {
    /// Always check the validity of the access token and refresh it if necessary.
    TokenRefresh,
    /// Never check the validity of the access token and never refresh it.
    NoTokenRefresh,
}

impl Default for AuthStrategy {
    fn default() -> Self {
        Self::TokenRefresh
    }
}

#[skip_serializing_none]
#[derive(Debug, Clone, Default, Serialize)]
pub struct AuthData {
    pub access_token: String,
    pub refresh_token: Option<String>,

    /// The timestamp the access token expires in.
    pub expiry_time: Option<DateTime<Utc>>,
}

impl AuthData {
    pub fn about_to_expire(&self) -> bool {
        self.expiry_time
            .map(|expiry_time| expiry_time.sub(Utc::now()).num_seconds() < 10)
            .unwrap_or(true)
    }

    pub fn update(&mut self, response: AuthResponse) {
        self.access_token = response.access_token;
        self.refresh_token = response.refresh_token;
        self.expiry_time = Some(Utc::now() + chrono::Duration::seconds(response.expires_in as i64));
    }
}