🐛 Trying to fix connection issues
This commit is contained in:
		| @@ -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<OidcService> 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<ActionResult<List<AccountConnection>>> 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<string>(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<string>(returnUrlKey); | ||||
|         await cacheService.RemoveAsync(returnUrlKey); | ||||
|  | ||||
|         return Redirect(string.IsNullOrEmpty(returnUrl) ? "/settings/connections" : returnUrl); | ||||
|     } | ||||
|   | ||||
| @@ -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<ActionResult> 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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user