From 94b2d9ce4dd9a6b550245449ff2ae1101d55e19b 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: Sat, 23 Mar 2024 14:39:13 -0400 Subject: [PATCH] Moving token generation to common area --- .../Shared/UserHelpers.cs | 85 +++++++++++++++++++ .../Controllers/UserController.cs | 78 +---------------- 2 files changed, 88 insertions(+), 75 deletions(-) create mode 100644 src/Nullinside.Api.Model/Shared/UserHelpers.cs diff --git a/src/Nullinside.Api.Model/Shared/UserHelpers.cs b/src/Nullinside.Api.Model/Shared/UserHelpers.cs new file mode 100644 index 0000000..c67c72f --- /dev/null +++ b/src/Nullinside.Api.Model/Shared/UserHelpers.cs @@ -0,0 +1,85 @@ +using System.Security.Cryptography; + +using Microsoft.EntityFrameworkCore; + +using Nullinside.Api.Common; +using Nullinside.Api.Model.Ddl; + +namespace Nullinside.Api.Model.Shared; + +/// +/// Helper methods for user functions in the database. +/// +public static class UserHelpers { + /// + /// Generates a new bearer token, saves it to the database, and returns it. + /// + /// The database context. + /// The email address of the user, user will be created if they don't already exist. + /// The cancellation token. + /// The authorization token for twitch, if applicable. + /// The refresh token for twitch, if applicable. + /// The expiration date of the token for twitch, if applicable. + /// The bearer token if successful, null otherwise. + public static async Task GetTokenAndSaveToDatabase(NullinsideContext dbContext, string email, CancellationToken token = new(), string? authToken = null, string? refreshToken = null, DateTime? expires = null) { + string bearerToken = GenerateBearerToken(); + try { + User? existing = await dbContext.Users.FirstOrDefaultAsync(u => u.Email == email, token); + if (null == existing) { + dbContext.Users.Add(new User { + Email = email, + Token = bearerToken, + TwitchToken = authToken, + TwitchRefreshToken = refreshToken, + TwitchTokenExpiration = expires, + UpdatedOn = DateTime.UtcNow, + CreatedOn = DateTime.UtcNow + }); + + await dbContext.SaveChangesAsync(token); + + existing = await dbContext.Users.FirstOrDefaultAsync(u => u.Email == email, token); + if (null == existing) { + return null; + } + + dbContext.UserRoles.Add(new UserRole { + Role = UserRoles.User, + UserId = existing.Id, + RoleAdded = DateTime.UtcNow + }); + } + else { + existing.Token = bearerToken; + existing.TwitchToken = authToken; + existing.TwitchRefreshToken = refreshToken; + existing.TwitchTokenExpiration = expires; + existing.UpdatedOn = DateTime.UtcNow; + } + + await dbContext.SaveChangesAsync(token); + return bearerToken; + } + catch { + return null; + } + } + + /// + /// Generates a new unique bearer token. + /// + /// A bearer token. + public static string GenerateBearerToken() { + // This method is trash but it doesn't matter. We should be doing real OAuth tokens with expirations and + // renewals. Right now nothing that exists on the site requires this level of sophistication. + string allowed = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789"; + int strlen = 255; // Or whatever + char[] randomChars = new char[strlen]; + + for (int i = 0; i < strlen; i++) { + randomChars[i] = allowed[RandomNumberGenerator.GetInt32(0, allowed.Length)]; + } + + return new string(randomChars); + } +} \ No newline at end of file diff --git a/src/Nullinside.Api/Controllers/UserController.cs b/src/Nullinside.Api/Controllers/UserController.cs index e723153..bf6480b 100644 --- a/src/Nullinside.Api/Controllers/UserController.cs +++ b/src/Nullinside.Api/Controllers/UserController.cs @@ -1,5 +1,4 @@ using System.Security.Claims; -using System.Security.Cryptography; using Google.Apis.Auth; @@ -7,9 +6,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Nullinside.Api.Common; using Nullinside.Api.Model; using Nullinside.Api.Model.Ddl; +using Nullinside.Api.Model.Shared; using Nullinside.Api.Shared; using Nullinside.Api.Shared.Json; @@ -66,7 +65,7 @@ public async Task Login([FromForm] GoogleOpenIdToken creds, Cance return Redirect($"{siteUrl}/user/login?error=1"); } - string? bearerToken = await GetTokenAndSaveToDatabase(credentials.Email, token); + string? bearerToken = await UserHelpers.GetTokenAndSaveToDatabase(_dbContext, credentials.Email, token); if (string.IsNullOrWhiteSpace(bearerToken)) { return Redirect($"{siteUrl}/user/login?error=2"); } @@ -107,7 +106,7 @@ public async Task TwitchLogin([FromQuery] string code, Cancellati return Redirect($"{siteUrl}/user/login?error=4"); } - string? bearerToken = await GetTokenAndSaveToDatabase(email, token); + string? bearerToken = await UserHelpers.GetTokenAndSaveToDatabase(_dbContext, email, token); if (string.IsNullOrWhiteSpace(bearerToken)) { return Redirect($"{siteUrl}/user/login?error=2"); } @@ -115,59 +114,6 @@ public async Task TwitchLogin([FromQuery] string code, Cancellati return Redirect($"{siteUrl}/user/login?token={bearerToken}"); } - /// - /// Generates a new bearer token, saves it to the database, and returns it. - /// - /// The email address of the user, user will be created if they don't already exist. - /// The cancellation token. - /// The authorization token for twitch, if applicable. - /// The refresh token for twitch, if applicable. - /// The expiration date of the token for twitch, if applicable. - /// The bearer token if successful, null otherwise. - private async Task GetTokenAndSaveToDatabase(string email, CancellationToken token = new(), string? authToken = null, string? refreshToken = null, DateTime? expires = null) { - string bearerToken = GenerateBearerToken(); - try { - User? existing = await _dbContext.Users.FirstOrDefaultAsync(u => u.Email == email, token); - if (null == existing) { - _dbContext.Users.Add(new User { - Email = email, - Token = bearerToken, - TwitchToken = authToken, - TwitchRefreshToken = refreshToken, - TwitchTokenExpiration = expires, - UpdatedOn = DateTime.UtcNow, - CreatedOn = DateTime.UtcNow - }); - - await _dbContext.SaveChangesAsync(token); - - existing = await _dbContext.Users.FirstOrDefaultAsync(u => u.Email == email, token); - if (null == existing) { - return null; - } - - _dbContext.UserRoles.Add(new UserRole { - Role = UserRoles.User, - UserId = existing.Id, - RoleAdded = DateTime.UtcNow - }); - } - else { - existing.Token = bearerToken; - existing.TwitchToken = authToken; - existing.TwitchRefreshToken = refreshToken; - existing.TwitchTokenExpiration = expires; - existing.UpdatedOn = DateTime.UtcNow; - } - - await _dbContext.SaveChangesAsync(token); - return bearerToken; - } - catch { - return null; - } - } - /// /// Gets the roles of the current user. /// @@ -209,22 +155,4 @@ public async Task Validate(AuthToken token) { return StatusCode(500); } } - - /// - /// Generates a new unique bearer token. - /// - /// A bearer token. - private static string GenerateBearerToken() { - // This method is trash but it doesn't matter. We should be doing real OAuth tokens with expirations and - // renewals. Right now nothing that exists on the site requires this level of sophistication. - string allowed = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789"; - int strlen = 255; // Or whatever - char[] randomChars = new char[strlen]; - - for (int i = 0; i < strlen; i++) { - randomChars[i] = allowed[RandomNumberGenerator.GetInt32(0, allowed.Length)]; - } - - return new string(randomChars); - } } \ No newline at end of file