✨ Self-contained oidc receiver page
This commit is contained in:
		| @@ -205,7 +205,7 @@ public class ConnectionController( | ||||
|             // Login existing user | ||||
|             var session = await auth.CreateSessionAsync(connection.Account, clock.GetCurrentInstant()); | ||||
|             var token = auth.CreateToken(session); | ||||
|             return Redirect($"/?token={token}"); | ||||
|             return Redirect($"/auth/token?token={token}"); | ||||
|         } | ||||
|  | ||||
|         // Register new user | ||||
| @@ -228,7 +228,7 @@ public class ConnectionController( | ||||
|  | ||||
|         var loginSession = await auth.CreateSessionAsync(account, clock.GetCurrentInstant()); | ||||
|         var loginToken = auth.CreateToken(loginSession); | ||||
|         return Redirect($"/?token={loginToken}"); | ||||
|         return Redirect($"/auth/token?token={loginToken}"); | ||||
|     } | ||||
|  | ||||
|     private static async Task<OidcCallbackData> ExtractCallbackData(HttpRequest request) | ||||
|   | ||||
| @@ -11,9 +11,18 @@ namespace DysonNetwork.Sphere.Auth.OpenId; | ||||
| /// <summary> | ||||
| /// Base service for OpenID Connect authentication providers | ||||
| /// </summary> | ||||
| public abstract class OidcService(IConfiguration configuration, IHttpClientFactory httpClientFactory, AppDatabase db) | ||||
| public abstract class OidcService | ||||
| { | ||||
|     protected readonly IHttpClientFactory _httpClientFactory = httpClientFactory; | ||||
|     protected readonly IConfiguration _configuration; | ||||
|     protected readonly IHttpClientFactory _httpClientFactory; | ||||
|     protected readonly AppDatabase _db; | ||||
|  | ||||
|     protected OidcService(IConfiguration configuration, IHttpClientFactory httpClientFactory, AppDatabase db) | ||||
|     { | ||||
|         _configuration = configuration; | ||||
|         _httpClientFactory = httpClientFactory; | ||||
|         _db = db; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the unique identifier for this provider | ||||
| @@ -47,9 +56,9 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto | ||||
|     { | ||||
|         return new ProviderConfiguration | ||||
|         { | ||||
|             ClientId = configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "", | ||||
|             ClientSecret = configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "", | ||||
|             RedirectUri = configuration["BaseUrl"] + "/auth/callback/" + ProviderName | ||||
|                         ClientId = _configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "", | ||||
|                         ClientSecret = _configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "", | ||||
|                         RedirectUri = _configuration["BaseUrl"] + "/auth/callback/" + ProviderName | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| @@ -58,7 +67,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto | ||||
|     /// </summary> | ||||
|     protected async Task<OidcDiscoveryDocument?> GetDiscoveryDocumentAsync() | ||||
|     { | ||||
|         var client = httpClientFactory.CreateClient(); | ||||
|         var client = _httpClientFactory.CreateClient(); | ||||
|         var response = await client.GetAsync(DiscoveryEndpoint); | ||||
|         response.EnsureSuccessStatusCode(); | ||||
|         return await response.Content.ReadFromJsonAsync<OidcDiscoveryDocument>(); | ||||
| @@ -78,7 +87,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto | ||||
|             throw new InvalidOperationException("Token endpoint not found in discovery document"); | ||||
|         } | ||||
|  | ||||
|         var client = httpClientFactory.CreateClient(); | ||||
|         var client = _httpClientFactory.CreateClient(); | ||||
|         var content = new FormUrlEncodedContent(BuildTokenRequestParameters(code, config, codeVerifier)); | ||||
|  | ||||
|         var response = await client.PostAsync(discoveryDocument.TokenEndpoint, content); | ||||
| @@ -169,7 +178,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto | ||||
|     ) | ||||
|     { | ||||
|         // Create or update the account connection | ||||
|         var connection = await db.AccountConnections | ||||
|                 var connection = await _db.AccountConnections | ||||
|             .FirstOrDefaultAsync(c => c.Provider == ProviderName && | ||||
|                                       c.ProvidedIdentifier == userInfo.UserId && | ||||
|                                       c.AccountId == account.Id | ||||
| @@ -186,7 +195,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto | ||||
|                 LastUsedAt = SystemClock.Instance.GetCurrentInstant(), | ||||
|                 AccountId = account.Id | ||||
|             }; | ||||
|             await db.AccountConnections.AddAsync(connection); | ||||
|                         await _db.AccountConnections.AddAsync(connection); | ||||
|         } | ||||
|  | ||||
|         // Create a challenge that's already completed | ||||
| @@ -206,7 +215,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto | ||||
|             UserAgent = request.Request.Headers.UserAgent, | ||||
|         }; | ||||
|  | ||||
|         await db.AuthChallenges.AddAsync(challenge); | ||||
|                 await _db.AuthChallenges.AddAsync(challenge); | ||||
|  | ||||
|         // Create a session | ||||
|         var session = new Session | ||||
| @@ -217,8 +226,8 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto | ||||
|             Challenge = challenge | ||||
|         }; | ||||
|  | ||||
|         await db.AuthSessions.AddAsync(session); | ||||
|         await db.SaveChangesAsync(); | ||||
|                 await _db.AuthSessions.AddAsync(session); | ||||
|                 await _db.SaveChangesAsync(); | ||||
|  | ||||
|         return session; | ||||
|     } | ||||
|   | ||||
							
								
								
									
										50
									
								
								DysonNetwork.Sphere/Pages/Auth/Token.cshtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								DysonNetwork.Sphere/Pages/Auth/Token.cshtml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| @page "/auth/token" | ||||
| @model DysonNetwork.Sphere.Pages.Auth.TokenModel | ||||
| @{ | ||||
|     ViewData["Title"] = "Authentication Successful"; | ||||
|     Layout = "_Layout"; | ||||
| } | ||||
|  | ||||
| <div class="h-full flex items-center justify-center"> | ||||
|     <div class="max-w-lg w-full mx-auto p-6 text-center"> | ||||
|         <h1 class="text-2xl font-bold text-gray-900 dark:text-white">Authentication Successful</h1> | ||||
|         <p class="mb-6 text-gray-900 dark:text-white">You can now close this window and return to the application.</p> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| @section Scripts { | ||||
|     <script> | ||||
|         (function() { | ||||
|             const urlParams = new URLSearchParams(window.location.search); | ||||
|             const token = urlParams.get('token'); | ||||
|  | ||||
|             if (token) { | ||||
|                 console.log("Authentication token received."); | ||||
|  | ||||
|                 // For WebView2/UWP apps that can handle window.external.notify | ||||
|                 if (window.external && typeof window.external.notify === 'function') { | ||||
|                     try { | ||||
|                         window.external.notify(token); | ||||
|                         console.log("Token sent via window.external.notify."); | ||||
|                         return; // Exit after successful notification | ||||
|                     } catch (e) { | ||||
|                         console.error("Failed to send token via window.external.notify:", e); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // For mobile apps that use custom URI schemes | ||||
|                 try { | ||||
|                     const customSchemeUrl = `dyson://auth?token=${encodeURIComponent(token)}`; | ||||
|                     window.location.href = customSchemeUrl; | ||||
|                     console.log("Attempting to redirect to custom scheme:", customSchemeUrl); | ||||
|                 } catch (e) { | ||||
|                     console.error("Failed to redirect to custom scheme:", e); | ||||
|                 } | ||||
|  | ||||
|             } else { | ||||
|                 console.error("Authentication token not found in URL."); | ||||
|                 document.querySelector('p').innerText = "Authentication failed: No token was provided."; | ||||
|             } | ||||
|         })(); | ||||
|     </script> | ||||
| } | ||||
							
								
								
									
										11
									
								
								DysonNetwork.Sphere/Pages/Auth/Token.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								DysonNetwork.Sphere/Pages/Auth/Token.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Pages.Auth | ||||
| { | ||||
|     public class TokenModel : PageModel | ||||
|     { | ||||
|         public void OnGet() | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user