-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from diogotr7/feature/discord-streamkit
Discord - removed need for custom client id and secret
- Loading branch information
Showing
17 changed files
with
732 additions
and
339 deletions.
There are no files selected for viewing
117 changes: 42 additions & 75 deletions
117
src/Artemis.Plugins.Modules.Discord/Authentication/DiscordAuthClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,112 +1,79 @@ | ||
using Artemis.Core; | ||
using Artemis.Core; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
|
||
namespace Artemis.Plugins.Modules.Discord.Authentication; | ||
|
||
public class DiscordAuthClient : IDisposable | ||
public class DiscordAuthClient : DiscordAuthClientBase | ||
{ | ||
private readonly string _clientId; | ||
public override string ClientId { get; } | ||
public override string Origin => throw new NotSupportedException("Discord does not support a custom origin"); | ||
private readonly string _clientSecret; | ||
private readonly PluginSetting<SavedToken> _token; | ||
private readonly HttpClient _httpClient; | ||
|
||
public DiscordAuthClient(string clientId, string clientSecret, PluginSetting<SavedToken> token) | ||
public DiscordAuthClient(PluginSettings settings) : base(settings.GetSetting<SavedToken>("DiscordToken")) | ||
{ | ||
_clientId = clientId; | ||
_clientSecret = clientSecret; | ||
_token = token; | ||
_httpClient = new(); | ||
} | ||
|
||
public bool HasToken => _token.Value != null; | ||
|
||
public bool IsTokenValid => HasToken && _token.Value!.ExpirationDate >= DateTime.UtcNow; | ||
|
||
public string AccessToken => _token.Value?.AccessToken ?? throw new InvalidOperationException("No token available"); | ||
|
||
public async Task RefreshTokenIfNeededAsync() | ||
{ | ||
if (!HasToken) | ||
return; | ||
|
||
if (_token.Value!.ExpirationDate >= DateTime.UtcNow.AddDays(1)) | ||
return; | ||
|
||
await RefreshAccessTokenAsync(); | ||
var clientIdSetting = settings.GetSetting<string>("DiscordClientId"); | ||
var clientSecretSetting = settings.GetSetting<string>("DiscordClientSecret"); | ||
|
||
if (!AreClientIdAndSecretValid(clientIdSetting, clientSecretSetting)) | ||
throw new InvalidOperationException("Invalid client id or secret. Please check your settings."); | ||
|
||
ClientId = clientIdSetting.Value!; | ||
_clientSecret = clientSecretSetting.Value!; | ||
} | ||
|
||
public async Task<TokenResponse> GetAccessTokenAsync(string challengeCode) | ||
private bool AreClientIdAndSecretValid(PluginSetting<string> clientId, PluginSetting<string> clientSecret) | ||
{ | ||
var token = await GetCredentialsAsync("authorization_code", "code", challengeCode); | ||
SaveToken(token); | ||
return token; | ||
return clientId.Value?.All(c => char.IsDigit(c)) == true && clientSecret.Value?.Length > 0; | ||
} | ||
|
||
public async Task RefreshAccessTokenAsync() | ||
{ | ||
if (!HasToken) | ||
throw new InvalidOperationException("No token to refresh"); | ||
|
||
TokenResponse token = await GetCredentialsAsync("refresh_token", "refresh_token", _token.Value!.RefreshToken); | ||
SaveToken(token); | ||
} | ||
|
||
private async Task<TokenResponse> GetCredentialsAsync(string grantType, string secretType, string secret) | ||
public override async Task<TokenResponse> GetAccessTokenAsync(string challengeCode) | ||
{ | ||
Dictionary<string, string> values = new() | ||
{ | ||
["grant_type"] = grantType, | ||
[secretType] = secret, | ||
["client_id"] = _clientId, | ||
["grant_type"] = "authorization_code", | ||
["code"] = challengeCode, | ||
["client_id"] = ClientId, | ||
["client_secret"] = _clientSecret | ||
}; | ||
|
||
using HttpResponseMessage response = await _httpClient.PostAsync("https://discord.com/api/oauth2/token", new FormUrlEncodedContent(values)); | ||
string responseString = await response.Content.ReadAsStringAsync(); | ||
using var response = await HttpClient.PostAsync("https://discord.com/api/oauth2/token", new FormUrlEncodedContent(values)); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
if (!response.IsSuccessStatusCode) | ||
{ | ||
throw new UnauthorizedAccessException(responseString); | ||
} | ||
|
||
return JsonConvert.DeserializeObject<TokenResponse>(responseString)!; | ||
var token = JsonConvert.DeserializeObject<TokenResponse>(responseString)!; | ||
SaveToken(token); | ||
return token; | ||
} | ||
|
||
private void SaveToken(TokenResponse newToken) | ||
public override async Task RefreshAccessTokenAsync() | ||
{ | ||
_token.Value = new SavedToken | ||
if (!HasToken) | ||
throw new InvalidOperationException("No token to refresh"); | ||
|
||
Dictionary<string, string> values = new() | ||
{ | ||
AccessToken = newToken.AccessToken, | ||
RefreshToken = newToken.RefreshToken, | ||
ExpirationDate = DateTime.UtcNow.AddSeconds(newToken.ExpiresIn) | ||
["grant_type"] = "refresh_token", | ||
["refresh_token"] = Token.Value!.RefreshToken, | ||
["client_id"] = ClientId, | ||
["client_secret"] = _clientSecret | ||
}; | ||
_token.Save(); | ||
} | ||
|
||
#region IDisposable | ||
private bool disposedValue; | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (!disposedValue) | ||
using var response = await HttpClient.PostAsync("https://discord.com/api/oauth2/token", new FormUrlEncodedContent(values)); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
if (!response.IsSuccessStatusCode) | ||
{ | ||
if (disposing) | ||
{ | ||
_httpClient?.Dispose(); | ||
} | ||
|
||
disposedValue = true; | ||
throw new UnauthorizedAccessException(responseString); | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||
Dispose(disposing: true); | ||
GC.SuppressFinalize(this); | ||
var token = JsonConvert.DeserializeObject<TokenResponse>(responseString)!; | ||
SaveToken(token); | ||
} | ||
#endregion | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
src/Artemis.Plugins.Modules.Discord/Authentication/IDiscordAuthClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
using System; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using Artemis.Core; | ||
|
||
namespace Artemis.Plugins.Modules.Discord.Authentication; | ||
|
||
public interface IDiscordAuthClient : IDisposable | ||
{ | ||
string ClientId { get; } | ||
string Origin { get; } | ||
bool HasToken { get; } | ||
string AccessToken { get; } | ||
Task<TokenResponse> GetAccessTokenAsync(string challengeCode); | ||
Task RefreshAccessTokenAsync(); | ||
} | ||
|
||
public abstract class DiscordAuthClientBase : IDiscordAuthClient | ||
{ | ||
protected HttpClient HttpClient = new(); | ||
protected readonly PluginSetting<SavedToken> Token; | ||
protected DiscordAuthClientBase(PluginSetting<SavedToken> token) | ||
{ | ||
Token = token; | ||
} | ||
|
||
public abstract string ClientId { get; } | ||
public abstract string Origin { get; } | ||
public bool HasToken => Token.Value != null; | ||
public string AccessToken => Token.Value?.AccessToken ?? throw new InvalidOperationException("No token available"); | ||
public abstract Task<TokenResponse> GetAccessTokenAsync(string challengeCode); | ||
public abstract Task RefreshAccessTokenAsync(); | ||
public void Dispose() | ||
{ | ||
HttpClient.Dispose(); | ||
} | ||
|
||
protected void SaveToken(TokenResponse newToken) | ||
{ | ||
Token.Value = new SavedToken | ||
{ | ||
AccessToken = newToken.AccessToken, | ||
RefreshToken = newToken.RefreshToken, | ||
ExpirationDate = DateTime.UtcNow.AddSeconds(newToken.ExpiresIn) | ||
}; | ||
Token.Save(); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/Artemis.Plugins.Modules.Discord/Authentication/LogitechAuthClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Artemis.Core; | ||
using Newtonsoft.Json; | ||
|
||
namespace Artemis.Plugins.Modules.Discord.Authentication; | ||
|
||
public class LogitechAuthClient : DiscordAuthClientBase | ||
{ | ||
private const string TokenEndpoint = "https://ymj1tb3arf.execute-api.us-east-1.amazonaws.com/prod/create_discord_access_token"; | ||
|
||
public LogitechAuthClient(PluginSettings token) : base(token.GetSetting<SavedToken>("DiscordTokenLogitech")) | ||
{ | ||
} | ||
|
||
public override string ClientId => "227491271223017472"; | ||
public override string Origin => "http://localhost"; | ||
|
||
public override async Task<TokenResponse> GetAccessTokenAsync(string challengeCode) | ||
{ | ||
var values = new Dictionary<string, string> | ||
{ | ||
["code"] = challengeCode | ||
}; | ||
|
||
using var httpContent = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json"); | ||
using var response = await HttpClient.PostAsync(TokenEndpoint, httpContent); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
var token = JsonConvert.DeserializeObject<TokenResponse>(responseString); | ||
SaveToken(token); | ||
return token; | ||
} | ||
|
||
public override async Task RefreshAccessTokenAsync() | ||
{ | ||
var values = new Dictionary<string, string> | ||
{ | ||
["refresh_token"] = Token.Value.RefreshToken | ||
}; | ||
|
||
using var httpContent = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json"); | ||
using var response = await HttpClient.PostAsync(TokenEndpoint, httpContent); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
var token = JsonConvert.DeserializeObject<TokenResponse>(responseString); | ||
SaveToken(token); | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/Artemis.Plugins.Modules.Discord/Authentication/RazerAuthClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using Artemis.Core; | ||
using Newtonsoft.Json; | ||
|
||
namespace Artemis.Plugins.Modules.Discord.Authentication; | ||
|
||
public class RazerAuthClient : DiscordAuthClientBase | ||
{ | ||
private const string RefreshEndpoint = "https://chroma.razer.com/discord/refreshtoken.php"; | ||
private const string GrantEndpoint = "https://chroma.razer.com/discord/grant.php"; | ||
private const string RedirectUri = "http://chroma.razer.com/discord/"; | ||
|
||
public RazerAuthClient(PluginSettings token) : base(token.GetSetting<SavedToken>("DiscordTokenRazer")) | ||
{ | ||
} | ||
|
||
public override string ClientId => "331511201655685132"; | ||
public override string Origin => "http://chroma.razer.com"; | ||
|
||
public override async Task<TokenResponse> GetAccessTokenAsync(string challengeCode) | ||
{ | ||
var values = new Dictionary<string, string> | ||
{ | ||
["client_id"] = ClientId, | ||
["grant_type"] = "authorization_code", | ||
["code"] = challengeCode, | ||
["redirect_uri"] = RedirectUri | ||
}; | ||
|
||
using var response = await HttpClient.PostAsync(GrantEndpoint, new FormUrlEncodedContent(values)); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
var token = JsonConvert.DeserializeObject<TokenResponse>(responseString); | ||
SaveToken(token); | ||
return token; | ||
} | ||
|
||
public override async Task RefreshAccessTokenAsync() | ||
{ | ||
var values = new Dictionary<string, string> | ||
{ | ||
["client_id"] = ClientId, | ||
["grant_type"] = "refresh_token", | ||
["refresh_token"] = Token.Value.RefreshToken, | ||
["redirect_uri"] = RedirectUri | ||
}; | ||
|
||
using var response = await HttpClient.PostAsync(RefreshEndpoint, new FormUrlEncodedContent(values)); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
var tkn = JsonConvert.DeserializeObject<TokenResponse>(responseString); | ||
SaveToken(tkn); | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
src/Artemis.Plugins.Modules.Discord/Authentication/SteelseriesAuthClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using Artemis.Core; | ||
using Newtonsoft.Json; | ||
|
||
namespace Artemis.Plugins.Modules.Discord.Authentication; | ||
|
||
public class SteelseriesAuthClient : DiscordAuthClientBase | ||
{ | ||
private const string TokenEndpoint = "https://id.steelseries.com/discord/auth"; | ||
|
||
public SteelseriesAuthClient(PluginSettings token) : base(token.GetSetting<SavedToken>("DiscordTokenSteelseries")) | ||
{ | ||
} | ||
|
||
public override string ClientId => "211138759029293067"; | ||
public override string Origin => "https://steelseries.com"; | ||
|
||
public override async Task<TokenResponse> GetAccessTokenAsync(string challengeCode) | ||
{ | ||
var values = new Dictionary<string, string> | ||
{ | ||
["client_id"] = ClientId, | ||
["grant_type"] = "authorization_code", | ||
["code"] = challengeCode | ||
}; | ||
|
||
using var response = await HttpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(values)); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
var token = JsonConvert.DeserializeObject<TokenResponse>(responseString); | ||
SaveToken(token); | ||
return token; | ||
} | ||
|
||
public override async Task RefreshAccessTokenAsync() | ||
{ | ||
var values = new Dictionary<string, string> | ||
{ | ||
["client_id"] = ClientId, | ||
["grant_type"] = "refresh_token", | ||
["refresh_token"] = Token.Value.RefreshToken | ||
}; | ||
|
||
using var response = await HttpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(values)); | ||
var responseString = await response.Content.ReadAsStringAsync(); | ||
var token = JsonConvert.DeserializeObject<TokenResponse>(responseString); | ||
SaveToken(token); | ||
} | ||
} |
Oops, something went wrong.