postit: allow self-signed OIDC TLS in Development #9

Merged
notazof merged 7 commits from fix/OIDC-debugging into main 2026-08-02 21:32:53 +01:00
8 changed files with 126 additions and 45 deletions
Showing only changes of commit 786016344b - Show all commits

refacto error handling
Some checks failed
Dotnet build and test / log-the-inputs (pull_request) Has been cancelled
Dotnet build and test / build (pull_request) Has been cancelled

Paul Schneider 2026-07-12 17:56:23 +01:00

View file

@ -791,12 +791,12 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
{
if (userId == null || code == null)
{
return View("Error");
return this.ErrorView<AccountController>("Error: userId or code is null.");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return View("Error");
return this.ErrorView<AccountController>("Error: user not found.");
}
IdentityResult result = null;
try
@ -819,12 +819,12 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
{
if (userId == null || code == null)
{
return View("Error");
return this.ErrorView<AccountController>("Error: userId or code is null.");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return View("Error");
return this.ErrorView<AccountController>("Error: user not found.");
}
bool result = false;
try
@ -837,7 +837,7 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
_logger.LogError(ex.StackTrace);
_logger.LogError(ex.Message);
}
return View(result ? "EmailConfirmed" : "Error");
return result ? View("EmailConfirmed") : this.ErrorView<AccountController>("Error confirming two factor token.");
}
//

View file

@ -1,6 +1,5 @@
using System.Security.Claims;
using System.IO;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
@ -490,12 +489,7 @@ namespace Yavsc.Controllers
: message == ManageMessageId.Error ? "An error has occurred."
: "";
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var userLogins = await _userManager.GetLoginsAsync(user);
ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1;
return View(new ManageLoginsViewModel
@ -522,15 +516,7 @@ namespace Yavsc.Controllers
public async Task<ActionResult> LinkLoginCallback()
{
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var info = await _signInManager.GetExternalLoginInfoAsync(User.GetUserId());
if (info == null)
{
return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error });
}
var result = await _userManager.AddLoginAsync(user, info);
var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error;
return RedirectToAction(nameof(ManageLogins), new { Message = message });

View file

@ -16,6 +16,7 @@ using System.Collections.Generic;
using System;
using Yavsc;
using Yavsc.Extensions;
using Yavsc.Models;
namespace IdentityServerHost.Quickstart.UI
{
@ -53,10 +54,11 @@ namespace IdentityServerHost.Quickstart.UI
{
return View("Index", vm);
}
return View("Error");
return this.ErrorView<ConsentController>("No consent request matching request: " + returnUrl);
}
/// <summary>
/// Handles the consent screen postback
/// </summary>
@ -88,8 +90,8 @@ namespace IdentityServerHost.Quickstart.UI
{
return View("Index", result.ViewModel);
}
return View("Error");
return this.ErrorView<ConsentController>($"ReturnUrl: {model}, result: {result}" );
}
/*****************************************/
@ -170,11 +172,6 @@ namespace IdentityServerHost.Quickstart.UI
{
return CreateConsentViewModel(model, returnUrl, request);
}
else
{
_logger.LogError("No consent request matching request: {0}", returnUrl);
}
return null;
}
@ -199,7 +196,7 @@ namespace IdentityServerHost.Quickstart.UI
vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
var apiScopes = new List<ScopeViewModel>();
foreach(var parsedScope in request.ValidatedResources.ParsedScopes)
foreach (var parsedScope in request.ValidatedResources.ParsedScopes)
{
var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName);
if (apiScope != null)

View file

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Yavsc.Models;
using Yavsc.Models.Access;
namespace Yavsc.Controllers
@ -49,7 +50,7 @@ namespace Yavsc.Controllers
if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture");
var vm = await BuildViewModelAsync(userCode);
if (vm == null) return View("Error");
if (vm == null) return this.ErrorView<DeviceController>($"ViewModel is null! userCodeParamName: {userCodeParamName}, userCode: {userCode}" );;
vm.ConfirmUserCode = true;
return View("UserCodeConfirmation", vm);
@ -60,7 +61,7 @@ namespace Yavsc.Controllers
public async Task<IActionResult> UserCodeCapture(string userCode)
{
var vm = await BuildViewModelAsync(userCode);
if (vm == null) return View("Error");
if (vm == null) return this.ErrorView<DeviceController>($"UserCodeCapture: ViewModel is null! userCode: {userCode}" );
return View("UserCodeConfirmation", vm);
}
@ -72,7 +73,20 @@ namespace Yavsc.Controllers
if (model == null) throw new ArgumentNullException(nameof(model));
var result = await ProcessConsent(model);
if (result.HasValidationError) return View("Error");
if (result.HasValidationError)
{
if (HttpContext.RequestServices.GetRequiredService<IHostEnvironment>().IsDevelopment())
{
throw new InvalidOperationException("Device Authorization Input validation error: " + result.ValidationError);
}
return View("Error",
new ErrorViewModel {
RequestId = HttpContext.TraceIdentifier,
Description = "Device Authorization Input validation error: " + result.ValidationError
}
);
}
return View("Success");
}

View file

@ -15,18 +15,24 @@ namespace Yavsc.Controllers
public class HomeController : Controller
{
readonly ApplicationDbContext _dbContext;
readonly ILogger<HomeController> _logger;
private readonly bool _isDevelopment;
readonly IHtmlLocalizer _localizer;
private SiteSettings siteSettings;
public HomeController(ILogger<HomeController> logger,
IHtmlLocalizer<HomeController> localizer,
ApplicationDbContext context,
IOptions<SiteSettings> settingsOptions)
IOptions<SiteSettings> settingsOptions,
IWebHostEnvironment env
)
{
_localizer = localizer;
_dbContext = context;
siteSettings = settingsOptions.Value;
_logger = logger;
_isDevelopment = env.IsDevelopment();
}
public async Task<IActionResult> Index(string id)
@ -99,18 +105,44 @@ namespace Yavsc.Controllers
public IActionResult Error()
{
var feature = this.HttpContext.Features.Get<IExceptionHandlerFeature>();
if (feature == null) return View();
var errorType = feature?.Error;
if (errorType == null) return View();
if (errorType is NotSupportedException notSupported)
if (_isDevelopment)
{
return View(new ErrorViewModel {
Description = notSupported.Message,
RequestId = this.HttpContext.TraceIdentifier
});
_logger.LogInformation(
"Home/Error requested in Development. This endpoint is disabled because DeveloperExceptionPage should handle unhandled exceptions.");
return NotFound(
"In Development, /Home/Error is disabled. Unhandled exceptions are rendered by DeveloperExceptionPage.");
}
return View("~/Views/Shared/Error.cshtml", feature?.Error);
var errorViewModel = new ErrorViewModel
{
RequestId = HttpContext.TraceIdentifier
};
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature is null)
{
_logger.LogWarning(
"Home/Error called without IExceptionHandlerPathFeature in non-development environment.");
return View("~/Views/Shared/Error.cshtml", errorViewModel);
}
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
errorViewModel.Description = "The file was not found.";
}
if (exceptionHandlerPathFeature?.Path == "/")
{
errorViewModel.Description ??= string.Empty;
errorViewModel.Description += " Page: Home.";
}
return View("~/Views/Shared/Error.cshtml", errorViewModel);
}
public IActionResult Status(int id)
{

View file

@ -967,7 +967,6 @@ public static class HostingExtensions
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
await app.MigrateDatabaseAsync();
}
else
{

View file

@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Mvc;
using Yavsc.Models;
public static class ErrorViewHelpers
{
public static IActionResult ErrorView<T>(this Controller controller, string message)
{
var logger = controller.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>()
.CreateLogger<T>();
logger.LogError(message);
Dictionary<string, string> dictionary = new Dictionary<string, string>();
if (!controller.ModelState.IsValid)
{
foreach (var modelState in controller.ModelState.Values)
{
foreach (var error in modelState.Errors)
{
logger.LogError("ModelState error: {0}", error.ErrorMessage);
foreach (var key in controller.ModelState.Keys)
{
logger.LogError("ModelState key: {0}", key);
dictionary.Add(key,
string.Join("\n",
controller.ModelState[key].Errors.Select( e => e.ErrorMessage).ToArray()));
}
}
}
}
if (controller.HttpContext.Request.Headers.ContainsKey("Accept")
&& controller.HttpContext.Request.Headers["Accept"].ToString().Contains("application/json"))
{
return controller.Json(new
{
RequestId = controller.HttpContext.TraceIdentifier,
Description = message,
ModelErrors = dictionary
});
}
return controller.View("Error",
new ErrorViewModel
{
RequestId = controller.HttpContext.TraceIdentifier,
Description = message,
ModelErrors = dictionary
}
);
}
}

View file

@ -7,4 +7,5 @@ public class ErrorViewModel
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public Dictionary<string, string> ModelErrors { get; set; }
}