From 50d8f74a984869e192805a3c06140b29116c9dea Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 17 Jun 2025 20:01:20 +0800 Subject: [PATCH] :bug: Trying to fix connection issues --- .../Auth/OpenId/ConnectionController.cs | 44 ++++++++++++------- .../Auth/OpenId/OidcController.cs | 20 ++++++--- DysonNetwork.Sphere/wwwroot/css/styles.css | 36 ++++++++++++++- 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs b/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs index ecaad6f..b9114d0 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs +++ b/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs @@ -2,7 +2,7 @@ using DysonNetwork.Sphere.Account; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using DysonNetwork.Sphere.Auth.OpenId; +using DysonNetwork.Sphere.Storage; using NodaTime; namespace DysonNetwork.Sphere.Auth.OpenId; @@ -14,9 +14,13 @@ public class ConnectionController( AppDatabase db, IEnumerable oidcServices, AccountService accounts, - AuthService auth + AuthService auth, + ICacheService cacheService ) : ControllerBase { + private const string StateCachePrefix = "oidc-state:"; + private const string ReturnUrlCachePrefix = "oidc-returning:"; + private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15); [HttpGet] public async Task>> GetConnections() { @@ -142,10 +146,12 @@ public class ConnectionController( var state = Guid.NewGuid().ToString("N"); var nonce = Guid.NewGuid().ToString("N"); - HttpContext.Session.SetString($"oidc_state_{state}", $"{currentUser.Id}|{request.Provider}|{nonce}"); - + var stateValue = $"{currentUser.Id}|{request.Provider}|{nonce}"; var finalReturnUrl = !string.IsNullOrEmpty(request.ReturnUrl) ? request.ReturnUrl : "/settings/connections"; - HttpContext.Session.SetString($"oidc_return_url_{state}", finalReturnUrl); + + // Store state and return URL in cache + await cacheService.SetAsync($"{StateCachePrefix}{state}", stateValue, StateExpiration); + await cacheService.SetAsync($"{ReturnUrlCachePrefix}{state}", finalReturnUrl, StateExpiration); var authUrl = oidcService.GetAuthorizationUrl(state, nonce); @@ -169,14 +175,22 @@ public class ConnectionController( if (callbackData.State == null) return BadRequest("State parameter is missing."); - var sessionState = HttpContext.Session.GetString($"oidc_state_{callbackData.State!}"); - HttpContext.Session.Remove($"oidc_state_{callbackData.State}"); + // Get and validate state from cache + var stateKey = $"{StateCachePrefix}{callbackData.State}"; + var stateValue = await cacheService.GetAsync(stateKey); + if (string.IsNullOrEmpty(stateValue)) + { + return BadRequest("Invalid or expired state parameter"); + } - // If sessionState is present, it's a manual connection flow for an existing user. - if (sessionState == null) return await HandleLoginOrRegistration(provider, oidcService, callbackData); - var stateParts = sessionState.Split('|'); - if (stateParts.Length != 3 || !stateParts[1].Equals(provider, StringComparison.OrdinalIgnoreCase)) - return BadRequest("State mismatch."); + // Remove state from cache to prevent replay attacks + await cacheService.RemoveAsync(stateKey); + + var stateParts = stateValue.Split('|'); + if (stateParts.Length != 3) + { + return BadRequest("Invalid state format"); + } var accountId = Guid.Parse(stateParts[0]); return await HandleManualConnection(provider, oidcService, callbackData, accountId); @@ -259,9 +273,9 @@ public class ConnectionController( } // Clean up and redirect - var returnUrl = HttpContext.Session.GetString($"oidc_return_url_{callbackData.State}"); - HttpContext.Session.Remove($"oidc_return_url_{callbackData.State}"); - HttpContext.Session.Remove($"oidc_state_{callbackData.State}"); + var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}"; + var returnUrl = await cacheService.GetAsync(returnUrlKey); + await cacheService.RemoveAsync(returnUrlKey); return Redirect(string.IsNullOrEmpty(returnUrl) ? "/settings/connections" : returnUrl); } diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs b/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs index ada8bc1..a89d28f 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs +++ b/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs @@ -1,4 +1,5 @@ using DysonNetwork.Sphere.Account; +using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; @@ -12,12 +13,16 @@ public class OidcController( IServiceProvider serviceProvider, AppDatabase db, AccountService accounts, - AuthService authService + AuthService auth, + ICacheService cache ) : ControllerBase { + private const string StateCachePrefix = "oidc-state:"; + private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15); + [HttpGet("{provider}")] - public ActionResult SignIn([FromRoute] string provider, [FromQuery] string? returnUrl = "/") + public async Task SignIn([FromRoute] string provider, [FromQuery] string? returnUrl = "/") { try { @@ -29,10 +34,11 @@ public class OidcController( var state = Guid.NewGuid().ToString(); var nonce = Guid.NewGuid().ToString(); - // Store user's ID, provider, and nonce in session. The callback will use this. - HttpContext.Session.SetString($"oidc_state_{state}", $"{currentUser.Id}|{provider}|{nonce}"); + // Store user's ID, provider, and nonce in cache. The callback will use this. + var stateValue = $"{currentUser.Id}|{provider}|{nonce}"; + await cache.SetAsync($"{StateCachePrefix}{state}", stateValue, StateExpiration); - // The state parameter sent to the provider is the GUID key for the session state. + // The state parameter sent to the provider is the GUID key for the cache. var authUrl = oidcService.GetAuthorizationUrl(state, nonce); return Redirect(authUrl); } @@ -88,7 +94,7 @@ public class OidcController( ); // Generate token using existing auth service - var token = authService.CreateToken(session); + var token = auth.CreateToken(session); return Ok(new AuthController.TokenExchangeResponse { Token = token }); } @@ -156,7 +162,7 @@ public class OidcController( Meta = userInfo.ToMetadata() }; - db.AccountConnections.Add(connection); + await db.AccountConnections.AddAsync(connection); await db.SaveChangesAsync(); return existingAccount; diff --git a/DysonNetwork.Sphere/wwwroot/css/styles.css b/DysonNetwork.Sphere/wwwroot/css/styles.css index ef55696..7b65a38 100644 --- a/DysonNetwork.Sphere/wwwroot/css/styles.css +++ b/DysonNetwork.Sphere/wwwroot/css/styles.css @@ -1,4 +1,4 @@ -/*! tailwindcss v4.1.7 | MIT License | https://tailwindcss.com */ +/*! tailwindcss v4.1.10 | MIT License | https://tailwindcss.com */ @layer properties; @layer theme, base, components, utilities; @layer theme { @@ -311,6 +311,9 @@ .max-w-lg { max-width: var(--container-lg); } + .border-collapse { + border-collapse: collapse; + } .transform { transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); } @@ -332,9 +335,23 @@ .rounded-lg { border-radius: var(--radius-lg); } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-2 { + border-style: var(--tw-border-style); + border-width: 2px; + } + .border-gray-300 { + border-color: var(--color-gray-300); + } .bg-blue-500 { background-color: var(--color-blue-500); } + .bg-gray-100 { + background-color: var(--color-gray-100); + } .bg-green-100 { background-color: var(--color-green-100); } @@ -430,6 +447,12 @@ .text-yellow-800 { color: var(--color-yellow-800); } + .lowercase { + text-transform: lowercase; + } + .underline { + text-decoration-line: underline; + } .opacity-80 { opacity: 80%; } @@ -504,6 +527,11 @@ line-height: var(--tw-leading, var(--text-6xl--line-height)); } } + .dark\:border-gray-600 { + @media (prefers-color-scheme: dark) { + border-color: var(--color-gray-600); + } + } .dark\:bg-gray-800 { @media (prefers-color-scheme: dark) { background-color: var(--color-gray-800); @@ -800,6 +828,11 @@ syntax: "*"; inherits: false; } +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} @property --tw-leading { syntax: "*"; inherits: false; @@ -953,6 +986,7 @@ --tw-rotate-z: initial; --tw-skew-x: initial; --tw-skew-y: initial; + --tw-border-style: solid; --tw-leading: initial; --tw-font-weight: initial; --tw-tracking: initial;