Compare commits
8 commits
c778f7e46b
...
573c12fe30
| Author | SHA1 | Date | |
|---|---|---|---|
| 573c12fe30 | |||
| 286c29f4e3 | |||
| 786016344b | |||
| fda43ba2d1 | |||
| 9622dbeaf2 | |||
| 3d80a3f2a1 | |||
| 13964b9f7f | |||
| eba44b46e2 |
17 changed files with 207 additions and 69 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -12,8 +12,10 @@
|
||||||
"DOTNET",
|
"DOTNET",
|
||||||
"ecdsa",
|
"ecdsa",
|
||||||
"envsubst",
|
"envsubst",
|
||||||
|
"Hsts",
|
||||||
"Newtonsoft",
|
"Newtonsoft",
|
||||||
"Npgsql",
|
"Npgsql",
|
||||||
|
"PKCE",
|
||||||
"postit",
|
"postit",
|
||||||
"pschneider",
|
"pschneider",
|
||||||
"SLNDIR",
|
"SLNDIR",
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public partial class App : Application
|
||||||
/// <c>DataValidationErrors.SetErrors</c>.
|
/// <c>DataValidationErrors.SetErrors</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IServiceProvider? Services { get; private set; }
|
public IServiceProvider? Services { get; private set; }
|
||||||
|
private MainWindow window;
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +137,7 @@ public partial class App : Application
|
||||||
var homePage = provider.GetRequiredService<HomePage>();
|
var homePage = provider.GetRequiredService<HomePage>();
|
||||||
homePage.DataContext = provider.GetRequiredService<HomePageViewModel>();
|
homePage.DataContext = provider.GetRequiredService<HomePageViewModel>();
|
||||||
|
|
||||||
var window = new MainWindow();
|
window = new MainWindow();
|
||||||
window.SessionBanner.DataContext = sessionStatus;
|
window.SessionBanner.DataContext = sessionStatus;
|
||||||
|
|
||||||
// Build the navigation stack from scratch: HomePage is the
|
// Build the navigation stack from scratch: HomePage is the
|
||||||
|
|
@ -165,7 +165,7 @@ public partial class App : Application
|
||||||
sessionStatus.LoginSucceeded += () =>
|
sessionStatus.LoginSucceeded += () =>
|
||||||
{
|
{
|
||||||
var w = (MainWindow)((IClassicDesktopStyleApplicationLifetime)ApplicationLifetime!).MainWindow!;
|
var w = (MainWindow)((IClassicDesktopStyleApplicationLifetime)ApplicationLifetime!).MainWindow!;
|
||||||
_ = PushMainPageAsync(provider, w);
|
_ = PushMainPageAsync();
|
||||||
};
|
};
|
||||||
|
|
||||||
// When the user clicks the "Paramètres" button on the
|
// When the user clicks the "Paramètres" button on the
|
||||||
|
|
@ -196,7 +196,7 @@ public partial class App : Application
|
||||||
_ = w.NavRoot.PushAsync(settingsPage);
|
_ = w.NavRoot.PushAsync(settingsPage);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.Opened += async (_, _) => await BootAsync(provider, api, window);
|
window.Opened += async (_, _) => await BootAsync(provider, api);
|
||||||
}
|
}
|
||||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
|
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
|
||||||
{
|
{
|
||||||
|
|
@ -223,15 +223,14 @@ public partial class App : Application
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task BootAsync(
|
private static async Task BootAsync(
|
||||||
IServiceProvider provider,
|
IServiceProvider provider,
|
||||||
YavscApiClient api,
|
YavscApiClient api)
|
||||||
MainWindow window)
|
|
||||||
{
|
{
|
||||||
var refreshed = await api.TrySilentLoginAsync().ConfigureAwait(true);
|
var refreshed = await api.TrySilentLoginAsync().ConfigureAwait(true);
|
||||||
var sessionStatus = provider.GetRequiredService<SessionStatusViewModel>();
|
var sessionStatus = provider.GetRequiredService<SessionStatusViewModel>();
|
||||||
sessionStatus.Refresh();
|
sessionStatus.Refresh();
|
||||||
if (!refreshed) return;
|
if (!refreshed) return;
|
||||||
|
|
||||||
await PushMainPageAsync(provider, window).ConfigureAwait(true);
|
await PushMainPageAsync().ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -241,12 +240,13 @@ public partial class App : Application
|
||||||
/// (interactive login from the banner). Pulled out as a helper so
|
/// (interactive login from the banner). Pulled out as a helper so
|
||||||
/// the two callers can't drift apart.
|
/// the two callers can't drift apart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task PushMainPageAsync(IServiceProvider provider, MainWindow window)
|
public static async Task PushMainPageAsync()
|
||||||
{
|
{
|
||||||
var mainVm = provider.GetRequiredService<MainPageViewModel>();
|
var app = (App)Current;
|
||||||
var mainPage = provider.GetRequiredService<MainPage>();
|
var mainVm = app.Services.GetRequiredService<MainPageViewModel>();
|
||||||
|
var mainPage = app.Services.GetRequiredService<MainPage>();
|
||||||
mainPage.DataContext = mainVm;
|
mainPage.DataContext = mainVm;
|
||||||
await window.NavRoot.PushAsync(mainPage).ConfigureAwait(true);
|
await app.window.FindControl<NavigationPage>("NavRoot").PushAsync(mainPage).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryHandOffCustomSchemeUrl()
|
private bool TryHandOffCustomSchemeUrl()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PostIt;
|
using PostIt;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
public class HomePageViewModel : ViewModelBase
|
public class HomePageViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +23,7 @@ public class HomePageViewModel : ViewModelBase
|
||||||
Api = api;
|
Api = api;
|
||||||
Settings = settings;
|
Settings = settings;
|
||||||
}
|
}
|
||||||
|
public RelayCommand OpenBlogs { get; set; } = new RelayCommand(() => App.PushMainPageAsync());
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Avalonia designer constructor. Builds a self-contained VM
|
/// Avalonia designer constructor. Builds a self-contained VM
|
||||||
/// with a freshly-constructed Settings so the XAML preview can
|
/// with a freshly-constructed Settings so the XAML preview can
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
|
@ -178,10 +179,20 @@ public partial class Settings : ViewModelBase
|
||||||
RedirectUri = Authentication.RedirectUri,
|
RedirectUri = Authentication.RedirectUri,
|
||||||
Scope = string.Join(' ', MergeScopes(this.Authentication.Scopes)),
|
Scope = string.Join(' ', MergeScopes(this.Authentication.Scopes)),
|
||||||
TokenClientCredentialStyle = IdentityModel.Client.ClientCredentialStyle.PostBody,
|
TokenClientCredentialStyle = IdentityModel.Client.ClientCredentialStyle.PostBody,
|
||||||
PostLogoutRedirectUri = "https//yavsc.pschneider.fr",
|
PostLogoutRedirectUri = Authentication.Authority,
|
||||||
// PKCE is enabled by default when no client_secret is provided.
|
// PKCE is enabled by default when no client_secret is provided.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (IsDevelopmentEnvironment())
|
||||||
|
{
|
||||||
|
// Dev only: allow local/self-signed TLS for discovery/token
|
||||||
|
// endpoints when the machine does not trust a custom root.
|
||||||
|
options.BackchannelHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (browser is not null)
|
if (browser is not null)
|
||||||
options.Browser = browser;
|
options.Browser = browser;
|
||||||
|
|
||||||
|
|
@ -231,6 +242,14 @@ public partial class Settings : ViewModelBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsDevelopmentEnvironment()
|
||||||
|
{
|
||||||
|
return string.Equals(
|
||||||
|
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
|
||||||
|
"Development",
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
internal void Load()
|
internal void Load()
|
||||||
{
|
{
|
||||||
if (Loaded) return;
|
if (Loaded) return;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
<ContentPage xmlns="https://github.com/avaloniaui"
|
<ContentPage xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:PostIt.ViewModels"
|
||||||
x:Class="PostIt.Views.HomePage"
|
x:Class="PostIt.Views.HomePage"
|
||||||
|
x:DataType="vm:HomePageViewModel"
|
||||||
Header="Home">
|
Header="Home">
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:HomePageViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
<StackPanel HorizontalAlignment="Center"
|
<StackPanel HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Spacing="12">
|
Spacing="12">
|
||||||
|
|
@ -9,5 +14,8 @@
|
||||||
FontSize="22"
|
FontSize="22"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
HorizontalAlignment="Center"/>
|
HorizontalAlignment="Center"/>
|
||||||
|
<Button Content="Open Blog Interface"
|
||||||
|
Command="{Binding OpenBlogs}"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
<StackPanel Grid.Row="0" Spacing="12"
|
<StackPanel Grid.Row="0" Spacing="12"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Top">
|
VerticalAlignment="Top">
|
||||||
<TextBlock Text="PostIt Blog API Interface" FontSize="20" FontWeight="Bold" />
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<Button Command="{Binding LoadPosts}" Content="Load posts" />
|
<Button Command="{Binding LoadPosts}" Content="Load posts" />
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
-->
|
-->
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<views:SessionStatusBanner x:Name="SessionBanner"
|
<views:SessionStatusBanner x:Name="SessionBanner"
|
||||||
DockPanel.Dock="Top"/>
|
DockPanel.Dock="Bottom"/>
|
||||||
<NavigationPage x:Name="NavRoot"/>
|
<NavigationPage x:Name="NavRoot"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -791,12 +791,12 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
|
||||||
{
|
{
|
||||||
if (userId == null || code == null)
|
if (userId == null || code == null)
|
||||||
{
|
{
|
||||||
return View("Error");
|
return this.ErrorView<AccountController>("Error: userId or code is null.");
|
||||||
}
|
}
|
||||||
var user = await _userManager.FindByIdAsync(userId);
|
var user = await _userManager.FindByIdAsync(userId);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
return View("Error");
|
return this.ErrorView<AccountController>("Error: user not found.");
|
||||||
}
|
}
|
||||||
IdentityResult result = null;
|
IdentityResult result = null;
|
||||||
try
|
try
|
||||||
|
|
@ -819,12 +819,12 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
|
||||||
{
|
{
|
||||||
if (userId == null || code == null)
|
if (userId == null || code == null)
|
||||||
{
|
{
|
||||||
return View("Error");
|
return this.ErrorView<AccountController>("Error: userId or code is null.");
|
||||||
}
|
}
|
||||||
var user = await _userManager.FindByIdAsync(userId);
|
var user = await _userManager.FindByIdAsync(userId);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
return View("Error");
|
return this.ErrorView<AccountController>("Error: user not found.");
|
||||||
}
|
}
|
||||||
bool result = false;
|
bool result = false;
|
||||||
try
|
try
|
||||||
|
|
@ -837,7 +837,7 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
|
||||||
_logger.LogError(ex.StackTrace);
|
_logger.LogError(ex.StackTrace);
|
||||||
_logger.LogError(ex.Message);
|
_logger.LogError(ex.Message);
|
||||||
}
|
}
|
||||||
return View(result ? "EmailConfirmed" : "Error");
|
return result ? View("EmailConfirmed") : this.ErrorView<AccountController>("Error confirming two factor token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.IO;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
@ -490,12 +489,7 @@ namespace Yavsc.Controllers
|
||||||
: message == ManageMessageId.Error ? "An error has occurred."
|
: message == ManageMessageId.Error ? "An error has occurred."
|
||||||
: "";
|
: "";
|
||||||
var user = await GetCurrentUserAsync();
|
var user = await GetCurrentUserAsync();
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return View("Error");
|
|
||||||
}
|
|
||||||
var userLogins = await _userManager.GetLoginsAsync(user);
|
var userLogins = await _userManager.GetLoginsAsync(user);
|
||||||
|
|
||||||
ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1;
|
ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1;
|
||||||
|
|
||||||
return View(new ManageLoginsViewModel
|
return View(new ManageLoginsViewModel
|
||||||
|
|
@ -522,15 +516,7 @@ namespace Yavsc.Controllers
|
||||||
public async Task<ActionResult> LinkLoginCallback()
|
public async Task<ActionResult> LinkLoginCallback()
|
||||||
{
|
{
|
||||||
var user = await GetCurrentUserAsync();
|
var user = await GetCurrentUserAsync();
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return View("Error");
|
|
||||||
}
|
|
||||||
var info = await _signInManager.GetExternalLoginInfoAsync(User.GetUserId());
|
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 result = await _userManager.AddLoginAsync(user, info);
|
||||||
var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error;
|
var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error;
|
||||||
return RedirectToAction(nameof(ManageLogins), new { Message = message });
|
return RedirectToAction(nameof(ManageLogins), new { Message = message });
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ using System.Collections.Generic;
|
||||||
using System;
|
using System;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Extensions;
|
using Yavsc.Extensions;
|
||||||
|
using Yavsc.Models;
|
||||||
|
|
||||||
namespace IdentityServerHost.Quickstart.UI
|
namespace IdentityServerHost.Quickstart.UI
|
||||||
{
|
{
|
||||||
|
|
@ -53,10 +54,11 @@ namespace IdentityServerHost.Quickstart.UI
|
||||||
{
|
{
|
||||||
return View("Index", vm);
|
return View("Index", vm);
|
||||||
}
|
}
|
||||||
|
return this.ErrorView<ConsentController>("No consent request matching request: " + returnUrl);
|
||||||
return View("Error");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the consent screen postback
|
/// Handles the consent screen postback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -88,8 +90,8 @@ namespace IdentityServerHost.Quickstart.UI
|
||||||
{
|
{
|
||||||
return View("Index", result.ViewModel);
|
return View("Index", result.ViewModel);
|
||||||
}
|
}
|
||||||
|
return this.ErrorView<ConsentController>($"ReturnUrl: {model}, result: {result}" );
|
||||||
|
|
||||||
return View("Error");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
|
|
@ -170,11 +172,6 @@ namespace IdentityServerHost.Quickstart.UI
|
||||||
{
|
{
|
||||||
return CreateConsentViewModel(model, returnUrl, request);
|
return CreateConsentViewModel(model, returnUrl, request);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogError("No consent request matching request: {0}", returnUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
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();
|
vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
||||||
|
|
||||||
var apiScopes = new List<ScopeViewModel>();
|
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);
|
var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName);
|
||||||
if (apiScope != null)
|
if (apiScope != null)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Yavsc.Models;
|
||||||
using Yavsc.Models.Access;
|
using Yavsc.Models.Access;
|
||||||
|
|
||||||
namespace Yavsc.Controllers
|
namespace Yavsc.Controllers
|
||||||
|
|
@ -49,7 +50,7 @@ namespace Yavsc.Controllers
|
||||||
if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture");
|
if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture");
|
||||||
|
|
||||||
var vm = await BuildViewModelAsync(userCode);
|
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;
|
vm.ConfirmUserCode = true;
|
||||||
return View("UserCodeConfirmation", vm);
|
return View("UserCodeConfirmation", vm);
|
||||||
|
|
@ -60,7 +61,7 @@ namespace Yavsc.Controllers
|
||||||
public async Task<IActionResult> UserCodeCapture(string userCode)
|
public async Task<IActionResult> UserCodeCapture(string userCode)
|
||||||
{
|
{
|
||||||
var vm = await BuildViewModelAsync(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);
|
return View("UserCodeConfirmation", vm);
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +73,20 @@ namespace Yavsc.Controllers
|
||||||
if (model == null) throw new ArgumentNullException(nameof(model));
|
if (model == null) throw new ArgumentNullException(nameof(model));
|
||||||
|
|
||||||
var result = await ProcessConsent(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");
|
return View("Success");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,24 @@ namespace Yavsc.Controllers
|
||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
readonly ApplicationDbContext _dbContext;
|
readonly ApplicationDbContext _dbContext;
|
||||||
|
readonly ILogger<HomeController> _logger;
|
||||||
|
private readonly bool _isDevelopment;
|
||||||
readonly IHtmlLocalizer _localizer;
|
readonly IHtmlLocalizer _localizer;
|
||||||
|
|
||||||
private SiteSettings siteSettings;
|
private SiteSettings siteSettings;
|
||||||
public HomeController(ILogger<HomeController> logger,
|
public HomeController(ILogger<HomeController> logger,
|
||||||
IHtmlLocalizer<HomeController> localizer,
|
IHtmlLocalizer<HomeController> localizer,
|
||||||
ApplicationDbContext context,
|
ApplicationDbContext context,
|
||||||
IOptions<SiteSettings> settingsOptions)
|
IOptions<SiteSettings> settingsOptions,
|
||||||
|
IWebHostEnvironment env
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_localizer = localizer;
|
_localizer = localizer;
|
||||||
_dbContext = context;
|
_dbContext = context;
|
||||||
siteSettings = settingsOptions.Value;
|
siteSettings = settingsOptions.Value;
|
||||||
|
_logger = logger;
|
||||||
|
_isDevelopment = env.IsDevelopment();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index(string id)
|
public async Task<IActionResult> Index(string id)
|
||||||
|
|
@ -99,18 +105,44 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
public IActionResult Error()
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
var feature = this.HttpContext.Features.Get<IExceptionHandlerFeature>();
|
if (_isDevelopment)
|
||||||
if (feature == null) return View();
|
|
||||||
var errorType = feature?.Error;
|
|
||||||
if (errorType == null) return View();
|
|
||||||
if (errorType is NotSupportedException notSupported)
|
|
||||||
{
|
{
|
||||||
return View(new ErrorViewModel {
|
_logger.LogInformation(
|
||||||
Description = notSupported.Message,
|
"Home/Error requested in Development. This endpoint is disabled because DeveloperExceptionPage should handle unhandled exceptions.");
|
||||||
RequestId = this.HttpContext.TraceIdentifier
|
|
||||||
});
|
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)
|
public IActionResult Status(int id)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -967,12 +967,12 @@ public static class HostingExtensions
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
await app.MigrateDatabaseAsync();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Home/Error");
|
app.UseExceptionHandler("/Home/Error");
|
||||||
logger.LogInformation("Running in production mode. Ensure the database is migrated.");
|
app.UseHsts();
|
||||||
|
logger.LogInformation("⨝ Running in production mode. Ensure the database is migrated.");
|
||||||
await app.MigrateDatabaseAsync();
|
await app.MigrateDatabaseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
52
src/Yavsc.Org/Helpers/ErrorViewHelpers.cs
Normal file
52
src/Yavsc.Org/Helpers/ErrorViewHelpers.cs
Normal 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
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,41 @@
|
||||||
@model ErrorViewModel
|
@using Microsoft.AspNetCore.Hosting
|
||||||
|
@using Yavsc.Models
|
||||||
|
@inject IWebHostEnvironment Env
|
||||||
|
@model object
|
||||||
@{
|
@{
|
||||||
ViewBag.Title = "Error";
|
ViewBag.Title = "Error";
|
||||||
}
|
}
|
||||||
|
|
||||||
<h1 class="text-danger">Error.</h1>
|
<h1 class="text-danger">Error.</h1>
|
||||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
|
||||||
|
|
||||||
@if (Model!=null) if (Model.ShowRequestId)
|
@if (Env.IsDevelopment())
|
||||||
{
|
{
|
||||||
<p>
|
<h2 class="text-danger">An unhandled exception occurred while processing your request.</h2>
|
||||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
if (Model is Exception exception)
|
||||||
</p>
|
{
|
||||||
|
<pre class="text-danger">@exception.ToString()</pre>
|
||||||
|
}
|
||||||
|
else if (Model is ErrorViewModel errorViewModel && !string.IsNullOrWhiteSpace(errorViewModel.Description))
|
||||||
|
{
|
||||||
|
<pre class="text-danger">@errorViewModel.Description</pre>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
if (Model is ErrorViewModel errorViewModel)
|
||||||
|
{
|
||||||
|
if (errorViewModel.ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@errorViewModel.RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(errorViewModel.Description))
|
||||||
|
{
|
||||||
|
<p class="text-danger">@errorViewModel.Description</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ public class ErrorViewModel
|
||||||
|
|
||||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
|
||||||
|
public Dictionary<string, string> ModelErrors { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue