Skip to content

Commit b7433d2

Browse files
bors[bot]DinnerboneEmilgardis
authored
Merge #22
22: Add refresh ability to user tokens, and expose tokens for storage r=Emilgardis a=Dinnerbone This changes the API by adding an extra (optional) client secret field. I also make some fields pub so that an application can retrieve them post-refresh and store them for the next run. Co-authored-by: Nathan Adams <[email protected]> Co-authored-by: Emil Gardström <[email protected]>
2 parents 386919a + 4ab4571 commit b7433d2

File tree

4 files changed

+93
-59
lines changed

4 files changed

+93
-59
lines changed

examples/user_token.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ async fn main() {
1313
.ok()
1414
.or_else(|| args.next())
1515
.map(twitch_oauth2::RefreshToken::new),
16+
std::env::var("TWITCH_CLIENT_SECRET")
17+
.ok()
18+
.or_else(|| args.next())
19+
.map(twitch_oauth2::ClientSecret::new),
1620
)
1721
.await
1822
.unwrap();

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
//! # let reqwest_http_client = twitch_oauth2::dummy_http_client; // This is only here to fool doc tests
2020
//! let token = AccessToken::new("sometokenherewhichisvalidornot".to_string());
2121
//!
22-
//! match UserToken::from_existing(reqwest_http_client, token, None).await {
22+
//! match UserToken::from_existing(reqwest_http_client, token, None, None).await {
2323
//! Ok(t) => println!("user_token: {}", t.token().secret()),
2424
//! Err(e) => panic!("got error: {}", e),
2525
//! }

src/tokens/errors.rs

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,57 @@
1-
//! Errors
2-
3-
use crate::id::TwitchTokenErrorResponse;
4-
use oauth2::HttpResponse as OAuth2HttpResponse;
5-
use oauth2::RequestTokenError;
6-
/// General errors for talking with twitch, currently only used in [AppAccessToken::get_app_access_token][crate::tokens::AppAccessToken::get_app_access_token]
7-
#[allow(missing_docs)]
8-
#[derive(thiserror::Error, Debug, displaydoc::Display)]
9-
pub enum TokenError<RE: std::error::Error + Send + Sync + 'static> {
10-
/// request for token failed. {0}
11-
Request(RequestTokenError<RE, TwitchTokenErrorResponse>),
12-
/// could not parse url
13-
ParseError(#[from] oauth2::url::ParseError),
14-
/// could not get validation for token
15-
ValidationError(#[from] ValidationError<RE>),
16-
}
17-
18-
/// Errors for [validate_token][crate::validate_token]
19-
#[derive(thiserror::Error, Debug, displaydoc::Display)]
20-
pub enum ValidationError<RE: std::error::Error + Send + Sync + 'static> {
21-
/// deserializations failed
22-
DeserializeError(#[from] serde_json::Error),
23-
/// token is not authorized for use
24-
NotAuthorized,
25-
/// twitch returned an unexpected status: {0}
26-
TwitchError(TwitchTokenErrorResponse),
27-
/// failed to request validation: {0}
28-
Request(#[source] RE),
29-
}
30-
31-
/// Errors for [revoke_token][crate::revoke_token]
32-
#[allow(missing_docs)]
33-
#[derive(thiserror::Error, Debug, displaydoc::Display)]
34-
pub enum RevokeTokenError<RE: std::error::Error + Send + Sync + 'static> {
35-
/// 400 Bad Request: {0}
36-
BadRequest(String),
37-
/// failed to do revokation: {0}
38-
RequestError(#[source] RE),
39-
/// got unexpected return: {0:?}
40-
Other(OAuth2HttpResponse),
41-
}
42-
43-
/// Errors for [TwitchToken::refresh_token][crate::TwitchToken::refresh_token]
44-
#[allow(missing_docs)]
45-
#[derive(thiserror::Error, Debug, displaydoc::Display)]
46-
pub enum RefreshTokenError<RE: std::error::Error + Send + Sync + 'static> {
47-
/// request for token failed. {0}
48-
RequestError(#[source] RequestTokenError<RE, TwitchTokenErrorResponse>),
49-
/// could not parse url
50-
ParseError(#[from] oauth2::url::ParseError),
51-
/// no refresh token found
52-
NoRefreshToken,
53-
}
1+
//! Errors
2+
3+
use crate::id::TwitchTokenErrorResponse;
4+
use oauth2::HttpResponse as OAuth2HttpResponse;
5+
use oauth2::RequestTokenError;
6+
/// General errors for talking with twitch, currently only used in [AppAccessToken::get_app_access_token][crate::tokens::AppAccessToken::get_app_access_token]
7+
#[allow(missing_docs)]
8+
#[derive(thiserror::Error, Debug, displaydoc::Display)]
9+
pub enum TokenError<RE: std::error::Error + Send + Sync + 'static> {
10+
/// request for token failed. {0}
11+
Request(RequestTokenError<RE, TwitchTokenErrorResponse>),
12+
/// could not parse url
13+
ParseError(#[from] oauth2::url::ParseError),
14+
/// could not get validation for token
15+
ValidationError(#[from] ValidationError<RE>),
16+
}
17+
18+
/// Errors for [validate_token][crate::validate_token]
19+
#[derive(thiserror::Error, Debug, displaydoc::Display)]
20+
pub enum ValidationError<RE: std::error::Error + Send + Sync + 'static> {
21+
/// deserializations failed
22+
DeserializeError(#[from] serde_json::Error),
23+
/// token is not authorized for use
24+
NotAuthorized,
25+
/// twitch returned an unexpected status: {0}
26+
TwitchError(TwitchTokenErrorResponse),
27+
/// failed to request validation: {0}
28+
Request(#[source] RE),
29+
}
30+
31+
/// Errors for [revoke_token][crate::revoke_token]
32+
#[allow(missing_docs)]
33+
#[derive(thiserror::Error, Debug, displaydoc::Display)]
34+
pub enum RevokeTokenError<RE: std::error::Error + Send + Sync + 'static> {
35+
/// 400 Bad Request: {0}
36+
BadRequest(String),
37+
/// failed to do revokation: {0}
38+
RequestError(#[source] RE),
39+
/// got unexpected return: {0:?}
40+
Other(OAuth2HttpResponse),
41+
}
42+
43+
/// Errors for [TwitchToken::refresh_token][crate::TwitchToken::refresh_token]
44+
#[allow(missing_docs)]
45+
#[derive(thiserror::Error, Debug, displaydoc::Display)]
46+
pub enum RefreshTokenError<RE: std::error::Error + Send + Sync + 'static> {
47+
/// request for token failed. {0}
48+
RequestError(#[source] RequestTokenError<RE, TwitchTokenErrorResponse>),
49+
/// could not parse url
50+
ParseError(#[from] oauth2::url::ParseError),
51+
/// no client secret found
52+
///
53+
/// A client secret is needed to request a refreshed token.
54+
NoClientSecretFound,
55+
/// no refresh token found
56+
NoRefreshToken,
57+
}

src/tokens/user_token.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@ use crate::tokens::{
22
errors::{RefreshTokenError, ValidationError},
33
Scope, TwitchToken,
44
};
5+
use crate::ClientSecret;
56
use oauth2::{AccessToken, ClientId, RefreshToken};
67
use oauth2::{HttpRequest, HttpResponse};
78
use std::future::Future;
89

910
/// An User Token from the [OAuth implicit code flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-implicit-code-flow) or [OAuth authorization code flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-authorization-code-flow)
1011
#[derive(Debug, Clone)]
1112
pub struct UserToken {
12-
access_token: AccessToken,
13+
/// The access token used to authenticate requests with
14+
pub access_token: AccessToken,
1315
client_id: ClientId,
16+
client_secret: Option<ClientSecret>,
1417
login: Option<String>,
15-
refresh_token: Option<RefreshToken>,
18+
/// The refresh token used to extend the life of this user token
19+
pub refresh_token: Option<RefreshToken>,
1620
expires: Option<std::time::Instant>,
1721
scopes: Vec<Scope>,
1822
}
@@ -23,12 +27,14 @@ impl UserToken {
2327
access_token: impl Into<AccessToken>,
2428
refresh_token: impl Into<Option<RefreshToken>>,
2529
client_id: impl Into<ClientId>,
30+
client_secret: impl Into<Option<ClientSecret>>,
2631
login: Option<String>,
2732
scopes: Option<Vec<Scope>>,
2833
) -> UserToken {
2934
UserToken {
3035
access_token: access_token.into(),
3136
client_id: client_id.into(),
37+
client_secret: client_secret.into(),
3238
login,
3339
refresh_token: refresh_token.into(),
3440
expires: None,
@@ -41,6 +47,7 @@ impl UserToken {
4147
http_client: C,
4248
access_token: AccessToken,
4349
refresh_token: impl Into<Option<RefreshToken>>,
50+
client_secret: impl Into<Option<ClientSecret>>,
4451
) -> Result<UserToken, ValidationError<RE>>
4552
where
4653
RE: std::error::Error + Send + Sync + 'static,
@@ -52,6 +59,7 @@ impl UserToken {
5259
access_token,
5360
refresh_token.into(),
5461
validated.client_id,
62+
client_secret,
5563
validated.login,
5664
validated.scopes,
5765
))
@@ -66,12 +74,30 @@ impl TwitchToken for UserToken {
6674

6775
fn login(&self) -> Option<&str> { self.login.as_deref() }
6876

69-
async fn refresh_token<RE, C, F>(&mut self, _: C) -> Result<(), RefreshTokenError<RE>>
77+
async fn refresh_token<RE, C, F>(
78+
&mut self,
79+
http_client: C,
80+
) -> Result<(), RefreshTokenError<RE>>
7081
where
7182
RE: std::error::Error + Send + Sync + 'static,
7283
C: FnOnce(HttpRequest) -> F,
73-
F: Future<Output = Result<HttpResponse, RE>>, {
74-
Err(RefreshTokenError::NoRefreshToken)
84+
F: Future<Output = Result<HttpResponse, RE>>,
85+
{
86+
if let Some(client_secret) = self.client_secret.clone() {
87+
let (access_token, expires, refresh_token) = if let Some(token) =
88+
self.refresh_token.take()
89+
{
90+
crate::refresh_token(http_client, token, &self.client_id, &client_secret).await?
91+
} else {
92+
return Err(RefreshTokenError::NoRefreshToken);
93+
};
94+
self.access_token = access_token;
95+
self.expires = expires;
96+
self.refresh_token = refresh_token;
97+
Ok(())
98+
} else {
99+
return Err(RefreshTokenError::NoClientSecretFound);
100+
}
75101
}
76102

77103
fn expires(&self) -> Option<std::time::Instant> { None }

0 commit comments

Comments
 (0)