Skip to content

Commit eb56d88

Browse files
feat: adding endpoints for refreshing oauth tokens
1 parent 0956c53 commit eb56d88

File tree

5 files changed

+86
-28
lines changed

5 files changed

+86
-28
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public interface ITwitchApiProxy {
1414
/// The Twitch access token. These are the credentials used for all requests.
1515
/// </summary>
1616
TwitchAccessToken? OAuth { get; set; }
17+
18+
/// <summary>
19+
/// The Twitch app configuration. These are used for all requests.
20+
/// </summary>
21+
TwitchAppConfig? TwitchAppConfig { get; set; }
1722

1823
/// <summary>
1924
/// Creates a new access token from a code using Twitch's OAuth workflow.

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

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,15 @@ public class TwitchApiProxy : ITwitchApiProxy {
2929
/// </summary>
3030
private static readonly ILog Log = LogManager.GetLogger(typeof(TwitchApiProxy));
3131

32-
/// <summary>
33-
/// The, public, twitch client id.
34-
/// </summary>
35-
protected readonly string? _clientId;
36-
37-
/// <summary>
38-
/// The, private, twitch client secret.
39-
/// </summary>
40-
protected readonly string? _clientSecret;
41-
42-
/// <summary>
43-
/// The redirect url.
44-
/// </summary>
45-
protected readonly string? _clientRedirect;
46-
4732
/// <summary>
4833
/// Initializes a new instance of the <see cref="TwitchApiProxy" /> class.
4934
/// </summary>
5035
public TwitchApiProxy() {
36+
TwitchAppConfig = new() {
37+
ClientId = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_ID"),
38+
ClientSecret = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_SECRET"),
39+
ClientRedirect = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_REDIRECT")
40+
};
5141
}
5242

5343
/// <summary>
@@ -64,15 +54,17 @@ public TwitchApiProxy() {
6454
/// "TWITCH_BOT_CLIENT_REDIRECT" when null.</param>
6555
public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires, string? clientId = null,
6656
string? clientSecret = null, string? clientRedirect = null) {
67-
_clientId = clientId ?? Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_ID");
68-
_clientSecret = clientSecret ?? Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_SECRET");
69-
_clientRedirect = clientRedirect ?? Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_REDIRECT");
70-
7157
OAuth = new TwitchAccessToken {
7258
AccessToken = token,
7359
RefreshToken = refreshToken,
7460
ExpiresUtc = tokenExpires
7561
};
62+
63+
TwitchAppConfig = new() {
64+
ClientId = clientId ?? Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_ID"),
65+
ClientSecret = clientSecret ?? Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_SECRET"),
66+
ClientRedirect = clientRedirect ?? Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_REDIRECT")
67+
};
7668
}
7769

7870
/// <summary>
@@ -81,12 +73,16 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires,
8173
public int Retries { get; set; } = 3;
8274

8375
/// <inheritdoc />
84-
public TwitchAccessToken? OAuth { get; set; }
76+
public virtual TwitchAccessToken? OAuth { get; set; }
77+
78+
/// <inheritdoc />
79+
public virtual TwitchAppConfig? TwitchAppConfig { get; set; }
8580

8681
/// <inheritdoc />
8782
public virtual async Task<TwitchAccessToken?> CreateAccessToken(string code, CancellationToken token = new()) {
8883
ITwitchAPI api = GetApi();
89-
AuthCodeResponse? response = await api.Auth.GetAccessTokenFromCodeAsync(code, _clientSecret, _clientRedirect);
84+
AuthCodeResponse? response = await api.Auth.GetAccessTokenFromCodeAsync(code, TwitchAppConfig?.ClientSecret,
85+
TwitchAppConfig?.ClientRedirect);
9086
if (null == response) {
9187
return null;
9288
}
@@ -102,12 +98,12 @@ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires,
10298
/// <inheritdoc />
10399
public virtual async Task<TwitchAccessToken?> RefreshAccessToken(CancellationToken token = new()) {
104100
try {
105-
if (string.IsNullOrWhiteSpace(_clientSecret) || string.IsNullOrWhiteSpace(_clientId)) {
101+
if (string.IsNullOrWhiteSpace(TwitchAppConfig?.ClientSecret) || string.IsNullOrWhiteSpace(TwitchAppConfig?.ClientId)) {
106102
return null;
107103
}
108104

109105
ITwitchAPI api = GetApi();
110-
RefreshResponse? response = await api.Auth.RefreshAuthTokenAsync(OAuth?.RefreshToken, _clientSecret, _clientId);
106+
RefreshResponse? response = await api.Auth.RefreshAuthTokenAsync(OAuth?.RefreshToken, TwitchAppConfig?.ClientSecret, TwitchAppConfig?.ClientId);
111107
if (null == response) {
112108
return null;
113109
}
@@ -175,7 +171,7 @@ public virtual async Task<IEnumerable<TwitchModeratedChannel>> GetUserModChanne
175171

176172
var request = new HttpRequestMessage(HttpMethod.Get, url);
177173
request.Headers.Add("Authorization", $"Bearer {OAuth?.AccessToken}");
178-
request.Headers.Add("Client-Id", _clientId);
174+
request.Headers.Add("Client-Id", TwitchAppConfig?.ClientId);
179175

180176
using HttpResponseMessage response = await client.SendAsync(request);
181177
response.EnsureSuccessStatusCode();
@@ -317,7 +313,7 @@ public virtual async Task<IEnumerable<string>> GetChannelsLive(IEnumerable<stri
317313
protected virtual ITwitchAPI GetApi() {
318314
var api = new TwitchAPI {
319315
Settings = {
320-
ClientId = _clientId,
316+
ClientId = TwitchAppConfig?.ClientId,
321317
AccessToken = OAuth?.AccessToken
322318
}
323319
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Nullinside.Api.Common.Twitch;
2+
3+
/// <summary>
4+
/// The configuration for a twitch app that provides OAuth tokens.
5+
/// </summary>
6+
public class TwitchAppConfig {
7+
/// <summary>
8+
/// The client id.
9+
/// </summary>
10+
public string? ClientId { get; set; }
11+
12+
/// <summary>
13+
/// The client secret.
14+
/// </summary>
15+
public string? ClientSecret { get; set; }
16+
17+
/// <summary>
18+
/// A registered URL that the Twitch API is allowed to redirect to on our website.
19+
/// </summary>
20+
public string? ClientRedirect { get; set; }
21+
}

src/Nullinside.Api/Controllers/UserController.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,46 @@ public async Task<RedirectResult> TwitchStreamingToolsLogin([FromQuery] string c
144144
CancellationToken token = new()) {
145145
string? siteUrl = _configuration.GetValue<string>("Api:SiteUrl");
146146
if (null == await api.CreateAccessToken(code, token)) {
147-
return Redirect($"{siteUrl}/user/login?error=3");
147+
return Redirect($"{siteUrl}/user/login/desktop?error=3");
148+
}
149+
150+
return Redirect($"{siteUrl}/user/login/desktop?bearer={api.OAuth?.AccessToken}&refresh={api.OAuth?.RefreshToken}&expiresUtc={api.OAuth?.ExpiresUtc?.ToString()}");
151+
}
152+
153+
/// <summary>
154+
/// Used to refresh OAuth tokens from the desktop application.
155+
/// </summary>
156+
/// <param name="code">The credentials provided by twitch.</param>
157+
/// <param name="api">The twitch api.</param>
158+
/// <param name="token">The cancellation token.</param>
159+
/// <returns>
160+
/// A redirect to the nullinside website.
161+
/// Errors:
162+
/// 2 = Internal error generating token.
163+
/// 3 = Code was invalid
164+
/// 4 = Twitch account has no email
165+
/// </returns>
166+
[AllowAnonymous]
167+
[HttpPost]
168+
[Route("twitch-login/twitch-streaming-tools")]
169+
public async Task<IActionResult> TwitchStreamingToolsRefreshToken(string refreshToken, [FromServices] ITwitchApiProxy api,
170+
CancellationToken token = new()) {
171+
string? siteUrl = _configuration.GetValue<string>("Api:SiteUrl");
172+
api.OAuth = new() {
173+
AccessToken = null,
174+
RefreshToken = refreshToken,
175+
ExpiresUtc = DateTime.MinValue
176+
};
177+
178+
if (null == await api.RefreshAccessToken(token)) {
179+
return this.BadRequest();
148180
}
149181

150-
return Redirect($"{siteUrl}/user/login?token={api.OAuth?.AccessToken}&refresh={api.OAuth?.RefreshToken}&expiresUtc={api.OAuth?.ExpiresUtc?.ToString()}&desktop=true");
182+
return Ok(new {
183+
bearer = api.OAuth.AccessToken,
184+
refresh = api.OAuth.RefreshToken,
185+
expiresUtc = api.OAuth.ExpiresUtc
186+
});
151187
}
152188

153189
/// <summary>

src/Nullinside.Api/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
builder.EnableRetryOnFailure(3);
3232
}));
3333
builder.Services.AddScoped<IAuthorizationHandler, BasicAuthorizationHandler>();
34-
builder.Services.AddScoped<ITwitchApiProxy, TwitchApiProxy>();
34+
builder.Services.AddTransient<ITwitchApiProxy, TwitchApiProxy>();
3535
builder.Services.AddAuthentication()
3636
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Bearer", _ => { });
3737
builder.Services.AddScoped<IDockerProxy, DockerProxy>();

0 commit comments

Comments
 (0)