From 3d724a468619c922a98edd87772b8d7204745e49 Mon Sep 17 00:00:00 2001 From: Dmitri Shimanski Date: Fri, 22 Aug 2025 08:51:44 +0300 Subject: [PATCH] Refactor OAuth handling and add GetAuthorizationUrl method --- DiscordOAuth.cs | 27 ++++++++++++++++++++++++--- Entities/DiscordSession.cs | 22 ---------------------- Entities/DiscordUser.cs | 2 +- Entities/OAuthToken.cs | 22 +++++++++++++++++----- Entities/TokenDto.cs | 16 ++++++++++++++++ Interfaces/IDiscord.cs | 1 + Interfaces/Models/ISession.cs | 2 -- Interfaces/Models/IUser.cs | 2 +- yawaflua.Discord.Net.csproj | 2 +- 9 files changed, 61 insertions(+), 35 deletions(-) create mode 100644 Entities/TokenDto.cs diff --git a/DiscordOAuth.cs b/DiscordOAuth.cs index 7c5c82d..e036a1f 100644 --- a/DiscordOAuth.cs +++ b/DiscordOAuth.cs @@ -103,10 +103,10 @@ public class DiscordOAuth : IDiscord var response = await _httpClient.PostAsync("https://discord.com/api/oauth2/token", content); var responseString = await response.Content.ReadAsStringAsync(); - var authToken = JsonSerializer.Deserialize(responseString); + var authToken = JsonSerializer.Deserialize(responseString); AccessToken = authToken?.AccessToken; - token = authToken; - return authToken; + token = OAuthToken.FromDTO(authToken, _httpClient, ClientId, ClientSecret); + return token; } public ISession CreateSession() @@ -116,6 +116,27 @@ public class DiscordOAuth : IDiscord else throw new InvalidOperationException("Token is not set. Please call GetTokenAsync first."); } + + public string GetAuthorizationUrl(string state) + { + + var uri = new UriBuilder("https://discord.com/api/oauth2/authorize?"); + + var queryParameters = HttpUtility.ParseQueryString(uri.Query); + + queryParameters["client_id"] = ClientId.ToString(); + queryParameters["redirect_uri"] = RedirectUri; + queryParameters["response_type"] = "code"; + queryParameters["scope"] = Scopes.ToString(); + queryParameters["state"] = state; + queryParameters["prompt"] = Prompt ? "consent" : "none"; + + return uri + string.Join("&", queryParameters.AllKeys + .SelectMany(key => queryParameters.GetValues(key)! + .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value)))) + .ToArray()); + + } } \ No newline at end of file diff --git a/Entities/DiscordSession.cs b/Entities/DiscordSession.cs index c98f4e1..6e0fac3 100644 --- a/Entities/DiscordSession.cs +++ b/Entities/DiscordSession.cs @@ -70,28 +70,6 @@ internal class DiscordSession (IToken token, HttpClient httpClient, ScopesBuilde return await _req("users/@me/connections"); } - public string GetAuthorizationUrl(string state) - { - - NameValueCollection query = new() - { - ["client_id"] = clientId.ToString(), - ["redirect_uri"] = redirectUri, - ["response_type"] = "code", - ["scope"] = scopes.ToString(), - ["state"] = state, - ["prompt"] = prompt ? "consent" : "none" - }; - - var uriBuilder = new UriBuilder("https://discord.com/api/oauth2/authorize") - { - Query = query.ToString() - }; - - return uriBuilder.ToString(); - - } - public IToken GetToken(CancellationToken cancellationToken = default) { if (token.AccessToken is null) diff --git a/Entities/DiscordUser.cs b/Entities/DiscordUser.cs index 8f798df..d56335d 100644 --- a/Entities/DiscordUser.cs +++ b/Entities/DiscordUser.cs @@ -7,7 +7,7 @@ namespace yawaflua.Discord.Net.Entities; internal class DiscordUser : IUser { [JsonPropertyName("id")] - public ulong Id { get; set; } + public string Id { get; set; } [JsonPropertyName("username")] public string Username { get; set; } diff --git a/Entities/OAuthToken.cs b/Entities/OAuthToken.cs index ae76381..0f17a4c 100644 --- a/Entities/OAuthToken.cs +++ b/Entities/OAuthToken.cs @@ -7,15 +7,27 @@ namespace yawaflua.Discord.Net.Entities; internal class OAuthToken (HttpClient client, ulong ClientId, string ClientSecret) : IToken { - [JsonPropertyName("access_token")] public string AccessToken { get; set; } + public string AccessToken { get; set; } - [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } + public int ExpiresIn { get; set; } - [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; } + public string RefreshToken { get; set; } - [JsonPropertyName("scope")] public string Scope { get; set; } + public string Scope { get; set; } - [JsonPropertyName("token_type")] public string TokenType { get; set; } + public string TokenType { get; set; } + + public static OAuthToken FromDTO(TokenDto dto, HttpClient client, ulong ClientId, string ClientSecret) + { + return new(client, ClientId, ClientSecret) + { + AccessToken = dto.AccessToken, + ExpiresIn = dto.ExpiresIn, + RefreshToken = dto.RefreshToken, + Scope = dto.Scope, + TokenType = dto.TokenType + }; + } public Task RevokeAsync(CancellationToken cancellationToken = default) { using var request = new HttpRequestMessage(HttpMethod.Post, "https://discord.com/api/oauth2/token/revoke") diff --git a/Entities/TokenDto.cs b/Entities/TokenDto.cs new file mode 100644 index 0000000..8c754d7 --- /dev/null +++ b/Entities/TokenDto.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace yawaflua.Discord.Net.Entities; + +public class TokenDto +{ + [JsonPropertyName("access_token")] public string AccessToken { get; set; } + + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } + + [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; } + + [JsonPropertyName("scope")] public string Scope { get; set; } + + [JsonPropertyName("token_type")] public string TokenType { get; set; } +} \ No newline at end of file diff --git a/Interfaces/IDiscord.cs b/Interfaces/IDiscord.cs index ae70bc1..8ccd919 100644 --- a/Interfaces/IDiscord.cs +++ b/Interfaces/IDiscord.cs @@ -6,4 +6,5 @@ public interface IDiscord { Task GetTokenAsync(string code); ISession? CreateSession(); + string GetAuthorizationUrl(string state); } \ No newline at end of file diff --git a/Interfaces/Models/ISession.cs b/Interfaces/Models/ISession.cs index 58f57f0..9ed4e1b 100644 --- a/Interfaces/Models/ISession.cs +++ b/Interfaces/Models/ISession.cs @@ -9,8 +9,6 @@ public interface ISession Task GetConnectionAsync(CancellationToken cancellationToken = default); - - string GetAuthorizationUrl(string state); IToken GetToken(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Interfaces/Models/IUser.cs b/Interfaces/Models/IUser.cs index 2a7b6d2..7e0ddc9 100644 --- a/Interfaces/Models/IUser.cs +++ b/Interfaces/Models/IUser.cs @@ -8,7 +8,7 @@ public interface IUser /// the user's id /// /// User-object - public ulong Id { get; set; } + public string Id { get; set; } /// /// the user's username, not unique across the platform diff --git a/yawaflua.Discord.Net.csproj b/yawaflua.Discord.Net.csproj index 55a1ba0..a568813 100644 --- a/yawaflua.Discord.Net.csproj +++ b/yawaflua.Discord.Net.csproj @@ -18,7 +18,7 @@ GIT Discord-OAuth2;Discord-OAuth-2;Discord-OAuth;DiscordOAuth;Discord;yawaflua;OAuth;OAuth-2;OAuth2 true - 1.0.5 + 1.0.6