🔊 Add verbose logs for oidc
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
@@ -17,7 +18,8 @@ public class ConnectionController(
|
|||||||
AccountService accounts,
|
AccountService accounts,
|
||||||
AuthService auth,
|
AuthService auth,
|
||||||
ICacheService cache,
|
ICacheService cache,
|
||||||
IConfiguration configuration
|
IConfiguration configuration,
|
||||||
|
ILogger<ConnectionController> logger
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
private const string StateCachePrefix = "oidc-state:";
|
private const string StateCachePrefix = "oidc-state:";
|
||||||
@@ -152,8 +154,13 @@ public class ConnectionController(
|
|||||||
{
|
{
|
||||||
var stateValue = await cache.GetAsync<string>(stateKey);
|
var stateValue = await cache.GetAsync<string>(stateKey);
|
||||||
if (string.IsNullOrEmpty(stateValue) || !OidcState.TryParse(stateValue, out oidcState) || oidcState == null)
|
if (string.IsNullOrEmpty(stateValue) || !OidcState.TryParse(stateValue, out oidcState) || oidcState == null)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Invalid or expired OIDC state: {State}", callbackData.State);
|
||||||
return BadRequest("Invalid or expired state parameter");
|
return BadRequest("Invalid or expired state parameter");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("OIDC callback for provider {Provider} with state {State} and flow {FlowType}", provider, callbackData.State, oidcState.FlowType);
|
||||||
|
|
||||||
// Remove the state from cache to prevent replay attacks
|
// Remove the state from cache to prevent replay attacks
|
||||||
await cache.RemoveAsync(stateKey);
|
await cache.RemoveAsync(stateKey);
|
||||||
@@ -174,13 +181,16 @@ public class ConnectionController(
|
|||||||
{
|
{
|
||||||
// Login/Registration flow
|
// Login/Registration flow
|
||||||
if (!string.IsNullOrEmpty(oidcState.DeviceId))
|
if (!string.IsNullOrEmpty(oidcState.DeviceId))
|
||||||
{
|
|
||||||
callbackData.State = oidcState.DeviceId;
|
callbackData.State = oidcState.DeviceId;
|
||||||
}
|
|
||||||
|
|
||||||
// Store return URL if provided
|
// Store return URL if provided
|
||||||
if (string.IsNullOrEmpty(oidcState.ReturnUrl) || oidcState.ReturnUrl == "/")
|
if (string.IsNullOrEmpty(oidcState.ReturnUrl) || oidcState.ReturnUrl == "/")
|
||||||
|
{
|
||||||
|
logger.LogInformation("No returnUrl provided in OIDC state, will use default.");
|
||||||
return await HandleLoginOrRegistration(provider, oidcService, callbackData);
|
return await HandleLoginOrRegistration(provider, oidcService, callbackData);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Storing returnUrl {ReturnUrl} for state {State}", oidcState.ReturnUrl, callbackData.State);
|
||||||
var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}";
|
var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}";
|
||||||
await cache.SetAsync(returnUrlKey, oidcState.ReturnUrl, StateExpiration);
|
await cache.SetAsync(returnUrlKey, oidcState.ReturnUrl, StateExpiration);
|
||||||
|
|
||||||
@@ -206,6 +216,7 @@ public class ConnectionController(
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
logger.LogError(ex, "Error processing OIDC callback for provider {Provider} during connection flow", provider);
|
||||||
return BadRequest($"Error processing {provider} authentication: {ex.Message}");
|
return BadRequest($"Error processing {provider} authentication: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,8 +281,9 @@ public class ConnectionController(
|
|||||||
{
|
{
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
catch (DbUpdateException)
|
catch (DbUpdateException ex)
|
||||||
{
|
{
|
||||||
|
logger.LogError(ex, "Failed to save OIDC connection for provider {Provider}", provider);
|
||||||
return StatusCode(500, $"Failed to save {provider} connection. Please try again.");
|
return StatusCode(500, $"Failed to save {provider} connection. Please try again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,8 +293,10 @@ public class ConnectionController(
|
|||||||
await cache.RemoveAsync(returnUrlKey);
|
await cache.RemoveAsync(returnUrlKey);
|
||||||
|
|
||||||
var siteUrl = configuration["SiteUrl"];
|
var siteUrl = configuration["SiteUrl"];
|
||||||
|
var redirectUrl = string.IsNullOrEmpty(returnUrl) ? siteUrl + "/auth/callback" : returnUrl;
|
||||||
return Redirect(string.IsNullOrEmpty(returnUrl) ? siteUrl + "/auth/callback" : returnUrl);
|
|
||||||
|
logger.LogInformation("Redirecting after OIDC connection to {RedirectUrl}", redirectUrl);
|
||||||
|
return Redirect(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IActionResult> HandleLoginOrRegistration(
|
private async Task<IActionResult> HandleLoginOrRegistration(
|
||||||
@@ -298,6 +312,7 @@ public class ConnectionController(
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
logger.LogError(ex, "Error processing OIDC callback for provider {Provider} during login/registration flow", provider);
|
||||||
return BadRequest($"Error processing callback: {ex.Message}");
|
return BadRequest($"Error processing callback: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,13 +320,21 @@ public class ConnectionController(
|
|||||||
{
|
{
|
||||||
return BadRequest($"Email or user ID is missing from {provider}'s response");
|
return BadRequest($"Email or user ID is missing from {provider}'s response");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve and clean up the return URL
|
||||||
|
var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}";
|
||||||
|
var returnUrl = await cache.GetAsync<string>(returnUrlKey);
|
||||||
|
await cache.RemoveAsync(returnUrlKey);
|
||||||
|
|
||||||
|
var siteUrl = configuration["SiteUrl"];
|
||||||
|
var redirectBaseUrl = string.IsNullOrEmpty(returnUrl) ? siteUrl + "/auth/callback" : returnUrl;
|
||||||
|
|
||||||
var connection = await db.AccountConnections
|
var connection = await db.AccountConnections
|
||||||
.Include(c => c.Account)
|
.Include(c => c.Account)
|
||||||
.FirstOrDefaultAsync(c => c.Provider == provider && c.ProvidedIdentifier == userInfo.UserId);
|
.FirstOrDefaultAsync(c => c.Provider == provider && c.ProvidedIdentifier == userInfo.UserId);
|
||||||
|
|
||||||
var clock = SystemClock.Instance;
|
var clock = SystemClock.Instance;
|
||||||
var siteUrl = configuration["SiteUrl"];
|
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
// Login existing user
|
// Login existing user
|
||||||
@@ -325,7 +348,9 @@ public class ConnectionController(
|
|||||||
HttpContext,
|
HttpContext,
|
||||||
deviceId ?? string.Empty);
|
deviceId ?? string.Empty);
|
||||||
|
|
||||||
return Redirect(siteUrl + $"/auth/callback?challenge={challenge.Id}");
|
var redirectUrl = QueryHelpers.AddQueryString(redirectBaseUrl, "challenge", challenge.Id.ToString());
|
||||||
|
logger.LogInformation("OIDC login successful for user {UserId}. Redirecting to {RedirectUrl}", connection.AccountId, redirectUrl);
|
||||||
|
return Redirect(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register new user
|
// Register new user
|
||||||
@@ -349,7 +374,9 @@ public class ConnectionController(
|
|||||||
var loginSession = await auth.CreateSessionForOidcAsync(account, clock.GetCurrentInstant());
|
var loginSession = await auth.CreateSessionForOidcAsync(account, clock.GetCurrentInstant());
|
||||||
var loginToken = auth.CreateToken(loginSession);
|
var loginToken = auth.CreateToken(loginSession);
|
||||||
|
|
||||||
return Redirect(siteUrl + $"/auth/callback?token={loginToken}");
|
var finalRedirectUrl = QueryHelpers.AddQueryString(redirectBaseUrl, "token", loginToken);
|
||||||
|
logger.LogInformation("OIDC registration successful for new user {UserId}. Redirecting to {RedirectUrl}", account.Id, finalRedirectUrl);
|
||||||
|
return Redirect(finalRedirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<OidcCallbackData> ExtractCallbackData(HttpRequest request)
|
private static async Task<OidcCallbackData> ExtractCallbackData(HttpRequest request)
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public class OidcController(
|
|||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
AccountService accounts,
|
AccountService accounts,
|
||||||
ICacheService cache
|
ICacheService cache,
|
||||||
|
ILogger<OidcController> logger
|
||||||
)
|
)
|
||||||
: ControllerBase
|
: ControllerBase
|
||||||
{
|
{
|
||||||
@@ -25,9 +26,10 @@ public class OidcController(
|
|||||||
public async Task<ActionResult> OidcLogin(
|
public async Task<ActionResult> OidcLogin(
|
||||||
[FromRoute] string provider,
|
[FromRoute] string provider,
|
||||||
[FromQuery] string? returnUrl = "/",
|
[FromQuery] string? returnUrl = "/",
|
||||||
[FromHeader(Name = "X-Device-Id")] string? deviceId = null
|
[FromQuery] string? deviceId = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
logger.LogInformation("OIDC login request for provider {Provider} with returnUrl {ReturnUrl} and deviceId {DeviceId}", provider, returnUrl, deviceId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var oidcService = GetOidcService(provider);
|
var oidcService = GetOidcService(provider);
|
||||||
@@ -41,6 +43,7 @@ public class OidcController(
|
|||||||
// Create and store connection state
|
// Create and store connection state
|
||||||
var oidcState = OidcState.ForConnection(currentUser.Id, provider, nonce, deviceId);
|
var oidcState = OidcState.ForConnection(currentUser.Id, provider, nonce, deviceId);
|
||||||
await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration);
|
await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration);
|
||||||
|
logger.LogInformation("OIDC connection flow started for user {UserId} with state {State}", currentUser.Id, state);
|
||||||
|
|
||||||
// The state parameter sent to the provider is the GUID key for the cache.
|
// The state parameter sent to the provider is the GUID key for the cache.
|
||||||
var authUrl = await oidcService.GetAuthorizationUrlAsync(state, nonce);
|
var authUrl = await oidcService.GetAuthorizationUrlAsync(state, nonce);
|
||||||
@@ -54,12 +57,14 @@ public class OidcController(
|
|||||||
// Create login state with return URL and device ID
|
// Create login state with return URL and device ID
|
||||||
var oidcState = OidcState.ForLogin(returnUrl ?? "/", deviceId);
|
var oidcState = OidcState.ForLogin(returnUrl ?? "/", deviceId);
|
||||||
await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration);
|
await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration);
|
||||||
|
logger.LogInformation("OIDC login flow started with state {State} and returnUrl {ReturnUrl}", state, oidcState.ReturnUrl);
|
||||||
var authUrl = await oidcService.GetAuthorizationUrlAsync(state, nonce);
|
var authUrl = await oidcService.GetAuthorizationUrlAsync(state, nonce);
|
||||||
return Redirect(authUrl);
|
return Redirect(authUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
logger.LogError(ex, "Error initiating OIDC flow for provider {Provider}", provider);
|
||||||
return BadRequest($"Error initiating OpenID Connect flow: {ex.Message}");
|
return BadRequest($"Error initiating OpenID Connect flow: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user