Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub async fn refresh_token<RE, C, F>(
) -> Result<
(
AccessToken,
Option<std::time::Instant>,
Option<std::time::Duration>,
Option<RefreshToken>,
),
RefreshTokenError<RE>,
Expand All @@ -174,8 +174,6 @@ where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
{
let now = std::time::Instant::now();

let client = TwitchClient::new(
client_id.clone(),
Some(client_secret.clone()),
Expand All @@ -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))
}
16 changes: 13 additions & 3 deletions src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ pub trait TwitchToken {
RE: std::error::Error + Send + Sync + 'static,
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>;
/// Get instant when token will expire.
fn expires(&self) -> Option<std::time::Instant>;
/// Get current lifetime of token.
fn expires_in(&self) -> Option<std::time::Duration>;
/// Retrieve scopes attached to the token
fn scopes(&self) -> Option<&[Scope]>;
/// Validate this token. Should be checked on regularly, according to <https://dev.twitch.tv/docs/authentication#validating-requests>
Expand Down Expand Up @@ -81,7 +81,7 @@ impl<T: TwitchToken> TwitchToken for Box<T> {
(**self).refresh_token(http_client).await
}

fn expires(&self) -> Option<std::time::Instant> { (**self).expires() }
fn expires_in(&self) -> Option<std::time::Duration> { (**self).expires_in() }

fn scopes(&self) -> Option<&[Scope]> { (**self).scopes() }
}
Expand All @@ -99,4 +99,14 @@ pub struct ValidatedToken {
pub user_id: Option<String>,
/// Scopes attached to the token.
pub scopes: Option<Vec<Scope>>,
/// Lifetime of the token
#[serde(deserialize_with = "seconds_to_duration")]
pub expires_in: Option<std::time::Duration>,
}

fn seconds_to_duration<'a, D: serde::de::Deserializer<'a>>(
d: D,
) -> Result<Option<std::time::Duration>, D::Error> {
let seconds = Option::<u64>::deserialize(d)?;
Ok(seconds.map(std::time::Duration::from_secs))
}
27 changes: 18 additions & 9 deletions src/tokens/app_access_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RefreshToken>,
expires: Option<std::time::Instant>,
/// 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<RefreshToken>,
/// Expiration from when the response was generated.
expires_in: Option<std::time::Duration>,
/// When this struct was created, not when token was created.
struct_created: std::time::Instant,
client_id: ClientId,
client_secret: ClientSecret,
login: Option<String>,
Expand All @@ -36,19 +41,22 @@ impl TwitchToken for AppAccessToken {
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
{
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<std::time::Instant> { self.expires }
fn expires_in(&self) -> Option<std::time::Duration> {
self.expires_in.map(|e| e - self.struct_created.elapsed())
}

fn scopes(&self) -> Option<&[Scope]> { self.scopes.as_deref() }
}
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -107,7 +116,6 @@ impl AppAccessToken {
C: Fn(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
{
let now = std::time::Instant::now();
let client = TwitchClient::new(
client_id.clone(),
Some(client_secret.clone()),
Expand All @@ -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,
Expand Down
16 changes: 12 additions & 4 deletions src/tokens/user_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ pub struct UserToken {
login: Option<String>,
/// The refresh token used to extend the life of this user token
pub refresh_token: Option<RefreshToken>,
expires: Option<std::time::Instant>,
/// Expiration from when the response was generated.
expires_in: Option<std::time::Duration>,
/// When this struct was created, not when token was created.
struct_created: std::time::Instant,
scopes: Vec<Scope>,
}

Expand All @@ -34,14 +37,16 @@ impl UserToken {
client_secret: impl Into<Option<ClientSecret>>,
login: Option<String>,
scopes: Option<Vec<Scope>>,
expires_in: Option<std::time::Duration>,
) -> UserToken {
UserToken {
access_token: access_token.into(),
client_id: client_id.into(),
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),
}
}
Expand All @@ -66,6 +71,7 @@ impl UserToken {
client_secret,
validated.login,
validated.scopes,
validated.expires_in,
))
}

Expand Down Expand Up @@ -105,15 +111,17 @@ 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 {
return Err(RefreshTokenError::NoClientSecretFound);
}
}

fn expires(&self) -> Option<std::time::Instant> { None }
fn expires_in(&self) -> Option<std::time::Duration> {
self.expires_in.map(|e| e - self.struct_created.elapsed())
}

fn scopes(&self) -> Option<&[Scope]> { Some(self.scopes.as_slice()) }
}
Expand Down