1use reqwest::header::AUTHORIZATION;
2use reqwest::Client;
3use reqwest::Url;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Deserialize, Serialize, Clone)]
8pub struct ValidatedToken {
9 client_id: String,
10 login: Option<String>,
11 user_id: Option<String>,
12 scopes: Vec<String>,
13}
14
15#[derive(Debug, Deserialize, Serialize, Clone)]
16pub struct AppAccessToken {
17 access_token: String,
18 expires_in: usize,
19 scope: Option<Vec<String>>,
20 token_type: String,
21}
22
23pub async fn get_app_access_token(
24 client_id: &str,
25 client_secret: &str,
26 scopes: Vec<String>,
27) -> Result<AppAccessToken, Box<dyn std::error::Error>> {
28 let joinee_scopes = scopes.join(" ");
29
30 let mut params = HashMap::new();
31 params.insert("grant_type", "client_credentials");
32 params.insert("client_id", client_id);
33 params.insert("client_secret", client_secret);
34 params.insert("scope", joinee_scopes.as_str());
35
36 let url = Url::parse_with_params("https://id.twitch.tv/oauth2/token", ¶ms).unwrap();
37
38 let resp: AppAccessToken = Client::new()
39 .post(url)
40 .send()
41 .await?
42 .error_for_status()?
43 .json()
44 .await?;
45
46 Ok(resp)
47}
48
49pub async fn validate_token(
50 token: AppAccessToken,
51) -> Result<ValidatedToken, Box<dyn std::error::Error>> {
52 let auth_header = format!("OAuth {}", token.access_token);
53
54 let resp: ValidatedToken = Client::new()
55 .get("https://id.twitch.tv/oauth2/validate")
56 .header(AUTHORIZATION, auth_header)
57 .send()
58 .await?
59 .error_for_status()?
60 .json()
61 .await?;
62
63 Ok(resp)
64}
65
66pub async fn revoke_token(
67 token: AppAccessToken,
68 client_id: &str,
69) -> Result<(), Box<dyn std::error::Error>> {
70 let mut params = HashMap::new();
71 params.insert("token", token.access_token.as_str());
72 params.insert("client_id", client_id);
73
74 let url = Url::parse_with_params("https://id.twitch.tv/oauth2/revoke", ¶ms).unwrap();
75
76 Client::new().post(url).send().await?.error_for_status()?;
77
78 Ok(())
79}