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