diff --git a/Cargo.lock b/Cargo.lock index 9be78428eb..3b50f46aba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1930,6 +1930,19 @@ dependencies = [ "tokio-tls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.0.1", + "hyper 0.14.14", + "native-tls", + "tokio 1.6.0", + "tokio-native-tls 0.3.0", +] + [[package]] name = "idna" version = "0.1.5" @@ -2347,9 +2360,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static", "libc", @@ -2383,7 +2396,7 @@ dependencies = [ "flate2", "futures 0.3.17", "hyper 0.13.10", - "hyper-tls", + "hyper-tls 0.4.3", "log", "rand 0.7.3", "serde", @@ -2546,11 +2559,12 @@ dependencies = [ "lru_time_cache", "magic-crypt", "md-5", - "newrelic-telemetry", + "native-tls", "pulldown-cmark", "r2d2_redis", "rand 0.8.4", "regex", + "reqwest 0.11.6", "ring", "rust-s3", "serde", @@ -2561,7 +2575,7 @@ dependencies = [ "subprocess", "tempdir", "time 0.2.26", - "tokio 0.2.25", + "tokio 1.6.0", "toml", "twitch_api2", "twitch_oauth2", @@ -3090,7 +3104,7 @@ dependencies = [ "http", "http-body 0.3.1", "hyper 0.13.10", - "hyper-tls", + "hyper-tls 0.4.3", "ipnet", "js-sys", "lazy_static", @@ -3126,17 +3140,20 @@ dependencies = [ "http", "http-body 0.4.4", "hyper 0.14.14", + "hyper-tls 0.5.0", "ipnet", "js-sys", "lazy_static", "log", "mime", + "native-tls", "percent-encoding 2.1.0", "pin-project-lite 0.2.6", "serde", "serde_json", "serde_urlencoded", "tokio 1.6.0", + "tokio-native-tls 0.3.0", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", @@ -3613,7 +3630,7 @@ dependencies = [ "native-tls", "once_cell", "tokio 0.2.25", - "tokio-native-tls", + "tokio-native-tls 0.1.0", ] [[package]] @@ -3951,7 +3968,7 @@ dependencies = [ "pin-project-lite 0.1.12", "signal-hook-registry", "slab", - "tokio-macros", + "tokio-macros 0.2.6", "winapi 0.3.9", ] @@ -3966,10 +3983,12 @@ dependencies = [ "libc", "memchr", "mio 0.7.11", + "num_cpus", "once_cell", "parking_lot 0.11.1", "pin-project-lite 0.2.6", "signal-hook-registry", + "tokio-macros 1.5.1", "winapi 0.3.9", ] @@ -4016,6 +4035,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-macros" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.1.0" @@ -4026,6 +4056,16 @@ dependencies = [ "tokio 0.2.25", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio 1.6.0", +] + [[package]] name = "tokio-reactor" version = "0.1.12" diff --git a/app/obs_gateway/Cargo.toml b/app/obs_gateway/Cargo.toml index 6a362ca5fa..3cdc80d250 100644 --- a/app/obs_gateway/Cargo.toml +++ b/app/obs_gateway/Cargo.toml @@ -34,7 +34,7 @@ lazy_static = "1.4.0" lfu = "0.2.5" log = "0.4.14" #newrelic = "0.2.2" -newrelic-telemetry = { path = "../../vendor/newrelic-telemetry-sdk-rust" } +#newrelic-telemetry = { path = "../../vendor/newrelic-telemetry-sdk-rust" } rand = "0.8.3" regex = "1.5.4" time = "0.2.26" @@ -46,7 +46,9 @@ uuid = { version = "0.8.2", features = ["v4"] } # NB(2): Really unfortunate. We can't use Tokio 1.x due to a bug in `rust-s3` that I filed: # https://github.com/durch/rust-s3/issues/198 #tokio = { version = "1.6.0", features = ["macros"] } -tokio = { version = "0.2.25", features = ["macros"] } +#tokio = { version = "0.2.25", features = ["macros"] } +# NB(3,2021-11-07): Actually, maybe we can do this in a complex multi-project monorepo +tokio = { version = "1.6.0", features = ["macros", "rt-multi-thread"] } # MySql Database # "offline" supports typesafe builds without a live database @@ -66,6 +68,11 @@ r2d2_redis = "0.14.0" twitch_api2 = { version = "0.6.0-rc.2", features = ["helix", "reqwest_client", "pubsub", "chrono", "unsupported"] } twitch_oauth2 = "0.6.0-rc.2" +# HTTP +reqwest = { version = "0.11.4" } + +native-tls = "0.2.8" + # In-memory caching lru_time_cache = "0.11.11" diff --git a/app/obs_gateway/src/main.rs b/app/obs_gateway/src/main.rs index 16bea80049..b802557a75 100644 --- a/app/obs_gateway/src/main.rs +++ b/app/obs_gateway/src/main.rs @@ -38,7 +38,9 @@ use sqlx::MySqlPool; use sqlx::mysql::MySqlPoolOptions; use std::sync::Arc; use std::time::Duration; +use twitch_oauth2::tokens::UserTokenBuilder; use twitch_oauth2::{AppAccessToken, Scope, TwitchToken, tokens::errors::AppAccessTokenError, ClientId, ClientSecret}; +use crate::twitch::twitch_client_wrapper::TwitchClientWrapper; const DEFAULT_BIND_ADDRESS : &'static str = "0.0.0.0:12345"; @@ -64,7 +66,8 @@ pub struct EnvConfig { pub website_homepage_redirect: String, } -#[actix_web::main] +//#[actix_web::main] +#[tokio::main] async fn main() -> AnyhowResult<()> { easyenv::init_all_with_default_logging(Some(DEFAULT_RUST_LOG)); @@ -87,6 +90,52 @@ async fn main() -> AnyhowResult<()> { let client_id = ClientId::new(&secrets.app_client_id); let client_secret = ClientSecret::new(&secrets.app_client_secret); + + // ======================================================== + + info!("Getting app access token..."); + + //let scopes = Scope::all(); + let scopes = vec![ + Scope::BitsRead, + Scope::UserReadEmail, + ]; + + let mut twitch_client = TwitchClientWrapper::new(client_id.clone(), client_secret.clone()); + twitch_client.request_access_token(scopes).await?; + + info!("Getting user id ..."); + + //let user_id = twitch_client.get_user_id_from_username("testytest512").await?; + let user_id = twitch_client.get_user_id_from_username("vocodes").await?; + + info!("User ID: {}", user_id); + + //std::thread::sleep(Duration::from_secs(5000)); + + + // ==================== OAUTH FLOW ==================== + + println!("Oauth flow..."); + + let redirect_url = twitch_oauth2::url::Url::parse("http://localhost/test")?; + let mut builder = UserTokenBuilder::new(client_id, client_secret, redirect_url) + .set_scopes(Scope::all()) + .force_verify(true); + + //builder.add_scope(Scope::BitsRead); + + let (url, _csrf_token) = builder.generate_url(); + + println!("Go to this page: {}", url); + + + + + + // ======================================================== + + info!("Connecting to database..."); let db_connection_string = diff --git a/app/obs_gateway/src/twitch/mod.rs b/app/obs_gateway/src/twitch/mod.rs index ba70f181c9..a021988480 100644 --- a/app/obs_gateway/src/twitch/mod.rs +++ b/app/obs_gateway/src/twitch/mod.rs @@ -1 +1,2 @@ +pub mod twitch_client_wrapper; pub mod twitch_secrets; \ No newline at end of file diff --git a/app/obs_gateway/src/twitch/twitch_client_wrapper.rs b/app/obs_gateway/src/twitch/twitch_client_wrapper.rs new file mode 100644 index 0000000000..881584e87b --- /dev/null +++ b/app/obs_gateway/src/twitch/twitch_client_wrapper.rs @@ -0,0 +1,65 @@ +//! The Helix client is independent of PubSub +//! This just wraps `twitch_api2` for more convenience. + +use anyhow::anyhow; +use container_common::anyhow_result::AnyhowResult; +use twitch_api2::TwitchClient; +use twitch_api2::helix::users::GetUsersRequest; +use twitch_api2::types::{Nickname, UserId}; +use twitch_oauth2::{ClientSecret, ClientId, AccessToken, AppAccessToken, Scope}; + +pub struct TwitchClientWrapper<'a> { + client_id: ClientId, + client_secret: ClientSecret, + twitch_client: TwitchClient<'a, reqwest::Client>, + access_token: Option, +} + +impl <'a> TwitchClientWrapper <'a> { + pub fn new(client_id: ClientId, client_secret: ClientSecret) -> Self { + Self { + client_id, + client_secret, + twitch_client: TwitchClient::default(), + access_token: None, + } + } + + pub async fn request_access_token(&mut self, scopes: Vec) -> AnyhowResult<()> { + self.get_access_token(scopes).await?; + Ok(()) + } + + async fn get_access_token(&mut self, scopes: Vec) -> AnyhowResult<()> { + let mut http_client = reqwest::Client::new(); + let access_token = twitch_oauth2::AppAccessToken::get_app_access_token( + &http_client, + self.client_id.clone(), + self.client_secret.clone(), + scopes, + ).await?; + + self.access_token = Some(access_token); + Ok(()) + } + + /// We frequently need to look up IDs from usernames. + pub async fn get_user_id_from_username(&self, username: &str) -> AnyhowResult { + let access_token = match self.access_token { + None => return Err(anyhow!("no access token has been granted")), + Some(ref token) => token, + }; + + let req = GetUsersRequest::builder() + .login(vec![Nickname::new(username)]) + .build(); + + let response = &self.twitch_client.helix.req_get(req, access_token) + .await?; + + match response.data.get(0) { + None => Err(anyhow!("No such user: `{}`.", username)), + Some(user) => Ok(user.id.as_str().parse::()?), + } + } +}