diff --git a/src/http/client.rs b/src/http/client.rs index 34b5ed6d224..4e58f00ca4b 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -2944,7 +2944,7 @@ impl Http { multipart: None, headers: None, method: LightMethod::Get, - route: Route::Oauth2ApplicationCurrent, + route: Route::OAuth2ApplicationCurrent, params: None, }) .await diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs index 1e863d7dfc7..706b1a671d4 100644 --- a/src/http/ratelimiting.rs +++ b/src/http/ratelimiting.rs @@ -228,7 +228,7 @@ impl Ratelimiter { timeout: Duration::from_secs_f64(retry_after), limit: 50, method: req.method, - path: req.route.path(), + path: req.route.clone().path(), global: true, }); sleep(Duration::from_secs_f64(retry_after)).await; @@ -322,7 +322,7 @@ impl Ratelimit { timeout: delay, limit: self.limit, method: req.method, - path: req.route.path(), + path: req.route.clone().path(), global: false, }); @@ -380,7 +380,7 @@ impl Ratelimit { timeout: Duration::from_secs_f64(retry_after), limit: self.limit, method: req.method, - path: req.route.path(), + path: req.route.clone().path(), global: false, }); diff --git a/src/http/routing.rs b/src/http/routing.rs index ab40f79729a..700e58378a2 100644 --- a/src/http/routing.rs +++ b/src/http/routing.rs @@ -1,5 +1,8 @@ use std::borrow::Cow; +use url::Url; + +use crate::model::application::{GrantType, Scope, TokenTypeHint}; use crate::model::id::*; /// Used to group requests together for ratelimiting. @@ -32,7 +35,7 @@ macro_rules! routes { $ratelimiting_kind:expr; )+ }) => { - #[derive(Clone, Copy, Debug)] + #[derive(Clone, Debug)] pub enum Route<$lt> { $( $name $({ $($field_name: $field_type),* })?, @@ -65,7 +68,7 @@ macro_rules! routes { #[must_use] pub fn ratelimiting_bucket(&self) -> RatelimitingBucket { #[allow(unused_variables)] - let ratelimiting_kind = match *self { + let ratelimiting_kind = match self { $( Self::$name $({ $($field_name),* })? => $ratelimiting_kind, )+ @@ -361,7 +364,7 @@ routes! ('a, { api!("/invites/{}", code), Some(RatelimitingKind::Path); - Oauth2ApplicationCurrent, + OAuth2ApplicationCurrent, api!("/oauth2/applications/@me"), None; @@ -496,4 +499,69 @@ routes! ('a, { StageInstance { channel_id: ChannelId }, api!("/stage-instances/{}", channel_id), Some(RatelimitingKind::Path); + + OAuth2Token { + state: Option<&'a str>, + grant_type: GrantType, + code: Option<&'a str>, + redirect_uri: Option<&'a Url>, + refresh_token: Option<&'a str>, + scope: Vec + }, + api!( + "/oauth2/token?{}grant_type={}{}{}{}{}", + if let Some(state) = state { + format!("state={state}&") + } else { + String::new() + }, + grant_type, + if let Some(code) = code { + format!("&code={code}") + } else { + String::new() + }, + if let Some(redirect_uri) = redirect_uri { + format!("&redirect_uri={redirect_uri}") + } else { + String::new() + }, + if let Some(refresh_token) = refresh_token { + format!("&refresh_token={refresh_token}") + } else { + String::new() + }, + if scope.is_empty() { + String::new() + } else { + format!( + "&scope={}", + scope + .into_iter() + .map(|scope| scope.to_string()) + .collect::>() + .join(" "), + ) + }, + ), + None; + + OAuth2TokenRevocation { + token: &'a str, + token_type_hint: Option + }, + api!( + "/oauth2/token/revoke?token={}{}", + token, + if let Some(token_type_hint) = token_type_hint { + format!("&token_type_hint={token_type_hint}") + } else { + String::new() + }, + ), + None; + + OAuth2AuthorizationCurrent, + api!("/oauth2/@me"), + None; }); diff --git a/src/model/application/oauth.rs b/src/model/application/oauth.rs index 084c9a1258e..24474f39446 100644 --- a/src/model/application/oauth.rs +++ b/src/model/application/oauth.rs @@ -111,3 +111,50 @@ impl fmt::Display for Scope { self.serialize(f) } } + +/// The type of OAuth2 grant. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GrantType { + /// Grants a token using a previously acquired authorization code. + /// + /// [Discord docs](https://discord.com/developers/docs/topics/oauth2#authorization-code-grant). + AuthorizationCode, + /// Grants a new token using a refresh token. + /// + /// [Discord docs](https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-refresh-token-exchange-example). + RefreshToken, + /// Grants the bot owner's token using client credentials. + /// + /// [Discord docs](https://discord.com/developers/docs/topics/oauth2#client-credentials-grant). + ClientCredentials, +} + +impl fmt::Display for GrantType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", match self { + GrantType::AuthorizationCode => "authorization_code", + GrantType::RefreshToken => "refresh_token", + GrantType::ClientCredentials => "client_credentials", + }) + } +} + +/// Hints a type of token. +/// +/// [Discord docs](https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-token-revocation-example). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TokenTypeHint { + /// Hints that a token is an access token. + AccessToken, + /// Hints that a token is a refresh token. + RefreshToken, +} + +impl fmt::Display for TokenTypeHint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", match self { + TokenTypeHint::AccessToken => "access_token", + TokenTypeHint::RefreshToken => "refresh_token", + }) + } +}