Skip to content

Commit 661b929

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

File tree

8 files changed

+72
-39
lines changed

8 files changed

+72
-39
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: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Security.Claims;
2+
using System.Text;
3+
using System.Text.Unicode;
24

35
using Google.Apis.Auth;
46

@@ -9,13 +11,18 @@
911

1012
using Moq;
1113

14+
using Newtonsoft.Json;
15+
16+
using Nullinside.Api.Common.Auth;
1217
using Nullinside.Api.Common.Twitch;
1318
using Nullinside.Api.Controllers;
1419
using Nullinside.Api.Model;
1520
using Nullinside.Api.Model.Ddl;
1621
using Nullinside.Api.Shared;
1722
using Nullinside.Api.Shared.Json;
1823

24+
using Org.BouncyCastle.Utilities.Encoders;
25+
1926
namespace Nullinside.Api.Tests.Nullinside.Api.Controllers;
2027

2128
/// <summary>
@@ -74,12 +81,15 @@ public async Task PerformGoogleLoginExisting() {
7481

7582
// We should have been redirected to the successful route.
7683
Assert.That(obj.Url.StartsWith("/user/login?token="), Is.True);
84+
var queryParam = obj.Url["/user/login?token=".Length..];
7785

7886
// No additional users should have been created.
7987
Assert.That(_db.Users.Count(), Is.EqualTo(1));
8088

81-
// We should have saved the token in the existing user's database.
82-
Assert.That(obj.Url.EndsWith(_db.Users.First().Token!), Is.True);
89+
// We should have saved the token in the existing user's database.
90+
var json = Encoding.UTF8.GetString(Convert.FromBase64String(queryParam));
91+
var oauth = JsonConvert.DeserializeObject<OAuthToken>(json);
92+
Assert.That(oauth?.AccessToken!, Is.EqualTo(_db.Users.First().Token!));
8393
}
8494

8595
/// <summary>
@@ -94,12 +104,15 @@ public async Task PerformGoogleLoginNewUser() {
94104

95105
// We should have been redirected to the successful route.
96106
Assert.That(obj.Url.StartsWith("/user/login?token="), Is.True);
107+
var queryParam = obj.Url["/user/login?token=".Length..];
97108

98109
// No additional users should have been created.
99110
Assert.That(_db.Users.Count(), Is.EqualTo(1));
100111

101112
// We should have saved the token in the existing user's database.
102-
Assert.That(obj.Url.EndsWith(_db.Users.First().Token!), Is.True);
113+
var json = Encoding.UTF8.GetString(Convert.FromBase64String(queryParam));
114+
var oauth = JsonConvert.DeserializeObject<OAuthToken>(json);
115+
Assert.That(oauth?.AccessToken!, Is.EqualTo(_db.Users.First().Token!));
103116
}
104117

105118
/// <summary>
@@ -139,7 +152,7 @@ public async Task GoToErrorOnBadGmailResponse() {
139152
public async Task PerformTwitchLoginExisting() {
140153
// Tells us twitch parsed the code successfully.
141154
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
142-
.Returns(() => Task.FromResult<TwitchAccessToken?>(new TwitchAccessToken()));
155+
.Returns(() => Task.FromResult<OAuthToken?>(new OAuthToken()));
143156

144157
// Gets a matching email address from our database
145158
_twitchApi.Setup(a => a.GetUserEmail(It.IsAny<CancellationToken>()))
@@ -159,12 +172,15 @@ public async Task PerformTwitchLoginExisting() {
159172

160173
// We should have been redirected to the successful route.
161174
Assert.That(obj.Url.StartsWith("/user/login?token="), Is.True);
175+
var queryParam = obj.Url["/user/login?token=".Length..];
162176

163177
// No additional users should have been created.
164178
Assert.That(_db.Users.Count(), Is.EqualTo(1));
165179

166180
// We should have saved the token in the existing user's database.
167-
Assert.That(obj.Url.EndsWith(_db.Users.First().Token!), Is.True);
181+
var json = Encoding.UTF8.GetString(Convert.FromBase64String(queryParam));
182+
var oauth = JsonConvert.DeserializeObject<OAuthToken>(json);
183+
Assert.That(oauth?.AccessToken!, Is.EqualTo(_db.Users.First().Token!));
168184
}
169185

170186
/// <summary>
@@ -174,7 +190,7 @@ public async Task PerformTwitchLoginExisting() {
174190
public async Task PerformTwitchLoginNewUser() {
175191
// Tells us twitch parsed the code successfully.
176192
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
177-
.Returns(() => Task.FromResult<TwitchAccessToken?>(new TwitchAccessToken()));
193+
.Returns(() => Task.FromResult<OAuthToken?>(new OAuthToken()));
178194

179195
// Gets a matching email address from our database
180196
_twitchApi.Setup(a => a.GetUserEmail(It.IsAny<CancellationToken>()))
@@ -186,12 +202,15 @@ public async Task PerformTwitchLoginNewUser() {
186202

187203
// We should have been redirected to the successful route.
188204
Assert.That(obj.Url.StartsWith("/user/login?token="), Is.True);
205+
var queryParam = obj.Url["/user/login?token=".Length..];
189206

190207
// No additional users should have been created.
191208
Assert.That(_db.Users.Count(), Is.EqualTo(1));
192209

193210
// We should have saved the token in the existing user's database.
194-
Assert.That(obj.Url.EndsWith(_db.Users.First().Token!), Is.True);
211+
var json = Encoding.UTF8.GetString(Convert.FromBase64String(queryParam));
212+
var oauth = JsonConvert.DeserializeObject<OAuthToken>(json);
213+
Assert.That(oauth?.AccessToken!, Is.EqualTo(_db.Users.First().Token!));
195214
}
196215

197216
/// <summary>
@@ -201,7 +220,7 @@ public async Task PerformTwitchLoginNewUser() {
201220
public async Task PerformTwitchLoginBadTwitchResponse() {
202221
// Tells us twitch thinks it was a bad code.
203222
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
204-
.Returns(() => Task.FromResult<TwitchAccessToken?>(null));
223+
.Returns(() => Task.FromResult<OAuthToken?>(null));
205224

206225
// Make the call and ensure it's successful.
207226
var controller = new TestableUserController(_configuration, _db, _webSocketPersister.Object);
@@ -218,7 +237,7 @@ public async Task PerformTwitchLoginBadTwitchResponse() {
218237
public async Task PerformTwitchLoginWithNoEmailAccount() {
219238
// Tells us twitch parsed the code successfully.
220239
_twitchApi.Setup(a => a.CreateAccessToken(It.IsAny<string>(), It.IsAny<CancellationToken>()))
221-
.Returns(() => Task.FromResult<TwitchAccessToken?>(new TwitchAccessToken()));
240+
.Returns(() => Task.FromResult<OAuthToken?>(new OAuthToken()));
222241

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

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

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

0 commit comments

Comments
 (0)