From b9edf51f05f18652610bac651aed5ab2bfef6f34 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 2 Nov 2025 14:58:23 +0800 Subject: [PATCH] :recycle: Refactor OpenID: Phase 3: Async Flow Modernization - Added async GetAuthorizationUrlAsync() methods to all OIDC providers - Updated base OidcService with abstract async contract and backward-compatible sync wrapper - Modified OidcController to use async authorization URL generation - Removed sync blocks using .GetAwaiter().GetResult() in Google provider - Maintained backward compatibility with existing sync method calls - Eliminated thread blocking and improved async flow throughout auth pipeline - Enhanced scalability by allowing non-blocking async authorization URL generation --- .../Auth/OpenId/AfdianOidcService.cs | 7 ++++- .../Auth/OpenId/AppleOidcService.cs | 26 ++++++++++++++++++- .../Auth/OpenId/DiscordOidcService.cs | 7 ++++- .../Auth/OpenId/GitHubOidcService.cs | 7 ++++- .../Auth/OpenId/GoogleOidcService.cs | 6 ++--- .../Auth/OpenId/MicrosoftOidcService.cs | 23 +++++++++++++++- .../Auth/OpenId/OidcController.cs | 6 ++--- DysonNetwork.Pass/Auth/OpenId/OidcService.cs | 12 +++++++-- 8 files changed, 81 insertions(+), 13 deletions(-) diff --git a/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs index c054015..2a13f05 100644 --- a/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs @@ -17,6 +17,11 @@ public class AfdianOidcService( protected override string DiscoveryEndpoint => ""; // Afdian doesn't have a standard OIDC discovery endpoint protected override string ConfigSectionName => "Afdian"; + public override Task GetAuthorizationUrlAsync(string state, string nonce) + { + return Task.FromResult(GetAuthorizationUrl(state, nonce)); + } + public override string GetAuthorizationUrl(string state, string nonce) { var config = GetProviderConfig(); @@ -90,4 +95,4 @@ public class AfdianOidcService( throw; } } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs index e68ec8d..620d8fe 100644 --- a/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs @@ -27,6 +27,30 @@ public class AppleOidcService( protected override string DiscoveryEndpoint => "https://appleid.apple.com/.well-known/openid-configuration"; protected override string ConfigSectionName => "Apple"; + public override async Task GetAuthorizationUrlAsync(string state, string nonce) + { + var config = GetProviderConfig(); + var discoveryDocument = await GetDiscoveryDocumentAsync(); + + if (discoveryDocument?.AuthorizationEndpoint == null) + { + throw new InvalidOperationException("Authorization endpoint not found in discovery document"); + } + + var queryParams = BuildAuthorizationParameters( + config.ClientId, + config.RedirectUri, + "name email", + "code id_token", + state, + nonce, + "form_post" + ); + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}"; + } + public override string GetAuthorizationUrl(string state, string nonce) { var config = GetProviderConfig(); @@ -276,4 +300,4 @@ public class AppleKey return Convert.FromBase64String(output); } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs index eba3fc3..427f262 100644 --- a/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs @@ -16,6 +16,11 @@ public class DiscordOidcService( protected override string DiscoveryEndpoint => ""; // Discord doesn't have a standard OIDC discovery endpoint protected override string ConfigSectionName => "Discord"; + public override Task GetAuthorizationUrlAsync(string state, string nonce) + { + return Task.FromResult(GetAuthorizationUrl(state, nonce)); + } + public override string GetAuthorizationUrl(string state, string nonce) { var config = GetProviderConfig(); @@ -111,4 +116,4 @@ public class DiscordOidcService( Provider = ProviderName }; } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs index 24c1c07..0190605 100644 --- a/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs @@ -16,6 +16,11 @@ public class GitHubOidcService( protected override string DiscoveryEndpoint => ""; // GitHub doesn't have a standard OIDC discovery endpoint protected override string ConfigSectionName => "GitHub"; + public override Task GetAuthorizationUrlAsync(string state, string nonce) + { + return Task.FromResult(GetAuthorizationUrl(state, nonce)); + } + public override string GetAuthorizationUrl(string state, string nonce) { var config = GetProviderConfig(); @@ -123,4 +128,4 @@ public class GitHubOidcService( public bool Primary { get; set; } public bool Verified { get; set; } } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs index 86bc5ad..46ddb2a 100644 --- a/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs @@ -19,10 +19,10 @@ public class GoogleOidcService( protected override string DiscoveryEndpoint => "https://accounts.google.com/.well-known/openid-configuration"; protected override string ConfigSectionName => "Google"; - public override string GetAuthorizationUrl(string state, string nonce) + public override async Task GetAuthorizationUrlAsync(string state, string nonce) { var config = GetProviderConfig(); - var discoveryDocument = GetDiscoveryDocumentAsync().GetAwaiter().GetResult(); + var discoveryDocument = await GetDiscoveryDocumentAsync(); if (discoveryDocument?.AuthorizationEndpoint == null) { @@ -48,7 +48,7 @@ public class GoogleOidcService( // Store code verifier in cache for later token exchange var codeVerifierKey = $"pkce:{state}"; - cache.SetAsync(codeVerifierKey, codeVerifier, TimeSpan.FromMinutes(15)).GetAwaiter().GetResult(); + await cache.SetAsync(codeVerifierKey, codeVerifier, TimeSpan.FromMinutes(15)); var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}"; diff --git a/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs index 0d1b4b8..fd28ea9 100644 --- a/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs @@ -20,6 +20,27 @@ public class MicrosoftOidcService( protected override string ConfigSectionName => "Microsoft"; + public override async Task GetAuthorizationUrlAsync(string state, string nonce) + { + var config = GetProviderConfig(); + var discoveryDocument = await GetDiscoveryDocumentAsync(); + + if (discoveryDocument?.AuthorizationEndpoint == null) + throw new InvalidOperationException("Authorization endpoint not found in discovery document."); + + var queryParams = BuildAuthorizationParameters( + config.ClientId, + config.RedirectUri, + "openid profile email", + "code", + state, + nonce + ); + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}"; + } + public override string GetAuthorizationUrl(string state, string nonce) { var config = GetProviderConfig(); @@ -120,4 +141,4 @@ public class MicrosoftOidcService( Provider = ProviderName }; } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcController.cs b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs index c5b2f76..653d2fb 100644 --- a/DysonNetwork.Pass/Auth/OpenId/OidcController.cs +++ b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs @@ -43,7 +43,7 @@ public class OidcController( await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration); // The state parameter sent to the provider is the GUID key for the cache. - var authUrl = oidcService.GetAuthorizationUrl(state, nonce); + var authUrl = await oidcService.GetAuthorizationUrlAsync(state, nonce); return Redirect(authUrl); } else // Otherwise, proceed with the login / registration flow @@ -54,7 +54,7 @@ public class OidcController( // Create login state with return URL and device ID var oidcState = OidcState.ForLogin(returnUrl ?? "/", deviceId); await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration); - var authUrl = oidcService.GetAuthorizationUrl(state, nonce); + var authUrl = await oidcService.GetAuthorizationUrlAsync(state, nonce); return Redirect(authUrl); } } @@ -194,4 +194,4 @@ public class OidcController( return newAccount; } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcService.cs b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs index 24ee21c..63a1a1f 100644 --- a/DysonNetwork.Pass/Auth/OpenId/OidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs @@ -42,9 +42,17 @@ public abstract class OidcService( protected abstract string ConfigSectionName { get; } /// - /// Gets the authorization URL for initiating the authentication flow + /// Gets the authorization URL for initiating the authentication flow (async) /// - public abstract string GetAuthorizationUrl(string state, string nonce); + public abstract Task GetAuthorizationUrlAsync(string state, string nonce); + + /// + /// Gets the authorization URL for initiating the authentication flow (sync for backward compatibility) + /// + public virtual string GetAuthorizationUrl(string state, string nonce) + { + return GetAuthorizationUrlAsync(state, nonce).GetAwaiter().GetResult(); + } /// /// Builds common authorization URL query parameters