From 8a39ddf80592df78098f225acde141de7c6fa84f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Gardstr=C3=B6m?= Date: Fri, 5 Mar 2021 23:11:25 +0100 Subject: [PATCH 1/2] make getting expiration better --- CHANGELOG.md | 1 + src/lib.rs | 6 ++---- src/tokens.rs | 16 +++++++++++++--- src/tokens/app_access_token.rs | 27 ++++++++++++++++++--------- src/tokens/user_token.rs | 16 ++++++++++++---- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c66d1aad..d9e52e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Made scope take `Cow<&'static str>` * Made fields `access_token` and `refresh_token` `pub` on `UserToken` * Fixed wrong scope `user:read:stream_key` -> `channel:read:stream_key` +* BREAKING: changed `TwitchToken::expires` -> `TwitchToken::expires_in` to calculate current lifetime of token ## End of Changelog diff --git a/src/lib.rs b/src/lib.rs index 41594e7d..0b8c3056 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,7 @@ pub async fn refresh_token( ) -> Result< ( AccessToken, - Option, + Option, Option, ), RefreshTokenError, @@ -174,8 +174,6 @@ where C: FnOnce(HttpRequest) -> F, F: Future>, { - let now = std::time::Instant::now(); - let client = TwitchClient::new( client_id.clone(), Some(client_secret.clone()), @@ -190,7 +188,7 @@ where .await .map_err(RefreshTokenError::RequestError)?; let refresh_token = res.refresh_token().cloned(); - let expires = res.expires_in().map(|dur| now + dur); + let expires = res.expires_in(); let access_token = res.access_token; Ok((access_token, expires, refresh_token)) } diff --git a/src/tokens.rs b/src/tokens.rs index 69f58a26..cd9196fd 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -34,8 +34,8 @@ pub trait TwitchToken { RE: std::error::Error + Send + Sync + 'static, C: FnOnce(HttpRequest) -> F, F: Future>; - /// Get instant when token will expire. - fn expires(&self) -> Option; + /// Get current lifetime of token. + fn expires_in(&self) -> Option; /// Retrieve scopes attached to the token fn scopes(&self) -> Option<&[Scope]>; /// Validate this token. Should be checked on regularly, according to @@ -81,7 +81,7 @@ impl TwitchToken for Box { (**self).refresh_token(http_client).await } - fn expires(&self) -> Option { (**self).expires() } + fn expires_in(&self) -> Option { (**self).expires_in() } fn scopes(&self) -> Option<&[Scope]> { (**self).scopes() } } @@ -99,4 +99,14 @@ pub struct ValidatedToken { pub user_id: Option, /// Scopes attached to the token. pub scopes: Option>, + /// Lifetime of the token + #[serde(deserialize_with = "seconds_to_duration")] + pub expires_in: Option, +} + +fn seconds_to_duration<'a, D: serde::de::Deserializer<'a>>( + d: D, +) -> Result, D::Error> { + let seconds = Option::::deserialize(d)?; + Ok(seconds.map(std::time::Duration::from_secs)) } diff --git a/src/tokens/app_access_token.rs b/src/tokens/app_access_token.rs index 9ac3f99a..60468a14 100644 --- a/src/tokens/app_access_token.rs +++ b/src/tokens/app_access_token.rs @@ -10,9 +10,14 @@ use std::future::Future; /// An App Access Token from the [OAuth client credentials flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow) #[derive(Debug, Clone)] pub struct AppAccessToken { - access_token: AccessToken, - refresh_token: Option, - expires: Option, + /// The access token used to authenticate requests with + pub access_token: AccessToken, + /// The refresh token used to extend the life of this user token + pub refresh_token: Option, + /// Expiration from when the response was generated. + expires_in: Option, + /// When this struct was created, not when token was created. + struct_created: std::time::Instant, client_id: ClientId, client_secret: ClientSecret, login: Option, @@ -36,19 +41,22 @@ impl TwitchToken for AppAccessToken { C: FnOnce(HttpRequest) -> F, F: Future>, { - let (access_token, expires, refresh_token) = if let Some(token) = self.refresh_token.take() + let (access_token, expires_in, refresh_token) = if let Some(token) = + self.refresh_token.take() { crate::refresh_token(http_client, token, &self.client_id, &self.client_secret).await? } else { return Err(RefreshTokenError::NoRefreshToken); }; self.access_token = access_token; - self.expires = expires; + self.expires_in = expires_in; self.refresh_token = refresh_token; Ok(()) } - fn expires(&self) -> Option { self.expires } + fn expires_in(&self) -> Option { + self.expires_in.map(|e| e - self.struct_created.elapsed()) + } fn scopes(&self) -> Option<&[Scope]> { self.scopes.as_deref() } } @@ -68,7 +76,8 @@ impl AppAccessToken { client_id: client_id.into(), client_secret: client_secret.into(), login, - expires: None, + expires_in: None, + struct_created: std::time::Instant::now(), scopes, } } @@ -107,7 +116,6 @@ impl AppAccessToken { C: Fn(HttpRequest) -> F, F: Future>, { - let now = std::time::Instant::now(); let client = TwitchClient::new( client_id.clone(), Some(client_secret.clone()), @@ -128,7 +136,8 @@ impl AppAccessToken { let app_access = AppAccessToken { access_token: response.access_token().clone(), refresh_token: response.refresh_token().cloned(), - expires: response.expires_in().map(|dur| now + dur), + expires_in: response.expires_in(), + struct_created: std::time::Instant::now(), client_id, client_secret, login: None, diff --git a/src/tokens/user_token.rs b/src/tokens/user_token.rs index 4704c871..cb5057b9 100644 --- a/src/tokens/user_token.rs +++ b/src/tokens/user_token.rs @@ -21,7 +21,10 @@ pub struct UserToken { login: Option, /// The refresh token used to extend the life of this user token pub refresh_token: Option, - expires: Option, + /// Expiration from when the response was generated. + expires_in: Option, + /// When this struct was created, not when token was created. + struct_created: std::time::Instant, scopes: Vec, } @@ -34,6 +37,7 @@ impl UserToken { client_secret: impl Into>, login: Option, scopes: Option>, + expires_in: Option, ) -> UserToken { UserToken { access_token: access_token.into(), @@ -41,7 +45,8 @@ impl UserToken { client_secret: client_secret.into(), login, refresh_token: refresh_token.into(), - expires: None, + expires_in, + struct_created: std::time::Instant::now(), scopes: scopes.unwrap_or_else(Vec::new), } } @@ -66,6 +71,7 @@ impl UserToken { client_secret, validated.login, validated.scopes, + validated.expires_in, )) } @@ -105,7 +111,7 @@ impl TwitchToken for UserToken { return Err(RefreshTokenError::NoRefreshToken); }; self.access_token = access_token; - self.expires = expires; + self.expires_in = expires; self.refresh_token = refresh_token; Ok(()) } else { @@ -113,7 +119,9 @@ impl TwitchToken for UserToken { } } - fn expires(&self) -> Option { None } + fn expires_in(&self) -> Option { + self.expires_in.map(|e| e - self.struct_created.elapsed()) + } fn scopes(&self) -> Option<&[Scope]> { Some(self.scopes.as_slice()) } } From fc8de9f9b84618d1454b1eb2f160d93e387a303b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Gardstr=C3=B6m?= Date: Fri, 5 Mar 2021 23:30:17 +0100 Subject: [PATCH 2/2] fix lint error also bumps msrv --- .github/workflows/ci.yml | 2 +- src/lib.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65dac32b..75158309 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: os: [windows-latest, ubuntu-latest] - rust: [1.45.2, nightly] + rust: [1.48, nightly] runs-on: ${{ matrix.os }} steps: - name: Checkout diff --git a/src/lib.rs b/src/lib.rs index 0b8c3056..589ffceb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ -#![allow(unknown_lints)] // remove once broken_intra_doc_links is on stable -#![deny(missing_docs, broken_intra_doc_links)] +#![allow(unknown_lints, renamed_and_removed_lints)] +#![deny(missing_docs, broken_intra_doc_links)] // This will be weird until 1.52, see https://github.com/rust-lang/rust/pull/80527 +#![cfg_attr(nightly, deny(rustdoc::broken_intra_doc_links))] #![cfg_attr(nightly, feature(doc_cfg))] #![doc(html_root_url = "https://docs.rs/twitch_oauth2/0.4.1")] //! [![github]](https://github.com/emilgardis/twitch_oauth2) [![crates-io]](https://crates.io/crates/twitch_oauth2) [![docs-rs]](https://docs.rs/twitch_oauth2/0.4.1/twitch_oauth2)