From 1596f6d4be176fc1660c406d7428e1f1d625b6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=E2=96=88=E2=96=88=E2=96=88=E2=96=88=E2=96=88?= Date: Wed, 23 Oct 2024 14:34:14 -0400 Subject: [PATCH] Moving twitch login code to our service The API was handling this for us because they already had all the logic anyway for getting the email. We've sufficiently moved things around enough that we can do it correctly by allowing the twitch bot service to handle its own logins. --- .../Twitch}/Support/TwitchBotLoginErrors.cs | 0 .../Twitch/TwitchApiProxy.cs | 21 ++--- .../Controllers/TwitchBotController.cs | 86 ------------------- .../Controllers/UserController.cs | 2 +- 4 files changed, 12 insertions(+), 97 deletions(-) rename src/{Nullinside.Api/Shared => Nullinside.Api.Common/Twitch}/Support/TwitchBotLoginErrors.cs (100%) delete mode 100644 src/Nullinside.Api/Controllers/TwitchBotController.cs diff --git a/src/Nullinside.Api/Shared/Support/TwitchBotLoginErrors.cs b/src/Nullinside.Api.Common/Twitch/Support/TwitchBotLoginErrors.cs similarity index 100% rename from src/Nullinside.Api/Shared/Support/TwitchBotLoginErrors.cs rename to src/Nullinside.Api.Common/Twitch/Support/TwitchBotLoginErrors.cs diff --git a/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs b/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs index e720d30..56a3ec4 100644 --- a/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs +++ b/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs @@ -14,6 +14,7 @@ using TwitchLib.Api.Helix.Models.Moderation.GetModerators; using TwitchLib.Api.Helix.Models.Streams.GetStreams; using TwitchLib.Api.Helix.Models.Users.GetUsers; +using TwitchLib.Api.Interfaces; using Stream = TwitchLib.Api.Helix.Models.Streams.GetStreams.Stream; @@ -73,7 +74,7 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires) /// public async Task CreateAccessToken(string code, CancellationToken token = new()) { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); AuthCodeResponse? response = await api.Auth.GetAccessTokenFromCodeAsync(code, ClientSecret, ClientRedirect); if (null == response) { return null; @@ -90,7 +91,7 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires) /// public async Task RefreshAccessToken(CancellationToken token = new()) { try { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); RefreshResponse? response = await api.Auth.RefreshAuthTokenAsync(OAuth?.RefreshToken, ClientSecret, ClientId); if (null == response) { return null; @@ -116,7 +117,7 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires) /// public async Task<(string? id, string? username)> GetUser(CancellationToken token = new()) { return await Retry.Execute(async () => { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); GetUsersResponse? response = await api.Helix.Users.GetUsersAsync(); if (null == response) { return (null, null); @@ -130,7 +131,7 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires) /// public async Task GetUserEmail(CancellationToken token = new()) { return await Retry.Execute(async () => { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); GetUsersResponse? response = await api.Helix.Users.GetUsersAsync(); if (null == response) { return null; @@ -175,7 +176,7 @@ public async Task> GetUserModChannels(string public async Task> BanChannelUsers(string channelId, string botId, IEnumerable<(string Id, string Username)> users, string reason, CancellationToken token = new()) { return await Retry.Execute(async () => { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); var bannedUsers = new List(); foreach ((string Id, string Username) user in users) { @@ -209,7 +210,7 @@ public async Task> BanChannelUsers(string channelId, str public async Task> GetChannelUsers(string channelId, string botId, CancellationToken token = new()) { return await Retry.Execute(async () => { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); var chatters = new List(); string? cursor = null; int total = 0; @@ -231,7 +232,7 @@ public async Task> GetChannelUsers(string channelId, string /// public async Task> GetChannelsLive(IEnumerable userIds) { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); // We can only query 100 at a time, so throttle the search. var liveUsers = new List(); @@ -256,7 +257,7 @@ public async Task> GetChannelsLive(IEnumerable userI /// public async Task> GetChannelMods(string channelId, CancellationToken token = new()) { return await Retry.Execute(async () => { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); var results = new List(); GetModeratorsResponse? response = null; @@ -283,7 +284,7 @@ public async Task> GetChannelsLive(IEnumerable userI /// public async Task AddChannelMod(string channelId, string userId, CancellationToken token = new()) { return await Retry.Execute(async () => { - TwitchAPI api = GetApi(); + ITwitchAPI api = GetApi(); await api.Helix.Moderation.AddChannelModeratorAsync(channelId, userId); return true; }, Retries, token); @@ -293,7 +294,7 @@ public async Task> GetChannelsLive(IEnumerable userI /// Gets a new instance of the . /// /// A new instance of the . - private TwitchAPI GetApi() { + protected ITwitchAPI GetApi() { var api = new TwitchAPI { Settings = { ClientId = ClientId, diff --git a/src/Nullinside.Api/Controllers/TwitchBotController.cs b/src/Nullinside.Api/Controllers/TwitchBotController.cs deleted file mode 100644 index 38b0a62..0000000 --- a/src/Nullinside.Api/Controllers/TwitchBotController.cs +++ /dev/null @@ -1,86 +0,0 @@ -using log4net; - -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -using Nullinside.Api.Common.Twitch; -using Nullinside.Api.Model; -using Nullinside.Api.Model.Shared; -using Nullinside.Api.Shared.Support; - -namespace Nullinside.Api.Controllers; - -/// -/// Handles user authentication and authorization. -/// -[ApiController] -[Route("[controller]")] -public class TwitchBotController : ControllerBase { - /// - /// The application's configuration file. - /// - private readonly IConfiguration _configuration; - - /// - /// The nullinside database. - /// - private readonly INullinsideContext _dbContext; - - /// - /// The logger. - /// - private readonly ILog _logger = LogManager.GetLogger(typeof(TwitchBotController)); - - /// - /// Initializes a new instance of the class. - /// - /// The application's configuration file. - /// The nullinside database. - public TwitchBotController(IConfiguration configuration, INullinsideContext dbContext) { - _configuration = configuration; - _dbContext = dbContext; - } - - /// - /// **NOT CALLED BY SITE OR USERS** This endpoint is called by twitch as part of their oauth workflow. It - /// redirects users back to the nullinside website. - /// - /// The credentials provided by twitch. - /// The twitch api. - /// The cancellation token. - /// - /// A redirect to the nullinside website. - /// Errors: - /// 2 = Internal error generating token. - /// 3 = Code was invalid - /// 4 = Twitch account has no email - /// - [AllowAnonymous] - [HttpGet] - [Route("login")] - public async Task TwitchLogin([FromQuery] string code, [FromServices] ITwitchApiProxy api, - CancellationToken token) { - string? siteUrl = _configuration.GetValue("Api:SiteUrl"); - if (null == await api.CreateAccessToken(code, token)) { - return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.TwitchErrorWithToken}"); - } - - string? email = await api.GetUserEmail(token); - if (string.IsNullOrWhiteSpace(email)) { - return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.TwitchAccountHasNoEmail}"); - } - - (string? id, string? username) user = await api.GetUser(token); - if (string.IsNullOrWhiteSpace(user.username) || string.IsNullOrWhiteSpace(user.id)) { - return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.InternalError}"); - } - - string? bearerToken = await UserHelpers.GetTokenAndSaveToDatabase(_dbContext, email, token, api.OAuth?.AccessToken, - api.OAuth?.RefreshToken, api.OAuth?.ExpiresUtc, user.username, user.id); - if (string.IsNullOrWhiteSpace(bearerToken)) { - return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.InternalError}"); - } - - return Redirect($"{siteUrl}/twitch-bot/config?token={bearerToken}"); - } -} \ No newline at end of file diff --git a/src/Nullinside.Api/Controllers/UserController.cs b/src/Nullinside.Api/Controllers/UserController.cs index 50e2780..7d30944 100644 --- a/src/Nullinside.Api/Controllers/UserController.cs +++ b/src/Nullinside.Api/Controllers/UserController.cs @@ -96,7 +96,7 @@ public async Task Login([FromForm] GoogleOpenIdToken creds, Cance [HttpGet] [Route("twitch-login")] public async Task TwitchLogin([FromQuery] string code, [FromServices] ITwitchApiProxy api, - CancellationToken token) { + CancellationToken token = new()) { string? siteUrl = _configuration.GetValue("Api:SiteUrl"); if (null == await api.CreateAccessToken(code, token)) { return Redirect($"{siteUrl}/user/login?error=3");