From d370d13f2d68ef5aa2d7999ba35a680aa93ccd99 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 16 Nov 2021 20:00:16 -0500 Subject: [PATCH] OAuth flow compiles, but has issues --- .../obs_gateway/src/endpoints/oauth_begin.rs | 18 ++--- .../src/endpoints/oauth_begin_redirect.rs | 18 ++--- .../obs_gateway/src/endpoints/oauth_end.rs | 80 +++++++++++++++++++ crates/app/obs_gateway/src/twitch/mod.rs | 1 + .../src/twitch/oauth_token_builder.rs | 25 ++++++ 5 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 crates/app/obs_gateway/src/twitch/oauth_token_builder.rs diff --git a/crates/app/obs_gateway/src/endpoints/oauth_begin.rs b/crates/app/obs_gateway/src/endpoints/oauth_begin.rs index a4c3c8a715..ac163d0325 100644 --- a/crates/app/obs_gateway/src/endpoints/oauth_begin.rs +++ b/crates/app/obs_gateway/src/endpoints/oauth_begin.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use twitch_api2::twitch_oauth2::ClientId; use twitch_oauth2::tokens::UserTokenBuilder; use twitch_oauth2::{Scope, ClientSecret}; +use crate::twitch::oauth_token_builder::get_oauth_token_builder; #[derive(Serialize)] pub struct OauthBeginEnrollResult { @@ -57,18 +58,11 @@ pub async fn oauth_begin_enroll( OauthBeginEnrollError::ServerError })?; - let client_id = ClientId::new(&server_state.twitch_oauth_secrets.client_id); - let client_secret = ClientSecret::new(&server_state.twitch_oauth_secrets.client_secret); - - // TODO: Not all scopes are needed! - let mut builder = UserTokenBuilder::new(client_id, client_secret, redirect_url) - .set_scopes(vec![ - Scope::BitsRead, - Scope::ChannelReadSubscriptions, - Scope::ChatEdit, - Scope::ChatRead, - ]) - .force_verify(true); + let mut builder = get_oauth_token_builder( + &server_state.twitch_oauth_secrets.client_id, + &server_state.twitch_oauth_secrets.client_secret, + &redirect_url, + true); let (url, _csrf_token) = builder.generate_url(); diff --git a/crates/app/obs_gateway/src/endpoints/oauth_begin_redirect.rs b/crates/app/obs_gateway/src/endpoints/oauth_begin_redirect.rs index 6db0746597..9001d43f83 100644 --- a/crates/app/obs_gateway/src/endpoints/oauth_begin_redirect.rs +++ b/crates/app/obs_gateway/src/endpoints/oauth_begin_redirect.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use twitch_api2::twitch_oauth2::ClientId; use twitch_oauth2::tokens::UserTokenBuilder; use twitch_oauth2::{Scope, ClientSecret}; +use crate::twitch::oauth_token_builder::get_oauth_token_builder; #[derive(Serialize)] pub struct OauthBeginEnrollResult { @@ -57,18 +58,11 @@ pub async fn oauth_begin_enroll_redirect( OauthBeginEnrollError::ServerError })?; - let client_id = ClientId::new(&server_state.twitch_oauth_secrets.client_id); - let client_secret = ClientSecret::new(&server_state.twitch_oauth_secrets.client_secret); - - // TODO: Not all scopes are needed! - let mut builder = UserTokenBuilder::new(client_id, client_secret, redirect_url) - .set_scopes(vec![ - Scope::BitsRead, - Scope::ChannelReadSubscriptions, - Scope::ChatEdit, - Scope::ChatRead, - ]) - .force_verify(true); + let mut builder = get_oauth_token_builder( + &server_state.twitch_oauth_secrets.client_id, + &server_state.twitch_oauth_secrets.client_secret, + &redirect_url, + true); let (url, _csrf_token) = builder.generate_url(); diff --git a/crates/app/obs_gateway/src/endpoints/oauth_end.rs b/crates/app/obs_gateway/src/endpoints/oauth_end.rs index 84295ad078..d0592e8217 100644 --- a/crates/app/obs_gateway/src/endpoints/oauth_end.rs +++ b/crates/app/obs_gateway/src/endpoints/oauth_end.rs @@ -1,13 +1,34 @@ use actix_http::StatusCode; use actix_web::{HttpResponse, HttpRequest, web, ResponseError}; use crate::server_state::ObsGatewayServerState; +use crate::twitch::oauth_token_builder::get_oauth_token_builder; use http_server_common::response::response_error_helpers::to_simple_json_error; +use log::error; +use log::info; +use log::warn; use std::fmt; use std::sync::Arc; +use twitch_oauth2::tokens::BearerTokenType::UserToken; + +#[derive(Deserialize)] +pub struct QueryParams { + /// OAuth authorization code + pub code: Option, + /// Opaque value used to avoid CSRF attacks. + pub state: Option, + /// List of permission scopes + pub scope: Option, + + /// Only set in the event of an error + pub error: Option, + /// Only set in the event of an error + pub error_description: Option, +} #[derive(Debug)] pub enum OauthEndEnrollFromRedirectError { ServerError, + TwitchOauthError { reason: String }, } // NB: Not using DeriveMore since Clion doesn't understand it. @@ -21,12 +42,14 @@ impl ResponseError for OauthEndEnrollFromRedirectError { fn status_code(&self) -> StatusCode { match *self { Self::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + OauthEndEnrollFromRedirectError::TwitchOauthError { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } fn error_response(&self) -> HttpResponse { let error_reason = match self { Self::ServerError => "server error".to_string(), + OauthEndEnrollFromRedirectError::TwitchOauthError { .. } => "twitch error".to_string(), }; to_simple_json_error(&error_reason, self.status_code()) @@ -35,8 +58,65 @@ impl ResponseError for OauthEndEnrollFromRedirectError { pub async fn oauth_end_enroll_from_redirect( http_request: HttpRequest, + query: web::Query, server_state: web::Data> ) -> Result { + + if let Some(error) = query.error.as_deref() { + return Err(OauthEndEnrollFromRedirectError::TwitchOauthError { reason: error.to_string() }) + } + if let Some(error) = query.error_description.as_deref() { + return Err(OauthEndEnrollFromRedirectError::TwitchOauthError { reason: error.to_string() }) + } + + let (code, state) = match (query.code.as_deref(), query.state.as_deref()) { + (Some(code), Some(state)) => (code.to_string(), state.to_string()), + _ => { + return Err(OauthEndEnrollFromRedirectError::TwitchOauthError { + reason: "params not set".to_string() + }); + } + }; + + let redirect_url = + twitch_oauth2::url::Url::parse(&server_state.twitch_oauth_secrets.redirect_url) + .map_err(|e| { + warn!("Error parsing url: {:?}", e); + OauthEndEnrollFromRedirectError::ServerError + })?; + + let mut builder = get_oauth_token_builder( + &server_state.twitch_oauth_secrets.client_id, + &server_state.twitch_oauth_secrets.client_secret, + &redirect_url, + true); + + let http_client = &reqwest::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .map_err(|e| { + error!("Problem creating HTTP client: {:?}", e); + OauthEndEnrollFromRedirectError::ServerError + })?; + + let user_token = builder + .get_user_token( + http_client, + &state, + &code, + ) + .await + .map_err(|e| { + warn!("Error fetching user token: {:?}", e); + OauthEndEnrollFromRedirectError::TwitchOauthError { + reason: "error fetching user token".to_string() + } + })?; + + let auth_token = user_token.access_token.as_str(); + + info!("Auth token: {:?}", auth_token); + Ok(HttpResponse::Ok() .body("TODO")) } diff --git a/crates/app/obs_gateway/src/twitch/mod.rs b/crates/app/obs_gateway/src/twitch/mod.rs index a7c619f5db..422b2a3dd0 100644 --- a/crates/app/obs_gateway/src/twitch/mod.rs +++ b/crates/app/obs_gateway/src/twitch/mod.rs @@ -1,3 +1,4 @@ +pub mod oauth_token_builder; pub mod twitch_client_wrapper; pub mod twitch_secrets; pub mod websocket_client; \ No newline at end of file diff --git a/crates/app/obs_gateway/src/twitch/oauth_token_builder.rs b/crates/app/obs_gateway/src/twitch/oauth_token_builder.rs new file mode 100644 index 0000000000..3e11fd6099 --- /dev/null +++ b/crates/app/obs_gateway/src/twitch/oauth_token_builder.rs @@ -0,0 +1,25 @@ +use twitch_oauth2::tokens::UserTokenBuilder; +use twitch_oauth2::{Scope, ClientSecret, ClientId}; +use twitch_oauth2::url::Url; + +/// Get the token builder with the scopes our app needs +/// This is used in several places, so centralize creation here. +pub fn get_oauth_token_builder( + client_id: &str, + client_secret: &str, + redirect_url: &Url, + force_verify: bool +) -> UserTokenBuilder { + + let client_id = ClientId::new(client_id); + let client_secret = ClientSecret::new(client_secret); + + UserTokenBuilder::new(client_id, client_secret, redirect_url.clone()) + .set_scopes(vec![ + Scope::BitsRead, + Scope::ChannelReadSubscriptions, + Scope::ChatEdit, + Scope::ChatRead, + ]) + .force_verify(force_verify) +} \ No newline at end of file