Skip to content

Commit 4ab4571

Browse files
committed
add RefreshTokenError::NoClientSecretFound
Also run cargo fmt with fix for newlines on multilined functions
1 parent 11ca64d commit 4ab4571

File tree

3 files changed

+215
-217
lines changed

3 files changed

+215
-217
lines changed

src/tokens/app_access_token.rs

Lines changed: 146 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,146 @@
1-
use super::errors::{TokenError, ValidationError};
2-
use crate::{
3-
id::TwitchClient,
4-
tokens::{errors::RefreshTokenError, Scope, TwitchToken},
5-
};
6-
use oauth2::{AccessToken, AuthUrl, ClientId, ClientSecret, RefreshToken, TokenResponse};
7-
use oauth2::{HttpRequest, HttpResponse};
8-
use std::future::Future;
9-
10-
/// An App Access Token from the [OAuth client credentials flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow)
11-
#[derive(Debug, Clone)]
12-
pub struct AppAccessToken {
13-
access_token: AccessToken,
14-
refresh_token: Option<RefreshToken>,
15-
expires: Option<std::time::Instant>,
16-
client_id: ClientId,
17-
client_secret: ClientSecret,
18-
login: Option<String>,
19-
scopes: Option<Vec<Scope>>,
20-
}
21-
22-
#[async_trait::async_trait(?Send)]
23-
impl TwitchToken for AppAccessToken {
24-
fn client_id(&self) -> &ClientId { &self.client_id }
25-
26-
fn token(&self) -> &AccessToken { &self.access_token }
27-
28-
fn login(&self) -> Option<&str> { self.login.as_deref() }
29-
30-
async fn refresh_token<RE, C, F>(
31-
&mut self,
32-
http_client: C,
33-
) -> Result<(), RefreshTokenError<RE>>
34-
where
35-
RE: std::error::Error + Send + Sync + 'static,
36-
C: FnOnce(HttpRequest) -> F,
37-
F: Future<Output = Result<HttpResponse, RE>>,
38-
{
39-
let (access_token, expires, refresh_token) = if let Some(token) = self.refresh_token.take()
40-
{
41-
crate::refresh_token(http_client, token, &self.client_id, &self.client_secret).await?
42-
} else {
43-
return Err(RefreshTokenError::NoRefreshToken);
44-
};
45-
self.access_token = access_token;
46-
self.expires = expires;
47-
self.refresh_token = refresh_token;
48-
Ok(())
49-
}
50-
51-
fn expires(&self) -> Option<std::time::Instant> { self.expires }
52-
53-
fn scopes(&self) -> Option<&[Scope]> { self.scopes.as_deref() }
54-
}
55-
56-
impl AppAccessToken {
57-
/// Assemble token without checks.
58-
pub fn from_existing_unchecked(
59-
access_token: AccessToken,
60-
client_id: impl Into<ClientId>,
61-
client_secret: impl Into<ClientSecret>,
62-
login: Option<String>,
63-
scopes: Option<Vec<Scope>>,
64-
) -> AppAccessToken
65-
{
66-
AppAccessToken {
67-
access_token,
68-
refresh_token: None,
69-
client_id: client_id.into(),
70-
client_secret: client_secret.into(),
71-
login,
72-
expires: None,
73-
scopes,
74-
}
75-
}
76-
77-
/// Assemble token and validate it. Retrieves [`client_id`](TwitchToken::client_id) and [`scopes`](TwitchToken::scopes).
78-
pub async fn from_existing<RE, C, F>(
79-
http_client: C,
80-
access_token: AccessToken,
81-
client_secret: ClientSecret,
82-
) -> Result<AppAccessToken, ValidationError<RE>>
83-
where
84-
RE: std::error::Error + Send + Sync + 'static,
85-
C: FnOnce(HttpRequest) -> F,
86-
F: Future<Output = Result<HttpResponse, RE>>,
87-
{
88-
let token = access_token;
89-
let validated = crate::validate_token(http_client, &token).await?;
90-
Ok(Self::from_existing_unchecked(
91-
token,
92-
validated.client_id,
93-
client_secret,
94-
None,
95-
validated.scopes,
96-
))
97-
}
98-
99-
/// Generate app access token via [OAuth client credentials flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow)
100-
pub async fn get_app_access_token<RE, C, F>(
101-
http_client: C,
102-
client_id: ClientId,
103-
client_secret: ClientSecret,
104-
scopes: Vec<Scope>,
105-
) -> Result<AppAccessToken, TokenError<RE>>
106-
where
107-
RE: std::error::Error + Send + Sync + 'static,
108-
C: Fn(HttpRequest) -> F,
109-
F: Future<Output = Result<HttpResponse, RE>>,
110-
{
111-
let now = std::time::Instant::now();
112-
let client = TwitchClient::new(
113-
client_id.clone(),
114-
Some(client_secret.clone()),
115-
AuthUrl::new("https://id.twitch.tv/oauth2/authorize".to_owned())
116-
.expect("unexpected failure to parse auth url for app_access_token"),
117-
Some(oauth2::TokenUrl::new(
118-
"https://id.twitch.tv/oauth2/token".to_string(),
119-
)?),
120-
);
121-
let client = client.set_auth_type(oauth2::AuthType::RequestBody);
122-
let mut client = client.exchange_client_credentials();
123-
for scope in scopes {
124-
client = client.add_scope(scope.as_oauth_scope());
125-
}
126-
let response = client
127-
.request_async(&http_client)
128-
.await
129-
.map_err(TokenError::Request)?;
130-
131-
let app_access = AppAccessToken {
132-
access_token: response.access_token().clone(),
133-
refresh_token: response.refresh_token().cloned(),
134-
expires: response.expires_in().map(|dur| now + dur),
135-
client_id,
136-
client_secret,
137-
login: None,
138-
scopes: response
139-
.scopes()
140-
.cloned()
141-
.map(|s| s.into_iter().map(|s| s.into()).collect()),
142-
};
143-
144-
let _ = app_access.validate_token(http_client).await?; // Sanity check
145-
Ok(app_access)
146-
}
147-
}
1+
use super::errors::{TokenError, ValidationError};
2+
use crate::{
3+
id::TwitchClient,
4+
tokens::{errors::RefreshTokenError, Scope, TwitchToken},
5+
};
6+
use oauth2::{AccessToken, AuthUrl, ClientId, ClientSecret, RefreshToken, TokenResponse};
7+
use oauth2::{HttpRequest, HttpResponse};
8+
use std::future::Future;
9+
10+
/// An App Access Token from the [OAuth client credentials flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow)
11+
#[derive(Debug, Clone)]
12+
pub struct AppAccessToken {
13+
access_token: AccessToken,
14+
refresh_token: Option<RefreshToken>,
15+
expires: Option<std::time::Instant>,
16+
client_id: ClientId,
17+
client_secret: ClientSecret,
18+
login: Option<String>,
19+
scopes: Option<Vec<Scope>>,
20+
}
21+
22+
#[async_trait::async_trait(?Send)]
23+
impl TwitchToken for AppAccessToken {
24+
fn client_id(&self) -> &ClientId { &self.client_id }
25+
26+
fn token(&self) -> &AccessToken { &self.access_token }
27+
28+
fn login(&self) -> Option<&str> { self.login.as_deref() }
29+
30+
async fn refresh_token<RE, C, F>(
31+
&mut self,
32+
http_client: C,
33+
) -> Result<(), RefreshTokenError<RE>>
34+
where
35+
RE: std::error::Error + Send + Sync + 'static,
36+
C: FnOnce(HttpRequest) -> F,
37+
F: Future<Output = Result<HttpResponse, RE>>,
38+
{
39+
let (access_token, expires, refresh_token) = if let Some(token) = self.refresh_token.take()
40+
{
41+
crate::refresh_token(http_client, token, &self.client_id, &self.client_secret).await?
42+
} else {
43+
return Err(RefreshTokenError::NoRefreshToken);
44+
};
45+
self.access_token = access_token;
46+
self.expires = expires;
47+
self.refresh_token = refresh_token;
48+
Ok(())
49+
}
50+
51+
fn expires(&self) -> Option<std::time::Instant> { self.expires }
52+
53+
fn scopes(&self) -> Option<&[Scope]> { self.scopes.as_deref() }
54+
}
55+
56+
impl AppAccessToken {
57+
/// Assemble token without checks.
58+
pub fn from_existing_unchecked(
59+
access_token: AccessToken,
60+
client_id: impl Into<ClientId>,
61+
client_secret: impl Into<ClientSecret>,
62+
login: Option<String>,
63+
scopes: Option<Vec<Scope>>,
64+
) -> AppAccessToken {
65+
AppAccessToken {
66+
access_token,
67+
refresh_token: None,
68+
client_id: client_id.into(),
69+
client_secret: client_secret.into(),
70+
login,
71+
expires: None,
72+
scopes,
73+
}
74+
}
75+
76+
/// Assemble token and validate it. Retrieves [`client_id`](TwitchToken::client_id) and [`scopes`](TwitchToken::scopes).
77+
pub async fn from_existing<RE, C, F>(
78+
http_client: C,
79+
access_token: AccessToken,
80+
client_secret: ClientSecret,
81+
) -> Result<AppAccessToken, ValidationError<RE>>
82+
where
83+
RE: std::error::Error + Send + Sync + 'static,
84+
C: FnOnce(HttpRequest) -> F,
85+
F: Future<Output = Result<HttpResponse, RE>>,
86+
{
87+
let token = access_token;
88+
let validated = crate::validate_token(http_client, &token).await?;
89+
Ok(Self::from_existing_unchecked(
90+
token,
91+
validated.client_id,
92+
client_secret,
93+
None,
94+
validated.scopes,
95+
))
96+
}
97+
98+
/// Generate app access token via [OAuth client credentials flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow)
99+
pub async fn get_app_access_token<RE, C, F>(
100+
http_client: C,
101+
client_id: ClientId,
102+
client_secret: ClientSecret,
103+
scopes: Vec<Scope>,
104+
) -> Result<AppAccessToken, TokenError<RE>>
105+
where
106+
RE: std::error::Error + Send + Sync + 'static,
107+
C: Fn(HttpRequest) -> F,
108+
F: Future<Output = Result<HttpResponse, RE>>,
109+
{
110+
let now = std::time::Instant::now();
111+
let client = TwitchClient::new(
112+
client_id.clone(),
113+
Some(client_secret.clone()),
114+
AuthUrl::new("https://id.twitch.tv/oauth2/authorize".to_owned())
115+
.expect("unexpected failure to parse auth url for app_access_token"),
116+
Some(oauth2::TokenUrl::new(
117+
"https://id.twitch.tv/oauth2/token".to_string(),
118+
)?),
119+
);
120+
let client = client.set_auth_type(oauth2::AuthType::RequestBody);
121+
let mut client = client.exchange_client_credentials();
122+
for scope in scopes {
123+
client = client.add_scope(scope.as_oauth_scope());
124+
}
125+
let response = client
126+
.request_async(&http_client)
127+
.await
128+
.map_err(TokenError::Request)?;
129+
130+
let app_access = AppAccessToken {
131+
access_token: response.access_token().clone(),
132+
refresh_token: response.refresh_token().cloned(),
133+
expires: response.expires_in().map(|dur| now + dur),
134+
client_id,
135+
client_secret,
136+
login: None,
137+
scopes: response
138+
.scopes()
139+
.cloned()
140+
.map(|s| s.into_iter().map(|s| s.into()).collect()),
141+
};
142+
143+
let _ = app_access.validate_token(http_client).await?; // Sanity check
144+
Ok(app_access)
145+
}
146+
}

0 commit comments

Comments
 (0)