🐛 Trying to fix connection issues
This commit is contained in:
parent
1a5d0bbfc0
commit
50d8f74a98
@ -2,7 +2,7 @@ using DysonNetwork.Sphere.Account;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Auth.OpenId;
|
namespace DysonNetwork.Sphere.Auth.OpenId;
|
||||||
@ -14,9 +14,13 @@ public class ConnectionController(
|
|||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
IEnumerable<OidcService> oidcServices,
|
IEnumerable<OidcService> oidcServices,
|
||||||
AccountService accounts,
|
AccountService accounts,
|
||||||
AuthService auth
|
AuthService auth,
|
||||||
|
ICacheService cacheService
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
|
private const string StateCachePrefix = "oidc-state:";
|
||||||
|
private const string ReturnUrlCachePrefix = "oidc-returning:";
|
||||||
|
private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15);
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<List<AccountConnection>>> GetConnections()
|
public async Task<ActionResult<List<AccountConnection>>> GetConnections()
|
||||||
{
|
{
|
||||||
@ -142,10 +146,12 @@ public class ConnectionController(
|
|||||||
|
|
||||||
var state = Guid.NewGuid().ToString("N");
|
var state = Guid.NewGuid().ToString("N");
|
||||||
var nonce = 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";
|
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);
|
var authUrl = oidcService.GetAuthorizationUrl(state, nonce);
|
||||||
|
|
||||||
@ -169,14 +175,22 @@ public class ConnectionController(
|
|||||||
if (callbackData.State == null)
|
if (callbackData.State == null)
|
||||||
return BadRequest("State parameter is missing.");
|
return BadRequest("State parameter is missing.");
|
||||||
|
|
||||||
var sessionState = HttpContext.Session.GetString($"oidc_state_{callbackData.State!}");
|
// Get and validate state from cache
|
||||||
HttpContext.Session.Remove($"oidc_state_{callbackData.State}");
|
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.
|
// Remove state from cache to prevent replay attacks
|
||||||
if (sessionState == null) return await HandleLoginOrRegistration(provider, oidcService, callbackData);
|
await cacheService.RemoveAsync(stateKey);
|
||||||
var stateParts = sessionState.Split('|');
|
|
||||||
if (stateParts.Length != 3 || !stateParts[1].Equals(provider, StringComparison.OrdinalIgnoreCase))
|
var stateParts = stateValue.Split('|');
|
||||||
return BadRequest("State mismatch.");
|
if (stateParts.Length != 3)
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid state format");
|
||||||
|
}
|
||||||
|
|
||||||
var accountId = Guid.Parse(stateParts[0]);
|
var accountId = Guid.Parse(stateParts[0]);
|
||||||
return await HandleManualConnection(provider, oidcService, callbackData, accountId);
|
return await HandleManualConnection(provider, oidcService, callbackData, accountId);
|
||||||
@ -259,9 +273,9 @@ public class ConnectionController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up and redirect
|
// Clean up and redirect
|
||||||
var returnUrl = HttpContext.Session.GetString($"oidc_return_url_{callbackData.State}");
|
var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}";
|
||||||
HttpContext.Session.Remove($"oidc_return_url_{callbackData.State}");
|
var returnUrl = await cacheService.GetAsync<string>(returnUrlKey);
|
||||||
HttpContext.Session.Remove($"oidc_state_{callbackData.State}");
|
await cacheService.RemoveAsync(returnUrlKey);
|
||||||
|
|
||||||
return Redirect(string.IsNullOrEmpty(returnUrl) ? "/settings/connections" : returnUrl);
|
return Redirect(string.IsNullOrEmpty(returnUrl) ? "/settings/connections" : returnUrl);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
@ -12,12 +13,16 @@ public class OidcController(
|
|||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
AccountService accounts,
|
AccountService accounts,
|
||||||
AuthService authService
|
AuthService auth,
|
||||||
|
ICacheService cache
|
||||||
)
|
)
|
||||||
: ControllerBase
|
: ControllerBase
|
||||||
{
|
{
|
||||||
|
private const string StateCachePrefix = "oidc-state:";
|
||||||
|
private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
[HttpGet("{provider}")]
|
[HttpGet("{provider}")]
|
||||||
public ActionResult SignIn([FromRoute] string provider, [FromQuery] string? returnUrl = "/")
|
public async Task<ActionResult> SignIn([FromRoute] string provider, [FromQuery] string? returnUrl = "/")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -29,10 +34,11 @@ public class OidcController(
|
|||||||
var state = Guid.NewGuid().ToString();
|
var state = Guid.NewGuid().ToString();
|
||||||
var nonce = Guid.NewGuid().ToString();
|
var nonce = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
// Store user's ID, provider, and nonce in session. The callback will use this.
|
// Store user's ID, provider, and nonce in cache. The callback will use this.
|
||||||
HttpContext.Session.SetString($"oidc_state_{state}", $"{currentUser.Id}|{provider}|{nonce}");
|
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);
|
var authUrl = oidcService.GetAuthorizationUrl(state, nonce);
|
||||||
return Redirect(authUrl);
|
return Redirect(authUrl);
|
||||||
}
|
}
|
||||||
@ -88,7 +94,7 @@ public class OidcController(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Generate token using existing auth service
|
// Generate token using existing auth service
|
||||||
var token = authService.CreateToken(session);
|
var token = auth.CreateToken(session);
|
||||||
|
|
||||||
return Ok(new AuthController.TokenExchangeResponse { Token = token });
|
return Ok(new AuthController.TokenExchangeResponse { Token = token });
|
||||||
}
|
}
|
||||||
@ -156,7 +162,7 @@ public class OidcController(
|
|||||||
Meta = userInfo.ToMetadata()
|
Meta = userInfo.ToMetadata()
|
||||||
};
|
};
|
||||||
|
|
||||||
db.AccountConnections.Add(connection);
|
await db.AccountConnections.AddAsync(connection);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
return existingAccount;
|
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 properties;
|
||||||
@layer theme, base, components, utilities;
|
@layer theme, base, components, utilities;
|
||||||
@layer theme {
|
@layer theme {
|
||||||
@ -311,6 +311,9 @@
|
|||||||
.max-w-lg {
|
.max-w-lg {
|
||||||
max-width: var(--container-lg);
|
max-width: var(--container-lg);
|
||||||
}
|
}
|
||||||
|
.border-collapse {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
.transform {
|
.transform {
|
||||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
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 {
|
.rounded-lg {
|
||||||
border-radius: var(--radius-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 {
|
.bg-blue-500 {
|
||||||
background-color: var(--color-blue-500);
|
background-color: var(--color-blue-500);
|
||||||
}
|
}
|
||||||
|
.bg-gray-100 {
|
||||||
|
background-color: var(--color-gray-100);
|
||||||
|
}
|
||||||
.bg-green-100 {
|
.bg-green-100 {
|
||||||
background-color: var(--color-green-100);
|
background-color: var(--color-green-100);
|
||||||
}
|
}
|
||||||
@ -430,6 +447,12 @@
|
|||||||
.text-yellow-800 {
|
.text-yellow-800 {
|
||||||
color: var(--color-yellow-800);
|
color: var(--color-yellow-800);
|
||||||
}
|
}
|
||||||
|
.lowercase {
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
.underline {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
.opacity-80 {
|
.opacity-80 {
|
||||||
opacity: 80%;
|
opacity: 80%;
|
||||||
}
|
}
|
||||||
@ -504,6 +527,11 @@
|
|||||||
line-height: var(--tw-leading, var(--text-6xl--line-height));
|
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 {
|
.dark\:bg-gray-800 {
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
background-color: var(--color-gray-800);
|
background-color: var(--color-gray-800);
|
||||||
@ -800,6 +828,11 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
|
@property --tw-border-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
@property --tw-leading {
|
@property --tw-leading {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@ -953,6 +986,7 @@
|
|||||||
--tw-rotate-z: initial;
|
--tw-rotate-z: initial;
|
||||||
--tw-skew-x: initial;
|
--tw-skew-x: initial;
|
||||||
--tw-skew-y: initial;
|
--tw-skew-y: initial;
|
||||||
|
--tw-border-style: solid;
|
||||||
--tw-leading: initial;
|
--tw-leading: initial;
|
||||||
--tw-font-weight: initial;
|
--tw-font-weight: initial;
|
||||||
--tw-tracking: initial;
|
--tw-tracking: initial;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user