Skip to content

Commit

Permalink
Merge pull request #30 from diogotr7/feature/discord-streamkit
Browse files Browse the repository at this point in the history
Discord - removed need for custom client id and secret
  • Loading branch information
diogotr7 authored Nov 23, 2023
2 parents 70beb55 + d5da3f2 commit c3a45b7
Show file tree
Hide file tree
Showing 17 changed files with 732 additions and 339 deletions.
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
}
}
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();
}
}
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);
}
}
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);
}
}
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);
}
}
Loading

0 comments on commit c3a45b7

Please sign in to comment.