Skip to content

Commit a6d09ef

Browse files
feat: returning access token to ui
1 parent 44ed0b6 commit a6d09ef

File tree

8 files changed

+49
-34
lines changed

8 files changed

+49
-34
lines changed

src/Nullinside.Api.Common.AspNetCore/Middleware/BasicAuthenticationHandler.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
6161
.AsNoTracking()
6262
.FirstOrDefaultAsync(u => !string.IsNullOrWhiteSpace(u.Token) &&
6363
u.Token == token &&
64+
u.TokenExpires > DateTime.UtcNow &&
6465
!u.IsBanned)
6566
.ConfigureAwait(false);
6667

src/Nullinside.Api.Common/Twitch/TwitchAccessToken.cs renamed to src/Nullinside.Api.Common/Auth/OAuthToken.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
namespace Nullinside.Api.Common.Twitch;
1+
namespace Nullinside.Api.Common.Auth;
22

33
/// <summary>
44
/// Represents an OAuth token in the Twitch workflow.
55
/// </summary>
6-
public class TwitchAccessToken {
6+
public class OAuthToken {
77
/// <summary>
88
/// The Twitch access token.
99
/// </summary>

src/Nullinside.Api.Common/Twitch/ITwitchApiProxy.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Nullinside.Api.Common.Twitch.Json;
1+
using Nullinside.Api.Common.Auth;
2+
using Nullinside.Api.Common.Twitch.Json;
23

34
using TwitchLib.Api.Helix.Models.Chat.GetChatters;
45
using TwitchLib.Api.Helix.Models.Moderation.BanUser;
@@ -14,7 +15,7 @@ public interface ITwitchApiProxy {
1415
/// <summary>
1516
/// The Twitch access token. These are the credentials used for all requests.
1617
/// </summary>
17-
TwitchAccessToken? OAuth { get; set; }
18+
OAuthToken? OAuth { get; set; }
1819

1920
/// <summary>
2021
/// The Twitch app configuration. These are used for all requests.
@@ -28,15 +29,15 @@ public interface ITwitchApiProxy {
2829
/// <param name="token">The cancellation token.</param>
2930
/// <remarks>The object will have its <see cref="OAuth" /> updated with the new settings for the token.</remarks>
3031
/// <returns>The OAuth details if successful, null otherwise.</returns>
31-
Task<TwitchAccessToken?> CreateAccessToken(string code, CancellationToken token = new());
32+
Task<OAuthToken?> CreateAccessToken(string code, CancellationToken token = new());
3233

3334
/// <summary>
3435
/// Refreshes the access token.
3536
/// </summary>
3637
/// <param name="token">The cancellation token.</param>
3738
/// <remarks>The object will have its <see cref="OAuth" /> updated with the new settings for the token.</remarks>
3839
/// <returns>The OAuth details if successful, null otherwise.</returns>
39-
Task<TwitchAccessToken?> RefreshAccessToken(CancellationToken token = new());
40+
Task<OAuthToken?> RefreshAccessToken(CancellationToken token = new());
4041

4142
/// <summary>
4243
/// Determines if the <see cref="OAuth" /> is valid.

src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using Newtonsoft.Json;
66

7+
using Nullinside.Api.Common.Auth;
78
using Nullinside.Api.Common.Twitch.Json;
89

910
using TwitchLib.Api;
@@ -60,7 +61,7 @@ public TwitchApiProxy() {
6061
/// </param>
6162
public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires, string? clientId = null,
6263
string? clientSecret = null, string? clientRedirect = null) {
63-
OAuth = new TwitchAccessToken {
64+
OAuth = new OAuthToken {
6465
AccessToken = token,
6566
RefreshToken = refreshToken,
6667
ExpiresUtc = tokenExpires
@@ -79,21 +80,21 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires,
7980
public int Retries { get; set; } = 3;
8081

8182
/// <inheritdoc />
82-
public virtual TwitchAccessToken? OAuth { get; set; }
83+
public virtual OAuthToken? OAuth { get; set; }
8384

8485
/// <inheritdoc />
8586
public virtual TwitchAppConfig? TwitchAppConfig { get; set; }
8687

8788
/// <inheritdoc />
88-
public virtual async Task<TwitchAccessToken?> CreateAccessToken(string code, CancellationToken token = new()) {
89+
public virtual async Task<OAuthToken?> CreateAccessToken(string code, CancellationToken token = new()) {
8990
ITwitchAPI api = GetApi();
9091
AuthCodeResponse? response = await api.Auth.GetAccessTokenFromCodeAsync(code, TwitchAppConfig?.ClientSecret,
9192
TwitchAppConfig?.ClientRedirect).ConfigureAwait(false);
9293
if (null == response) {
9394
return null;
9495
}
9596

96-
OAuth = new TwitchAccessToken {
97+
OAuth = new OAuthToken {
9798
AccessToken = response.AccessToken,
9899
RefreshToken = response.RefreshToken,
99100
ExpiresUtc = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn)
@@ -102,7 +103,7 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires,
102103
}
103104

104105
/// <inheritdoc />
105-
public virtual async Task<TwitchAccessToken?> RefreshAccessToken(CancellationToken token = new()) {
106+
public virtual async Task<OAuthToken?> RefreshAccessToken(CancellationToken token = new()) {
106107
try {
107108
if (string.IsNullOrWhiteSpace(TwitchAppConfig?.ClientSecret) || string.IsNullOrWhiteSpace(TwitchAppConfig?.ClientId)) {
108109
return null;
@@ -114,7 +115,7 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires,
114115
return null;
115116
}
116117

117-
OAuth = new TwitchAccessToken {
118+
OAuth = new OAuthToken {
118119
AccessToken = response.AccessToken,
119120
RefreshToken = response.RefreshToken,
120121
ExpiresUtc = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn)

src/Nullinside.Api.Model/Shared/UserHelpers.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ public static class UserHelpers {
2323
/// <param name="twitchUsername">The username of the user on twitch.</param>
2424
/// <param name="twitchId">The id of the user on twitch.</param>
2525
/// <returns>The bearer token if successful, null otherwise.</returns>
26-
public static async Task<string?> GenerateTokenAndSaveToDatabase(INullinsideContext dbContext, string email,
26+
public static async Task<OAuthToken?> GenerateTokenAndSaveToDatabase(INullinsideContext dbContext, string email,
2727
TimeSpan tokenExpires, string? authToken = null, string? refreshToken = null, DateTime? expires = null,
2828
string? twitchUsername = null, string? twitchId = null, CancellationToken cancellationToken = new()) {
2929
string bearerToken = AuthUtils.GenerateToken();
3030
string bearerRefreshToken = AuthUtils.GenerateToken();
31+
DateTime expiresUtc = DateTime.UtcNow + tokenExpires;
3132
try {
3233
User? existing = await dbContext.Users.FirstOrDefaultAsync(u => u.Email == email && !u.IsBanned, cancellationToken).ConfigureAwait(false);
3334
if (null == existing && !string.IsNullOrWhiteSpace(twitchUsername)) {
@@ -75,7 +76,11 @@ public static class UserHelpers {
7576
}
7677

7778
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
78-
return bearerToken;
79+
return new() {
80+
AccessToken = bearerToken,
81+
RefreshToken = bearerRefreshToken,
82+
ExpiresUtc = expiresUtc
83+
};
7984
}
8085
catch {
8186
return null;

src/Nullinside.Api.Tests/Nullinside.Api.Model/Shared/UserHelpersTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ public async Task GenerateTokenForExistingUser() {
2323
Assert.That(_db.Users.Count(), Is.EqualTo(1));
2424

2525
// Generate a new token
26-
string? token = await UserHelpers.GenerateTokenAndSaveToDatabase(_db, "email", Constants.OAUTH_TOKEN_TIME_LIMIT).ConfigureAwait(false);
26+
var token = await UserHelpers.GenerateTokenAndSaveToDatabase(_db, "email", Constants.OAUTH_TOKEN_TIME_LIMIT).ConfigureAwait(false);
2727
Assert.That(token, Is.Not.Null);
2828

2929
// Verify we still only have one user
3030
Assert.That(_db.Users.Count(), Is.EqualTo(1));
31-
Assert.That(_db.Users.First().Token, Is.EqualTo(token));
31+
Assert.That(_db.Users.First().Token, Is.EqualTo(token.AccessToken));
3232
}
3333

3434
/// <summary>
@@ -48,12 +48,12 @@ public async Task GenerateTokenForNewUser() {
4848
Assert.That(_db.Users.Count(), Is.EqualTo(1));
4949

5050
// Generate a new token
51-
string? token = await UserHelpers.GenerateTokenAndSaveToDatabase(_db, "email", Constants.OAUTH_TOKEN_TIME_LIMIT).ConfigureAwait(false);
51+
var token = await UserHelpers.GenerateTokenAndSaveToDatabase(_db, "email", Constants.OAUTH_TOKEN_TIME_LIMIT).ConfigureAwait(false);
5252
Assert.That(token, Is.Not.Null);
5353

5454
// Verify we have a new user
5555
Assert.That(_db.Users.Count(), Is.EqualTo(2));
56-
Assert.That(_db.Users.FirstOrDefault(u => u.Email == "email")?.Token, Is.EqualTo(token));
56+
Assert.That(_db.Users.FirstOrDefault(u => u.Email == "email")?.Token, Is.EqualTo(token.AccessToken));
5757

5858
// Verfy the old user is untouched
5959
Assert.That(_db.Users.FirstOrDefault(u => u.Email == "email2")?.Token, Is.Null);
@@ -65,7 +65,7 @@ public async Task GenerateTokenForNewUser() {
6565
[Test]
6666
public async Task HandleUnexpectedErrors() {
6767
// Force an error to occur.
68-
string? token = await UserHelpers.GenerateTokenAndSaveToDatabase(null!, "email", Constants.OAUTH_TOKEN_TIME_LIMIT).ConfigureAwait(false);
68+
var token = await UserHelpers.GenerateTokenAndSaveToDatabase(null!, "email", Constants.OAUTH_TOKEN_TIME_LIMIT).ConfigureAwait(false);
6969
Assert.That(token, Is.Null);
7070
}
7171
}

src/Nullinside.Api.Tests/Nullinside.Api/Controllers/UserControllerTests.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
using Moq;
1111

12+
using Nullinside.Api.Common.Auth;
1213
using Nullinside.Api.Common.Twitch;
1314
using Nullinside.Api.Controllers;
1415
using Nullinside.Api.Model;
@@ -139,7 +140,7 @@ public async Task GoToErrorOnBadGmailResponse() {
139140
public async Task PerformTwitchLoginExisting() {
140141
// Tells us twitch parsed the code successfully.
141142
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
142-
.Returns(() => Task.FromResult<TwitchAccessToken?>(new TwitchAccessToken()));
143+
.Returns(() => Task.FromResult<OAuthToken?>(new OAuthToken()));
143144

144145
// Gets a matching email address from our database
145146
_twitchApi.Setup(a => a.GetUserEmail(It.IsAny<CancellationToken>()))
@@ -174,7 +175,7 @@ public async Task PerformTwitchLoginExisting() {
174175
public async Task PerformTwitchLoginNewUser() {
175176
// Tells us twitch parsed the code successfully.
176177
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
177-
.Returns(() => Task.FromResult<TwitchAccessToken?>(new TwitchAccessToken()));
178+
.Returns(() => Task.FromResult<OAuthToken?>(new OAuthToken()));
178179

179180
// Gets a matching email address from our database
180181
_twitchApi.Setup(a => a.GetUserEmail(It.IsAny<CancellationToken>()))
@@ -201,7 +202,7 @@ public async Task PerformTwitchLoginNewUser() {
201202
public async Task PerformTwitchLoginBadTwitchResponse() {
202203
// Tells us twitch thinks it was a bad code.
203204
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
204-
.Returns(() => Task.FromResult<TwitchAccessToken?>(null));
205+
.Returns(() => Task.FromResult<OAuthToken?>(null));
205206

206207
// Make the call and ensure it's successful.
207208
var controller = new TestableUserController(_configuration, _db, _webSocketPersister.Object);
@@ -218,7 +219,7 @@ public async Task PerformTwitchLoginBadTwitchResponse() {
218219
public async Task PerformTwitchLoginWithNoEmailAccount() {
219220
// Tells us twitch parsed the code successfully.
220221
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
221-
.Returns(() => Task.FromResult<TwitchAccessToken?>(new TwitchAccessToken()));
222+
.Returns(() => Task.FromResult<OAuthToken?>(new OAuthToken()));
222223

223224
// Make the call and ensure it's successful.
224225
var controller = new TestableUserController(_configuration, _db, _webSocketPersister.Object);
@@ -237,7 +238,7 @@ public async Task PerformTwitchLoginDbFailure() {
237238

238239
// Tells us twitch parsed the code successfully.
239240
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
240-
.Returns(() => Task.FromResult<TwitchAccessToken?>(new TwitchAccessToken()));
241+
.Returns(() => Task.FromResult<OAuthToken?>(new OAuthToken()));
241242

242243
// Gets an email address from twitch
243244
_twitchApi.Setup(a => a.GetUserEmail(It.IsAny<CancellationToken>()))

src/Nullinside.Api/Controllers/UserController.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Net.WebSockets;
22
using System.Security.Claims;
3+
using System.Text;
34

45
using Google.Apis.Auth;
56

@@ -11,6 +12,7 @@
1112

1213
using Newtonsoft.Json;
1314

15+
using Nullinside.Api.Common.Auth;
1416
using Nullinside.Api.Common.Extensions;
1517
using Nullinside.Api.Common.Twitch;
1618
using Nullinside.Api.Model;
@@ -19,6 +21,8 @@
1921
using Nullinside.Api.Shared;
2022
using Nullinside.Api.Shared.Json;
2123

24+
using Org.BouncyCastle.Utilities.Encoders;
25+
2226
namespace Nullinside.Api.Controllers;
2327

2428
/// <summary>
@@ -77,12 +81,13 @@ public UserController(IConfiguration configuration, INullinsideContext dbContext
7781
return Redirect($"{siteUrl}/user/login?error=1");
7882
}
7983

80-
string? bearerToken = await UserHelpers.GenerateTokenAndSaveToDatabase(_dbContext, credentials.Email, Constants.OAUTH_TOKEN_TIME_LIMIT, cancellationToken: token).ConfigureAwait(false);
81-
if (string.IsNullOrWhiteSpace(bearerToken)) {
84+
var bearerToken = await UserHelpers.GenerateTokenAndSaveToDatabase(_dbContext, credentials.Email, Constants.OAUTH_TOKEN_TIME_LIMIT, cancellationToken: token).ConfigureAwait(false);
85+
if (null == bearerToken) {
8286
return Redirect($"{siteUrl}/user/login?error=2");
8387
}
84-
85-
return Redirect($"{siteUrl}/user/login?token={bearerToken}");
88+
89+
var json = JsonConvert.SerializeObject(bearerToken);
90+
return Redirect($"{siteUrl}/user/login?token={Convert.ToBase64String(Encoding.UTF8.GetBytes(json))}");
8691
}
8792
catch (InvalidJwtException) {
8893
return Redirect($"{siteUrl}/user/login?error=1");
@@ -127,12 +132,13 @@ public async Task<RedirectResult> TwitchLogin([FromQuery] string code, [FromServ
127132
return Redirect($"{siteUrl}/user/login?error=4");
128133
}
129134

130-
string? bearerToken = await UserHelpers.GenerateTokenAndSaveToDatabase(_dbContext, email, Constants.OAUTH_TOKEN_TIME_LIMIT, cancellationToken: token).ConfigureAwait(false);
131-
if (string.IsNullOrWhiteSpace(bearerToken)) {
135+
var bearerToken = await UserHelpers.GenerateTokenAndSaveToDatabase(_dbContext, email, Constants.OAUTH_TOKEN_TIME_LIMIT, cancellationToken: token).ConfigureAwait(false);
136+
if (null == bearerToken) {
132137
return Redirect($"{siteUrl}/user/login?error=2");
133138
}
134139

135-
return Redirect($"{siteUrl}/user/login?token={bearerToken}");
140+
var json = JsonConvert.SerializeObject(bearerToken);
141+
return Redirect($"{siteUrl}/user/login?token={Convert.ToBase64String(Encoding.UTF8.GetBytes(json))}");
136142
}
137143

138144
/// <summary>
@@ -170,7 +176,7 @@ public async Task<RedirectResult> TwitchStreamingToolsLogin([FromQuery] string c
170176
// socket so we will pull up that socket and give them their oauth information.
171177
try {
172178
WebSocket socket = _webSockets.WebSockets[state];
173-
var oAuth = new TwitchAccessToken {
179+
var oAuth = new OAuthToken {
174180
AccessToken = api.OAuth?.AccessToken ?? string.Empty,
175181
RefreshToken = api.OAuth?.RefreshToken ?? string.Empty,
176182
ExpiresUtc = api.OAuth?.ExpiresUtc ?? DateTime.MinValue
@@ -232,7 +238,7 @@ public async Task<RedirectResult> TwitchStreamingToolsLogin([FromQuery] string c
232238
[Route("twitch-login/twitch-streaming-tools")]
233239
public async Task<IActionResult> TwitchStreamingToolsRefreshToken([FromForm] string refreshToken, [FromServices] ITwitchApiProxy api,
234240
CancellationToken token = new()) {
235-
api.OAuth = new TwitchAccessToken {
241+
api.OAuth = new OAuthToken {
236242
AccessToken = null,
237243
RefreshToken = refreshToken,
238244
ExpiresUtc = DateTime.MinValue
@@ -242,7 +248,7 @@ public async Task<IActionResult> TwitchStreamingToolsRefreshToken([FromForm] str
242248
return BadRequest();
243249
}
244250

245-
return Ok(new TwitchAccessToken {
251+
return Ok(new OAuthToken {
246252
AccessToken = api.OAuth.AccessToken ?? string.Empty,
247253
RefreshToken = api.OAuth.RefreshToken ?? string.Empty,
248254
ExpiresUtc = api.OAuth.ExpiresUtc ?? DateTime.MinValue

0 commit comments

Comments
 (0)