diff --git a/_migrations/2022-01-11-042235_twitch_oauth_tokens/up.sql b/_migrations/2022-01-11-042235_twitch_oauth_tokens/up.sql index 0bf8aa6f48..6c07fbb901 100644 --- a/_migrations/2022-01-11-042235_twitch_oauth_tokens/up.sql +++ b/_migrations/2022-01-11-042235_twitch_oauth_tokens/up.sql @@ -55,30 +55,163 @@ CREATE TABLE twitch_oauth_tokens( -- subsequent time will bump by one. Mostly for debugging. refresh_count INT(10) UNSIGNED DEFAULT 0, - -- ========== OAUTH SCOPES ========== + -- ====================================================== + -- ==================== OAUTH SCOPES ==================== + -- ====================================================== + + -- TODO: Probably could have stored this in a bitfield. Ugh. + + -- "analytics:read:extensions" + -- "View analytics data for the Twitch Extensions owned by the authenticated account." + has_analytics_read_extensions BOOLEAN NOT NULL DEFAULT FALSE, + + -- scope: "analytics:read:games" + -- "View analytics data for the games owned by the authenticated account." + has_analytics_read_games BOOLEAN NOT NULL DEFAULT FALSE, -- "bits:read" -- "View Bits information for a channel." has_bits_read BOOLEAN NOT NULL DEFAULT FALSE, + -- "channel:edit:commercial" + -- "Run commercials on a channel." + has_channel_edit_commercial BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:manage:broadcast" + -- "Manage a channel's broadcast configuration, including updating channel configuration and managing stream markers and stream tags." + has_channel_manage_broadcast BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:manage:extensions" + -- "Manage a channel's Extension configuration, including activating Extensions." + has_channel_manage_extensions BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:manage:polls" + -- "Manage a channel's polls." + has_channel_manage_polls BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:manage:predictions" + -- "Manage of channel's Channel Points Predictions" + has_channel_manage_predictions BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:manage:redemptions" + -- "Manage Channel Points custom rewards and their redemptions on a channel." + has_channel_manage_redemptions BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:manage:schedule" + -- "Manage a channel's stream schedule." + has_channel_manage_schedule BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:manage:videos" + -- "Manage a channel's videos, including deleting videos." + has_channel_manage_videos BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:moderate" + -- "Perform moderation actions in a channel. The user requesting the scope must be a moderator in the channel." + has_channel_moderate BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:read:editors" + -- "View a list of users with the editor role for a channel." + has_channel_read_editors BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:read:goals" + -- "View Creator Goals for a channel." + has_channel_read_goals BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:read:hype_train" + -- "View Hype Train information for a channel." + has_channel_read_hype_train BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:read:polls" + -- "View a channel's polls." + has_channel_read_polls BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:read:predictions" + -- "View a channel's Channel Points Predictions." + has_channel_read_predictions BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:read:redemptions" + -- "View Channel Points custom rewards and their redemptions on a channel." + -- eg. PubSub subscribe to `channel-points-channel-v1.` (channel points spends) + has_channel_read_redemptions BOOLEAN NOT NULL DEFAULT FALSE, + + -- "channel:read:stream_key" + -- "View an authorized user's stream key." + has_channel_read_stream_key BOOLEAN NOT NULL DEFAULT FALSE, + -- "channel:read:subscriptions" -- "View a list of all subscribers to a channel and check if a user is subscribed to a channel." -- eg. Enumerate list of subscribers for a channel -- eg. PubSub subscribe to `channel-subscribe-events-v1.` (subscribe, resubscribe, gift) has_channel_read_subscriptions BOOLEAN NOT NULL DEFAULT FALSE, - -- "channel:read:redemptions" - -- "View Channel Points custom rewards and their redemptions on a channel." - -- eg. PubSub subscribe to `channel-points-channel-v1.` (channel points spends) - has_channel_read_redemptions BOOLEAN NOT NULL DEFAULT FALSE, + -- "channel_subscriptions" + -- "\\[DEPRECATED\\] Read all subscribers to your channel." + has_channel_subscriptions BOOLEAN NOT NULL DEFAULT FALSE, + + -- "chat:edit" + -- "Send live stream chat and rooms messages." + has_chat_edit BOOLEAN NOT NULL DEFAULT FALSE, + + -- "chat:read" + -- "View live stream chat and rooms messages." + has_chat_read BOOLEAN NOT NULL DEFAULT FALSE, + + -- "clips:edit" + -- "Manage Clips for a channel." + has_clips_edit BOOLEAN NOT NULL DEFAULT FALSE, + + -- "moderation:read" + -- "View a channel's moderation data including Moderators, Bans, Timeouts, and Automod settings." + has_moderation_read BOOLEAN NOT NULL DEFAULT FALSE, + + -- "moderator:manage:automod" + -- "Manage messages held for review by AutoMod in channels where you are a moderator." + has_moderator_manage_auto_mod BOOLEAN NOT NULL DEFAULT FALSE, + + -- "user:edit" + -- "Manage a user object." + has_user_edit BOOLEAN NOT NULL DEFAULT FALSE, + + -- "user:edit:broadcast" + -- "Edit your channel's broadcast configuration, including extension configuration. (This scope implies user:read:broadcast capability.)" + has_user_edit_broadcast BOOLEAN NOT NULL DEFAULT FALSE, + + -- "user:edit:follows" + -- "Edit a user's follows." + has_user_edit_follows BOOLEAN NOT NULL DEFAULT FALSE, + + -- "user:manage:blocked_users" + -- "Manage the block list of a user." + has_user_manage_blocked_users BOOLEAN NOT NULL DEFAULT FALSE, + + -- "user:read:blocked_users" + -- "View the block list of a user." + has_user_read_blocked_users BOOLEAN NOT NULL DEFAULT FALSE, + + -- "user:read:broadcast" + -- "View a user's broadcasting configuration, including Extension configurations." + has_user_read_broadcast BOOLEAN NOT NULL DEFAULT FALSE, + + -- "user:read:email" + -- "Read an authorized user's email address." + has_user_read_email BOOLEAN NOT NULL DEFAULT FALSE, -- "user:read:follows" -- "View the list of channels a user follows." -- eg. We can use this to see if a Twitch user follows us. has_user_read_follows BOOLEAN NOT NULL DEFAULT FALSE, + -- "user:read:subscriptions" + -- "View if an authorized user is subscribed to specific channels." + has_user_read_subscriptions BOOLEAN NOT NULL DEFAULT FALSE, - -- THERE ARE MORE SCOPES... PERHAPS WE SHOULD ADD THEM? + -- "whispers:edit" + -- "Send whisper messages." + has_whispers_edit BOOLEAN NOT NULL DEFAULT FALSE, + + -- "whispers:read" + -- "View your whisper messages." + has_whispers_read BOOLEAN NOT NULL DEFAULT FALSE, -- ========== SECURITY ========== diff --git a/crates/lib/database_queries/src/twitch_oauth/insert.rs b/crates/lib/database_queries/src/twitch_oauth/insert.rs index 68bd187b36..914060b522 100644 --- a/crates/lib/database_queries/src/twitch_oauth/insert.rs +++ b/crates/lib/database_queries/src/twitch_oauth/insert.rs @@ -38,10 +38,13 @@ pub struct TwitchOauthTokenInsertBuilder { /// Number of times we've refreshed the token refresh_count: u32, + // ===== OAuth Scopes ===== + has_bits_read: bool, - has_channel_read_subscriptions: bool, has_channel_read_redemptions: bool, - has_user_read_follows: bool, + has_channel_read_subscriptions: bool, + has_chat_edit: bool, + has_chat_read: bool, } impl TwitchOauthTokenInsertBuilder { @@ -58,7 +61,8 @@ impl TwitchOauthTokenInsertBuilder { has_bits_read: false, has_channel_read_subscriptions: false, has_channel_read_redemptions: false, - has_user_read_follows: false, + has_chat_edit: false, + has_chat_read: false, refresh_count: 0, } } @@ -93,23 +97,28 @@ impl TwitchOauthTokenInsertBuilder { self } - pub fn set_has_bits_read(mut self, has_bits_read: bool) -> Self { - self.has_bits_read = has_bits_read; + pub fn has_bits_read(mut self, value: bool) -> Self { + self.has_bits_read = value; self } - pub fn has_channel_read_subscriptions(mut self, channel_read_subscriptions: bool) -> Self { - self.has_channel_read_subscriptions = channel_read_subscriptions; + pub fn has_channel_read_redemptions(mut self, value: bool) -> Self { + self.has_channel_read_redemptions = value; self } - pub fn has_channel_read_redemptions(mut self, channel_read_redemptions: bool) -> Self { - self.has_channel_read_redemptions = channel_read_redemptions; + pub fn has_channel_read_subscriptions(mut self, value: bool) -> Self { + self.has_channel_read_subscriptions = value; self } - pub fn has_user_read_follows(mut self, has_user_read_follows: bool) -> Self { - self.has_user_read_follows = has_user_read_follows; + pub fn has_chat_edit(mut self, value: bool) -> Self { + self.has_chat_edit = value; + self + } + + pub fn has_chat_read(mut self, value: bool) -> Self { + self.has_chat_read = value; self } @@ -131,9 +140,10 @@ SET token_type = ?, expires_in_seconds = ?, has_bits_read = ?, - has_channel_read_subscriptions = ?, has_channel_read_redemptions= ?, - has_user_read_follows = ?, + has_channel_read_subscriptions = ?, + has_chat_edit = ?, + has_chat_read = ?, ip_address_creation = ?, expires_at = DATE_ADD(NOW(), INTERVAL ? SECOND) "#, @@ -147,9 +157,10 @@ SET self.token_type.clone(), expires_in_seconds, self.has_bits_read.clone(), - self.has_channel_read_subscriptions.clone(), self.has_channel_read_redemptions.clone(), - self.has_user_read_follows.clone(), + self.has_channel_read_subscriptions.clone(), + self.has_chat_edit.clone(), + self.has_chat_read.clone(), self.maybe_ip_address_creation.clone(), expires_in_seconds, ) @@ -168,9 +179,10 @@ SET token_type = ?, expires_in_seconds = NULL, has_bits_read = ?, - has_channel_read_subscriptions = ?, has_channel_read_redemptions= ?, - has_user_read_follows = ?, + has_channel_read_subscriptions = ?, + has_chat_edit = ?, + has_chat_read = ?, ip_address_creation = ?, expires_at = NULL "#, @@ -183,9 +195,10 @@ SET self.refresh_count, self.token_type.clone(), self.has_bits_read.clone(), - self.has_channel_read_subscriptions.clone(), self.has_channel_read_redemptions.clone(), - self.has_user_read_follows.clone(), + self.has_channel_read_subscriptions.clone(), + self.has_chat_edit.clone(), + self.has_chat_read.clone(), self.maybe_ip_address_creation.clone(), ) }; diff --git a/crates/lib/twitch_common/src/oauth_token_builder.rs b/crates/lib/twitch_common/src/oauth_token_builder.rs index 0f135aed74..49588c7ed2 100644 --- a/crates/lib/twitch_common/src/oauth_token_builder.rs +++ b/crates/lib/twitch_common/src/oauth_token_builder.rs @@ -14,13 +14,53 @@ pub fn get_oauth_token_builder( let client_id = ClientId::new(client_id); let client_secret = ClientSecret::new(client_secret); + /* + - AnalyticsReadExtensions, scope: "analytics:read:extensions", doc: "View analytics data for the Twitch Extensions owned by the authenticated account."; + - AnalyticsReadGames, scope: "analytics:read:games", doc: "View analytics data for the games owned by the authenticated account."; + [YES] BitsRead, scope: "bits:read", doc: "View Bits information for a channel."; + - ChannelEditCommercial, scope: "channel:edit:commercial", doc: "Run commercials on a channel."; + - ChannelManageBroadcast, scope: "channel:manage:broadcast", doc: "Manage a channel’s broadcast configuration, including updating channel configuration and managing stream markers and stream tags."; + - ChannelManageExtensions, scope: "channel:manage:extensions", doc: "Manage a channel’s Extension configuration, including activating Extensions."; + (?) ChannelManagePolls, scope: "channel:manage:polls", doc: "Manage a channel’s polls."; + (?) ChannelManagePredictions, scope: "channel:manage:predictions", doc: "Manage of channel’s Channel Points Predictions"; + - ChannelManageRedemptions, scope: "channel:manage:redemptions", doc: "Manage Channel Points custom rewards and their redemptions on a channel."; + - ChannelManageSchedule, scope: "channel:manage:schedule", doc: "Manage a channel’s stream schedule."; + - ChannelManageVideos, scope: "channel:manage:videos", doc: "Manage a channel’s videos, including deleting videos."; + - ChannelModerate, scope: "channel:moderate", doc: "Perform moderation actions in a channel. The user requesting the scope must be a moderator in the channel."; + - ChannelReadEditors, scope: "channel:read:editors", doc: "View a list of users with the editor role for a channel."; + - ChannelReadGoals, scope: "channel:read:goals", doc: "View Creator Goals for a channel."; + - ChannelReadHypeTrain, scope: "channel:read:hype_train", doc: "View Hype Train information for a channel."; + (?) ChannelReadPolls, scope: "channel:read:polls", doc: "View a channel’s polls."; + (?) ChannelReadPredictions, scope: "channel:read:predictions", doc: "View a channel’s Channel Points Predictions."; + + ChannelReadRedemptions, scope: "channel:read:redemptions", doc: "View Channel Points custom rewards and their redemptions on a channel."; + - ChannelReadStreamKey, scope: "channel:read:stream_key", doc: "View an authorized user’s stream key."; + + ChannelReadSubscriptions, scope: "channel:read:subscriptions", doc: "View a list of all subscribers to a channel and check if a user is subscribed to a channel."; + - ChannelSubscriptions, scope: "channel_subscriptions", doc: "\\[DEPRECATED\\] Read all subscribers to your channel."; + [YES] ChatEdit, scope: "chat:edit", doc: "Send live stream chat and rooms messages."; + [YES] ChatRead, scope: "chat:read", doc: "View live stream chat and rooms messages."; + (?) ClipsEdit, scope: "clips:edit", doc: "Manage Clips for a channel."; + (?) ModerationRead, scope: "moderation:read", doc: "View a channel’s moderation data including Moderators, Bans, Timeouts, and Automod settings."; + (?) ModeratorManageAutoMod, scope: "moderator:manage:automod", doc: "Manage messages held for review by AutoMod in channels where you are a moderator."; + - UserEdit, scope: "user:edit", doc: "Manage a user object."; + - UserEditBroadcast, scope: "user:edit:broadcast", doc: "Edit your channel's broadcast configuration, including extension configuration. (This scope implies user:read:broadcast capability.)"; + - UserEditFollows, scope: "user:edit:follows", doc: "Edit a user’s follows."; + - UserManageBlockedUsers, scope: "user:manage:blocked_users", doc: "Manage the block list of a user."; + - UserReadBlockedUsers, scope: "user:read:blocked_users", doc: "View the block list of a user."; + - UserReadBroadcast, scope: "user:read:broadcast", doc: "View a user’s broadcasting configuration, including Extension configurations."; + - UserReadEmail, scope: "user:read:email", doc: "Read an authorized user’s email address."; + - UserReadFollows, scope: "user:read:follows", doc: "View the list of channels a user follows."; + (?) UserReadSubscriptions, scope: "user:read:subscriptions", doc: "View if an authorized user is subscribed to specific channels."; + - WhispersEdit, scope: "whispers:edit", doc: "Send whisper messages."; + - WhispersRead, scope: "whispers:read", doc: "View your whisper messages."; + */ + UserTokenBuilder::new(client_id, client_secret, redirect_url.clone()) .set_scopes(vec![ Scope::BitsRead, // Using bits to cheer (cost money, larger streams) Scope::ChannelReadRedemptions, // Channel points redeemed (free points, smaller streams) - Scope::ChannelReadSubscriptions, - Scope::ChatEdit, - Scope::ChatRead, + Scope::ChannelReadSubscriptions, // List subscribing (paying) users + Scope::ChatEdit, // IRC send + Scope::ChatRead, // IRC read ]) .force_verify(force_verify) } diff --git a/crates/service/obs_gateway/src/main.rs b/crates/service/obs_gateway/src/main.rs index 0af7b216ff..06fb86b935 100644 --- a/crates/service/obs_gateway/src/main.rs +++ b/crates/service/obs_gateway/src/main.rs @@ -86,10 +86,6 @@ async fn main() -> AnyhowResult<()> { let website_homepage_redirect = easyenv::get_env_string_or_default("WEBSITE_HOMEPAGE_REDIRECT", "https://vo.codes/"); - let oauth_redirect_url = easyenv::get_env_string_or_default( - "TWITCH_OAUTH_REDIRECT_URL", - "http://localhost:54321/twitch/oauth_redirect"); - let db_connection_string = easyenv::get_env_string_or_default( "MYSQL_URL", @@ -126,7 +122,6 @@ async fn main() -> AnyhowResult<()> { twitch_oauth_secrets: TwitchOauthSecrets { client_id: twitch_secrets.app_client_id.clone(), client_secret: twitch_secrets.app_client_secret.clone(), - redirect_url: oauth_redirect_url, }, hostname: server_hostname, backends: BackendsConfig { diff --git a/crates/service/obs_gateway/src/server_state.rs b/crates/service/obs_gateway/src/server_state.rs index 3fd23142c3..230ebdcbec 100644 --- a/crates/service/obs_gateway/src/server_state.rs +++ b/crates/service/obs_gateway/src/server_state.rs @@ -38,5 +38,4 @@ pub struct EnvConfig { pub struct TwitchOauthSecrets { pub client_id: String, pub client_secret: String, - pub redirect_url: String, } diff --git a/crates/service/storyteller_web/src/http_server/endpoints/twitch/mod.rs b/crates/service/storyteller_web/src/http_server/endpoints/twitch/mod.rs index 34e1b0b58b..0b47d3ca95 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/twitch/mod.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/twitch/mod.rs @@ -1,3 +1,3 @@ -pub mod oauth_begin; +pub mod oauth_begin_json; pub mod oauth_begin_redirect; pub mod oauth_end; diff --git a/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_begin.rs b/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_begin_json.rs similarity index 98% rename from crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_begin.rs rename to crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_begin_json.rs index 61f4d1d0cc..cb4eefaa69 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_begin.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_begin_json.rs @@ -46,7 +46,7 @@ impl ResponseError for OauthBeginEnrollError { } } -pub async fn oauth_begin_enroll( +pub async fn oauth_begin_enroll_json( http_request: HttpRequest, server_state: web::Data> ) -> Result { diff --git a/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_end.rs b/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_end.rs index e34b50c30d..d6d5ac2f36 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_end.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/twitch/oauth_end.rs @@ -144,10 +144,12 @@ pub async fn oauth_end_enroll_from_redirect( TwitchOauthTokenInsertBuilder::new(&user_id, &twitch_username, &auth_token) .set_expires_in_seconds(Some(expires_seconds)) .set_refresh_token(refresh_token.as_deref()) - .set_ip_address_creation(Some(&ip_address)); - //.set_user_token() - //.set_token_type() - //.set_has_bits_read(true) ... + .set_ip_address_creation(Some(&ip_address)) + .has_bits_read(true) + .has_channel_read_redemptions(true) + .has_channel_read_subscriptions(true) + .has_chat_edit(true) + .has_chat_read(true); let result = insert_builder.insert(&server_state.mysql_pool) .await diff --git a/crates/service/storyteller_web/src/routes.rs b/crates/service/storyteller_web/src/routes.rs index c4a5d8c3ce..30eafb239c 100644 --- a/crates/service/storyteller_web/src/routes.rs +++ b/crates/service/storyteller_web/src/routes.rs @@ -43,7 +43,7 @@ use crate::http_server::endpoints::tts::get_tts_model_use_count::get_tts_model_u use crate::http_server::endpoints::tts::get_tts_result::get_tts_inference_result_handler; use crate::http_server::endpoints::tts::get_tts_upload_model_job_status::get_tts_upload_model_job_status_handler; use crate::http_server::endpoints::tts::list_tts_models::list_tts_models_handler; -use crate::http_server::endpoints::twitch::oauth_begin::oauth_begin_enroll; +use crate::http_server::endpoints::twitch::oauth_begin_json::oauth_begin_enroll_json; use crate::http_server::endpoints::twitch::oauth_begin_redirect::oauth_begin_enroll_redirect; use crate::http_server::endpoints::twitch::oauth_end::oauth_end_enroll_from_redirect; use crate::http_server::endpoints::users::create_account::create_account_handler; @@ -539,8 +539,8 @@ fn add_twitch_oauth_routes (app: App) -> App >, { app.service(web::scope("/twitch") - .service(web::resource("/oauth_enroll") - .route(web::get().to(oauth_begin_enroll)) + .service(web::resource("/oauth_enroll_json") + .route(web::get().to(oauth_begin_enroll_json)) .route(web::head().to(|| HttpResponse::Ok())) ) .service(web::resource("/oauth_enroll_redirect")