🐛 Fix infinite oauth token reconnect websocket due to missing device id 🐛 Fix IP forwarded headers didn't work
96 lines
3.9 KiB
C#
96 lines
3.9 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
|
|
namespace DysonNetwork.Sphere.Auth.OpenId;
|
|
|
|
public class DiscordOidcService : OidcService
|
|
{
|
|
public DiscordOidcService(IConfiguration configuration, IHttpClientFactory httpClientFactory, AppDatabase db)
|
|
: base(configuration, httpClientFactory, db)
|
|
{
|
|
}
|
|
|
|
public override string ProviderName => "Discord";
|
|
protected override string DiscoveryEndpoint => ""; // Discord doesn't have a standard OIDC discovery endpoint
|
|
protected override string ConfigSectionName => "Discord";
|
|
|
|
public override string GetAuthorizationUrl(string state, string nonce)
|
|
{
|
|
var config = GetProviderConfig();
|
|
var queryParams = new Dictionary<string, string>
|
|
{
|
|
{ "client_id", config.ClientId },
|
|
{ "redirect_uri", config.RedirectUri },
|
|
{ "response_type", "code" },
|
|
{ "scope", "identify email" },
|
|
{ "state", state },
|
|
};
|
|
|
|
var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
|
|
return $"https://discord.com/api/oauth2/authorize?{queryString}";
|
|
}
|
|
|
|
public override async Task<OidcUserInfo> ProcessCallbackAsync(OidcCallbackData callbackData)
|
|
{
|
|
var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code);
|
|
if (tokenResponse?.AccessToken == null)
|
|
{
|
|
throw new InvalidOperationException("Failed to obtain access token from Discord");
|
|
}
|
|
|
|
var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken);
|
|
|
|
userInfo.AccessToken = tokenResponse.AccessToken;
|
|
userInfo.RefreshToken = tokenResponse.RefreshToken;
|
|
|
|
return userInfo;
|
|
}
|
|
|
|
protected override async Task<OidcTokenResponse?> ExchangeCodeForTokensAsync(string code, string? codeVerifier = null)
|
|
{
|
|
var config = GetProviderConfig();
|
|
var client = _httpClientFactory.CreateClient();
|
|
|
|
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
|
{
|
|
{ "client_id", config.ClientId },
|
|
{ "client_secret", config.ClientSecret },
|
|
{ "grant_type", "authorization_code" },
|
|
{ "code", code },
|
|
{ "redirect_uri", config.RedirectUri },
|
|
});
|
|
|
|
var response = await client.PostAsync("https://discord.com/api/oauth2/token", content);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
return await response.Content.ReadFromJsonAsync<OidcTokenResponse>();
|
|
}
|
|
|
|
private async Task<OidcUserInfo> GetUserInfoAsync(string accessToken)
|
|
{
|
|
var client = _httpClientFactory.CreateClient();
|
|
var request = new HttpRequestMessage(HttpMethod.Get, "https://discord.com/api/users/@me");
|
|
request.Headers.Add("Authorization", $"Bearer {accessToken}");
|
|
|
|
var response = await client.SendAsync(request);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
var json = await response.Content.ReadAsStringAsync();
|
|
var discordUser = JsonDocument.Parse(json).RootElement;
|
|
|
|
var userId = discordUser.GetProperty("id").GetString() ?? "";
|
|
var avatar = discordUser.TryGetProperty("avatar", out var avatarElement) ? avatarElement.GetString() : null;
|
|
|
|
return new OidcUserInfo
|
|
{
|
|
UserId = userId,
|
|
Email = (discordUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null) ?? "",
|
|
EmailVerified = discordUser.TryGetProperty("verified", out var verifiedElement) && verifiedElement.GetBoolean(),
|
|
DisplayName = (discordUser.TryGetProperty("global_name", out var globalNameElement) ? globalNameElement.GetString() : null) ?? "",
|
|
PreferredUsername = discordUser.GetProperty("username").GetString() ?? "",
|
|
ProfilePictureUrl = !string.IsNullOrEmpty(avatar) ? $"https://cdn.discordapp.com/avatars/{userId}/{avatar}.png" : "",
|
|
Provider = ProviderName
|
|
};
|
|
}
|
|
}
|