Skip to content

Commit 8a39ddf

Browse files
committed
make getting expiration better
1 parent 62196c7 commit 8a39ddf

File tree

5 files changed

+46
-20
lines changed

5 files changed

+46
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* Made scope take `Cow<&'static str>`
2020
* Made fields `access_token` and `refresh_token` `pub` on `UserToken`
2121
* Fixed wrong scope `user:read:stream_key` -> `channel:read:stream_key`
22+
* BREAKING: changed `TwitchToken::expires` -> `TwitchToken::expires_in` to calculate current lifetime of token
2223

2324
## End of Changelog
2425

src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ pub async fn refresh_token<RE, C, F>(
164164
) -> Result<
165165
(
166166
AccessToken,
167-
Option<std::time::Instant>,
167+
Option<std::time::Duration>,
168168
Option<RefreshToken>,
169169
),
170170
RefreshTokenError<RE>,
@@ -174,8 +174,6 @@ where
174174
C: FnOnce(HttpRequest) -> F,
175175
F: Future<Output = Result<HttpResponse, RE>>,
176176
{
177-
let now = std::time::Instant::now();
178-
179177
let client = TwitchClient::new(
180178
client_id.clone(),
181179
Some(client_secret.clone()),
@@ -190,7 +188,7 @@ where
190188
.await
191189
.map_err(RefreshTokenError::RequestError)?;
192190
let refresh_token = res.refresh_token().cloned();
193-
let expires = res.expires_in().map(|dur| now + dur);
191+
let expires = res.expires_in();
194192
let access_token = res.access_token;
195193
Ok((access_token, expires, refresh_token))
196194
}

src/tokens.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ pub trait TwitchToken {
3434
RE: std::error::Error + Send + Sync + 'static,
3535
C: FnOnce(HttpRequest) -> F,
3636
F: Future<Output = Result<HttpResponse, RE>>;
37-
/// Get instant when token will expire.
38-
fn expires(&self) -> Option<std::time::Instant>;
37+
/// Get current lifetime of token.
38+
fn expires_in(&self) -> Option<std::time::Duration>;
3939
/// Retrieve scopes attached to the token
4040
fn scopes(&self) -> Option<&[Scope]>;
4141
/// Validate this token. Should be checked on regularly, according to <https://dev.twitch.tv/docs/authentication#validating-requests>
@@ -81,7 +81,7 @@ impl<T: TwitchToken> TwitchToken for Box<T> {
8181
(**self).refresh_token(http_client).await
8282
}
8383

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

8686
fn scopes(&self) -> Option<&[Scope]> { (**self).scopes() }
8787
}
@@ -99,4 +99,14 @@ pub struct ValidatedToken {
9999
pub user_id: Option<String>,
100100
/// Scopes attached to the token.
101101
pub scopes: Option<Vec<Scope>>,
102+
/// Lifetime of the token
103+
#[serde(deserialize_with = "seconds_to_duration")]
104+
pub expires_in: Option<std::time::Duration>,
105+
}
106+
107+
fn seconds_to_duration<'a, D: serde::de::Deserializer<'a>>(
108+
d: D,
109+
) -> Result<Option<std::time::Duration>, D::Error> {
110+
let seconds = Option::<u64>::deserialize(d)?;
111+
Ok(seconds.map(std::time::Duration::from_secs))
102112
}

src/tokens/app_access_token.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ use std::future::Future;
1010
/// An App Access Token from the [OAuth client credentials flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow)
1111
#[derive(Debug, Clone)]
1212
pub struct AppAccessToken {
13-
access_token: AccessToken,
14-
refresh_token: Option<RefreshToken>,
15-
expires: Option<std::time::Instant>,
13+
/// The access token used to authenticate requests with
14+
pub access_token: AccessToken,
15+
/// The refresh token used to extend the life of this user token
16+
pub refresh_token: Option<RefreshToken>,
17+
/// Expiration from when the response was generated.
18+
expires_in: Option<std::time::Duration>,
19+
/// When this struct was created, not when token was created.
20+
struct_created: std::time::Instant,
1621
client_id: ClientId,
1722
client_secret: ClientSecret,
1823
login: Option<String>,
@@ -36,19 +41,22 @@ impl TwitchToken for AppAccessToken {
3641
C: FnOnce(HttpRequest) -> F,
3742
F: Future<Output = Result<HttpResponse, RE>>,
3843
{
39-
let (access_token, expires, refresh_token) = if let Some(token) = self.refresh_token.take()
44+
let (access_token, expires_in, refresh_token) = if let Some(token) =
45+
self.refresh_token.take()
4046
{
4147
crate::refresh_token(http_client, token, &self.client_id, &self.client_secret).await?
4248
} else {
4349
return Err(RefreshTokenError::NoRefreshToken);
4450
};
4551
self.access_token = access_token;
46-
self.expires = expires;
52+
self.expires_in = expires_in;
4753
self.refresh_token = refresh_token;
4854
Ok(())
4955
}
5056

51-
fn expires(&self) -> Option<std::time::Instant> { self.expires }
57+
fn expires_in(&self) -> Option<std::time::Duration> {
58+
self.expires_in.map(|e| e - self.struct_created.elapsed())
59+
}
5260

5361
fn scopes(&self) -> Option<&[Scope]> { self.scopes.as_deref() }
5462
}
@@ -68,7 +76,8 @@ impl AppAccessToken {
6876
client_id: client_id.into(),
6977
client_secret: client_secret.into(),
7078
login,
71-
expires: None,
79+
expires_in: None,
80+
struct_created: std::time::Instant::now(),
7281
scopes,
7382
}
7483
}
@@ -107,7 +116,6 @@ impl AppAccessToken {
107116
C: Fn(HttpRequest) -> F,
108117
F: Future<Output = Result<HttpResponse, RE>>,
109118
{
110-
let now = std::time::Instant::now();
111119
let client = TwitchClient::new(
112120
client_id.clone(),
113121
Some(client_secret.clone()),
@@ -128,7 +136,8 @@ impl AppAccessToken {
128136
let app_access = AppAccessToken {
129137
access_token: response.access_token().clone(),
130138
refresh_token: response.refresh_token().cloned(),
131-
expires: response.expires_in().map(|dur| now + dur),
139+
expires_in: response.expires_in(),
140+
struct_created: std::time::Instant::now(),
132141
client_id,
133142
client_secret,
134143
login: None,

src/tokens/user_token.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ pub struct UserToken {
2121
login: Option<String>,
2222
/// The refresh token used to extend the life of this user token
2323
pub refresh_token: Option<RefreshToken>,
24-
expires: Option<std::time::Instant>,
24+
/// Expiration from when the response was generated.
25+
expires_in: Option<std::time::Duration>,
26+
/// When this struct was created, not when token was created.
27+
struct_created: std::time::Instant,
2528
scopes: Vec<Scope>,
2629
}
2730

@@ -34,14 +37,16 @@ impl UserToken {
3437
client_secret: impl Into<Option<ClientSecret>>,
3538
login: Option<String>,
3639
scopes: Option<Vec<Scope>>,
40+
expires_in: Option<std::time::Duration>,
3741
) -> UserToken {
3842
UserToken {
3943
access_token: access_token.into(),
4044
client_id: client_id.into(),
4145
client_secret: client_secret.into(),
4246
login,
4347
refresh_token: refresh_token.into(),
44-
expires: None,
48+
expires_in,
49+
struct_created: std::time::Instant::now(),
4550
scopes: scopes.unwrap_or_else(Vec::new),
4651
}
4752
}
@@ -66,6 +71,7 @@ impl UserToken {
6671
client_secret,
6772
validated.login,
6873
validated.scopes,
74+
validated.expires_in,
6975
))
7076
}
7177

@@ -105,15 +111,17 @@ impl TwitchToken for UserToken {
105111
return Err(RefreshTokenError::NoRefreshToken);
106112
};
107113
self.access_token = access_token;
108-
self.expires = expires;
114+
self.expires_in = expires;
109115
self.refresh_token = refresh_token;
110116
Ok(())
111117
} else {
112118
return Err(RefreshTokenError::NoClientSecretFound);
113119
}
114120
}
115121

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

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

0 commit comments

Comments
 (0)