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