1mod app_access_token;
4pub mod errors;
5mod user_token;
6
7pub use app_access_token::AppAccessToken;
8use twitch_types::{UserId, UserIdRef, UserName, UserNameRef};
9pub use user_token::{
10 DeviceUserTokenBuilder, ImplicitUserTokenBuilder, UserToken, UserTokenBuilder,
11};
12
13#[cfg(feature = "client")]
14use crate::client::Client;
15use crate::{id::TwitchTokenErrorResponse, scopes::Scope, RequestParseError};
16
17use errors::ValidationError;
18#[cfg(feature = "client")]
19use errors::{RefreshTokenError, RevokeTokenError};
20
21use crate::types::{AccessToken, ClientId};
22use serde_derive::Deserialize;
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum BearerTokenType {
27 UserToken,
29 AppAccessToken,
34}
35
36#[cfg_attr(feature = "client", async_trait::async_trait)]
38pub trait TwitchToken {
39 fn token_type() -> BearerTokenType;
41 fn client_id(&self) -> &ClientId;
43 fn token(&self) -> &AccessToken;
55 fn login(&self) -> Option<&UserNameRef>;
57 fn user_id(&self) -> Option<&UserIdRef>;
59 #[cfg(feature = "client")]
61 async fn refresh_token<'a, C>(
62 &mut self,
63 http_client: &'a C,
64 ) -> Result<(), RefreshTokenError<<C as Client>::Error>>
65 where
66 Self: Sized,
67 C: Client;
68 fn expires_in(&self) -> std::time::Duration;
70
71 fn is_elapsed(&self) -> bool {
86 let exp = self.expires_in();
87 exp.as_secs() == 0 && exp.as_nanos() == 0
88 }
89 fn scopes(&self) -> &[Scope];
91 #[cfg(feature = "client")]
97 async fn validate_token<'a, C>(
98 &self,
99 http_client: &'a C,
100 ) -> Result<ValidatedToken, ValidationError<<C as Client>::Error>>
101 where
102 Self: Sized,
103 C: Client,
104 {
105 let token = &self.token();
106 token.validate_token(http_client).await
107 }
108
109 #[cfg(feature = "client")]
111 async fn revoke_token<'a, C>(
112 self,
113 http_client: &'a C,
114 ) -> Result<(), RevokeTokenError<<C as Client>::Error>>
115 where
116 Self: Sized,
117 C: Client,
118 {
119 let token = self.token();
120 let client_id = self.client_id();
121 token.revoke_token(http_client, client_id).await
122 }
123}
124
125#[cfg_attr(feature = "client", async_trait::async_trait)]
126impl<T: TwitchToken + Send> TwitchToken for Box<T> {
127 fn token_type() -> BearerTokenType { T::token_type() }
128
129 fn client_id(&self) -> &ClientId { (**self).client_id() }
130
131 fn token(&self) -> &AccessToken { (**self).token() }
132
133 fn login(&self) -> Option<&UserNameRef> { (**self).login() }
134
135 fn user_id(&self) -> Option<&UserIdRef> { (**self).user_id() }
136
137 #[cfg(feature = "client")]
138 async fn refresh_token<'a, C>(
139 &mut self,
140 http_client: &'a C,
141 ) -> Result<(), RefreshTokenError<<C as Client>::Error>>
142 where
143 Self: Sized,
144 C: Client,
145 {
146 (**self).refresh_token(http_client).await
147 }
148
149 fn expires_in(&self) -> std::time::Duration { (**self).expires_in() }
150
151 fn scopes(&self) -> &[Scope] { (**self).scopes() }
152}
153
154#[derive(Debug, Clone, Deserialize)]
158pub struct ValidatedToken {
159 pub client_id: ClientId,
161 pub login: Option<UserName>,
163 pub user_id: Option<UserId>,
165 pub scopes: Option<Vec<Scope>>,
167 #[serde(deserialize_with = "expires_in")]
169 pub expires_in: Option<std::time::Duration>,
170}
171
172fn expires_in<'a, D: serde::de::Deserializer<'a>>(
173 d: D,
174) -> Result<Option<std::time::Duration>, D::Error> {
175 use serde::Deserialize;
176 let num = u64::deserialize(d)?;
177 if num == 0 {
178 Ok(None)
179 } else {
180 Ok(Some(std::time::Duration::from_secs(num)))
181 }
182}
183
184impl ValidatedToken {
185 pub fn from_response<B: AsRef<[u8]>>(
189 response: &http::Response<B>,
190 ) -> Result<ValidatedToken, ValidationError<std::convert::Infallible>> {
191 match crate::parse_response(response) {
192 Ok(ok) => Ok(ok),
193 Err(err) => match err {
194 RequestParseError::TwitchError(TwitchTokenErrorResponse { status, .. })
195 if status == http::StatusCode::UNAUTHORIZED =>
196 {
197 Err(ValidationError::NotAuthorized)
198 }
199 err => Err(err.into()),
200 },
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::ValidatedToken;
208
209 use super::errors::ValidationError;
210
211 #[test]
212 fn validated_token() {
213 let body = br#"
214 {
215 "client_id": "wbmytr93xzw8zbg0p1izqyzzc5mbiz",
216 "login": "twitchdev",
217 "scopes": [
218 "channel:read:subscriptions"
219 ],
220 "user_id": "141981764",
221 "expires_in": 5520838
222 }
223 "#;
224 let response = http::Response::builder().status(200).body(body).unwrap();
225 ValidatedToken::from_response(&response).unwrap();
226 }
227
228 #[test]
229 fn validated_non_expiring_token() {
230 let body = br#"
231 {
232 "client_id": "wbmytr93xzw8zbg0p1izqyzzc5mbiz",
233 "login": "twitchdev",
234 "scopes": [
235 "channel:read:subscriptions"
236 ],
237 "user_id": "141981764",
238 "expires_in": 0
239 }
240 "#;
241 let response = http::Response::builder().status(200).body(body).unwrap();
242 let token = ValidatedToken::from_response(&response).unwrap();
243 assert!(token.expires_in.is_none());
244 }
245
246 #[test]
247 fn validated_error_response() {
248 let body = br#"
249 {
250 "status": 401,
251 "message": "missing authorization token",
252 }
253 "#;
254 let response = http::Response::builder().status(401).body(body).unwrap();
255 let error = ValidatedToken::from_response(&response).unwrap_err();
256 assert!(matches!(error, ValidationError::RequestParseError(_)))
257 }
258}