paypal_rust/client/
auth.rs

1use std::borrow::Cow;
2use std::ops::Sub;
3
4use chrono::{DateTime, Utc};
5use reqwest::Method;
6use serde::{Deserialize, Serialize};
7use serde_with::skip_serializing_none;
8
9use crate::client::endpoint::Endpoint;
10use crate::client::request::{HttpRequestHeaders, RequestStrategy, RetryCount};
11
12#[derive(Debug, Clone, Serialize)]
13pub struct AuthRequestBody {
14    grant_type: String,
15}
16
17#[skip_serializing_none]
18#[derive(Clone, Debug, Deserialize, Default)]
19pub struct AuthResponse {
20    /// Which scopes are granted. Comma seperated URLS as String.
21    pub scope: Option<String>,
22
23    /// Access token.
24    pub access_token: String,
25
26    /// Refresh token.
27    pub refresh_token: Option<String>,
28
29    /// Token type (Bearer, Basic, etc.).
30    pub token_type: String,
31
32    /// The clients App ID.
33    pub app_id: String,
34
35    /// Number of seconds until the access_token expires
36    pub expires_in: i32,
37
38    /// Authentication Nonce.
39    pub nonce: String,
40}
41
42#[derive(Debug)]
43pub struct Authenticate {
44    pub authorization: String,
45}
46
47impl Authenticate {
48    pub const fn new(authorization: String) -> Self {
49        Self { authorization }
50    }
51}
52
53impl Endpoint for Authenticate {
54    type QueryParams = ();
55    type RequestBody = AuthRequestBody;
56    type ResponseBody = AuthResponse;
57
58    fn path(&self) -> Cow<str> {
59        Cow::Borrowed("v1/oauth2/token")
60    }
61
62    fn headers(&self) -> HttpRequestHeaders {
63        HttpRequestHeaders {
64            authorization: self.authorization.clone(),
65            content_type: "application/x-www-form-urlencoded".to_string(),
66            ..Default::default()
67        }
68    }
69
70    fn request_body(&self) -> Option<Self::RequestBody> {
71        Some(AuthRequestBody {
72            grant_type: "client_credentials".to_string(),
73        })
74    }
75
76    fn request_method(&self) -> Method {
77        Method::POST
78    }
79
80    fn request_strategy(&self) -> RequestStrategy {
81        RequestStrategy::Retry(RetryCount::from(3))
82    }
83
84    fn auth_strategy(&self) -> AuthStrategy {
85        AuthStrategy::NoTokenRefresh
86    }
87}
88
89#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
90/// Whether to check the validity of the access token and refresh it if necessary or not.
91pub enum AuthStrategy {
92    /// Always check the validity of the access token and refresh it if necessary.
93    #[default]
94    TokenRefresh,
95    /// Never check the validity of the access token and never refresh it.
96    NoTokenRefresh,
97}
98
99#[skip_serializing_none]
100#[derive(Debug, Clone, Default, Serialize)]
101pub struct AuthData {
102    pub access_token: String,
103    pub refresh_token: Option<String>,
104
105    /// The timestamp the access token expires in.
106    pub expiry_time: Option<DateTime<Utc>>,
107}
108
109impl AuthData {
110    pub fn about_to_expire(&self) -> bool {
111        self.expiry_time.map_or(true, |expiry_time| {
112            expiry_time.sub(Utc::now()).num_seconds() < 10
113        })
114    }
115
116    pub fn update(&mut self, response: AuthResponse) {
117        self.access_token = response.access_token;
118        self.refresh_token = response.refresh_token;
119        self.expiry_time =
120            Some(Utc::now() + chrono::Duration::seconds(i64::from(response.expires_in)));
121    }
122}