tw_api/
auth.rs

1use crate::helix::Client;
2use crate::helix::User;
3
4use anyhow::Result;
5use async_trait::async_trait;
6use chrono::DateTime;
7use chrono::Utc;
8use serde::Deserialize;
9use serde::Serialize;
10
11#[async_trait]
12pub trait TokenStorage {
13    async fn save(&mut self, token: &Token) -> Result<()>;
14}
15
16#[derive(Debug, Default, Clone, PartialEq)]
17pub enum TokenType {
18    #[default]
19    UserAccessToken,
20    AppAccessToken,
21}
22
23#[derive(Debug, Default, Clone, Serialize, Deserialize)]
24pub struct Token {
25    #[serde(skip)]
26    pub token_type: TokenType,
27    #[serde(default)]
28    pub refresh_token: String,
29    pub access_token: String,
30    pub expires_in: i64,
31    #[serde(default = "Utc::now")]
32    pub created_at: DateTime<Utc>,
33    #[serde(skip)]
34    pub user: Option<User>,
35}
36
37#[derive(Debug, Clone)]
38pub struct VoidStorage {}
39#[async_trait]
40impl TokenStorage for VoidStorage {
41    async fn save(&mut self, _token: &Token) -> Result<()> {
42        Ok(())
43    }
44}
45
46#[derive(Deserialize)]
47struct ValidateToken {
48    pub expires_in: i64,
49}
50
51impl<T: TokenStorage> Client<T> {
52    pub async fn validate_token(&mut self) -> Result<()> {
53        let token = match self
54            .get::<ValidateToken>("https://id.twitch.tv/oauth2/validate".to_string())
55            .await
56        {
57            Ok(r) => r,
58            Err(..) => {
59                self.refresh_token().await?;
60                return Ok(());
61            }
62        };
63
64        if token.expires_in < 3600 {
65            self.refresh_token().await?;
66        }
67
68        Ok(())
69    }
70
71    pub async fn refresh_token(&mut self) -> Result<()> {
72        if self.token.token_type == TokenType::AppAccessToken {
73            self.get_app_token().await?;
74            return Ok(());
75        }
76
77        let res = self
78            .http_request::<()>(
79                reqwest::Method::POST,
80                "https://id.twitch.tv/oauth2/token".to_string(),
81                None,
82                Some(format!(
83                    "client_id={0}&client_secret={1}&grant_type=refresh_token&refresh_token={2}",
84                    self.client_id, self.client_secret, self.token.refresh_token
85                )),
86            )
87            .await?;
88
89        self.token = res.json::<Token>().await?;
90        self.token_storage.save(&self.token).await?;
91
92        Ok(())
93    }
94
95    pub fn from_token_no_validation(
96        client_id: String,
97        client_secret: String,
98        token_storage: T,
99        token: Token,
100    ) -> Client<T> {
101        Client {
102            client_id: client_id,
103            client_secret: client_secret,
104            token: token,
105            http_client: reqwest::Client::new(),
106            token_storage: token_storage,
107        }
108    }
109
110    pub async fn from_token(
111        client_id: String,
112        client_secret: String,
113        token_storage: T,
114        token: Token,
115    ) -> Result<Client<T>> {
116        let mut client =
117            Self::from_token_no_validation(client_id, client_secret, token_storage, token);
118        client.token.user = Some(client.get_user().await?);
119        Ok(client)
120    }
121
122    async fn get_app_token(&mut self) -> Result<()> {
123        let token = self
124            .http_client
125            .post("https://id.twitch.tv/oauth2/token")
126            .body(format!(
127                "client_id={0}&client_secret={1}&grant_type=client_credentials",
128                self.client_id, self.client_secret
129            ))
130            .send()
131            .await?
132            .json::<Token>()
133            .await?;
134
135        self.token = token;
136        self.token.token_type = TokenType::AppAccessToken;
137        self.token_storage.save(&self.token).await?;
138
139        Ok(())
140    }
141
142    pub async fn from_get_app_token(
143        client_id: String,
144        client_secret: String,
145        token_storage: T,
146    ) -> Result<Client<T>> {
147        let http_client = reqwest::Client::new();
148        let mut client = Client {
149            client_id: client_id,
150            client_secret: client_secret,
151            http_client: http_client,
152            token_storage: token_storage,
153            token: Token::default(),
154        };
155        client.get_app_token().await?;
156        Ok(client)
157    }
158
159    pub async fn from_authorization(
160        client_id: String,
161        client_secret: String,
162        token_storage: T,
163        code: String,
164        redirect_uri: String,
165    ) -> Result<Client<T>> {
166        let http_client = reqwest::Client::new();
167        let token = http_client.post("https://id.twitch.tv/oauth2/token")
168                .body(format!("client_id={client_id}&client_secret={client_secret}&code={code}&grant_type=authorization_code&redirect_uri={redirect_uri}"))
169                .send()
170                .await?
171                .json::<Token>()
172                .await?;
173        let mut client = Client {
174            client_id: client_id,
175            client_secret: client_secret,
176            token: token,
177            http_client: http_client,
178            token_storage: token_storage,
179        };
180        client.token.user = Some(client.get_user().await?);
181        client.token_storage.save(&client.token).await?;
182        Ok(client)
183    }
184}