diff --git a/Hcs.WebApp/Components/Layout/MainLayout.razor b/Hcs.WebApp/Components/Layout/MainLayout.razor index 64ab11d..8b3fc99 100644 --- a/Hcs.WebApp/Components/Layout/MainLayout.razor +++ b/Hcs.WebApp/Components/Layout/MainLayout.razor @@ -3,8 +3,7 @@ @implements IDisposable @inject NavigationManager NavigationManager - - +@inject NotificationService NotificationService @@ -25,11 +24,7 @@ - -
- - - +
@@ -40,6 +35,7 @@ + @Body
Произошла непредвиденная ошибка @@ -69,6 +65,8 @@ { currentUrl = NavigationManager.ToBaseRelativePath(e.Location); + NotificationService.Messages.Clear(); + StateHasChanged(); } } diff --git a/Hcs.WebApp/Components/Pages/Account/Register.razor b/Hcs.WebApp/Components/Pages/Account/Register.razor index 50020ff..dd03f9d 100644 --- a/Hcs.WebApp/Components/Pages/Account/Register.razor +++ b/Hcs.WebApp/Components/Pages/Account/Register.razor @@ -1,21 +1,13 @@ @page "/account/register" -@using Microsoft.AspNetCore.Identity -@using Hcs.WebApp.Data -@using Hcs.WebApp.Identity - -@inject IUserStore UserStore -@inject UserManager UserManager @inject NotificationService NotificationService -@inject SignInManager SignInManager -@inject IdentityRedirectManager RedirectManager Регистрация аккаунта - + - Регистрация + Регистрация @@ -42,7 +34,7 @@ - + @@ -63,28 +55,28 @@ [SupplyParameterFromForm] InputModel Input { get; set; } = new(); + [SupplyParameterFromQuery] + string? Errors { get; set; } + [SupplyParameterFromQuery] string? ReturnUrl { get; set; } - async Task OnSubmit(InputModel mode) + protected override void OnAfterRender(bool firstRender) { - var user = Activator.CreateInstance(); - await UserStore.SetUserNameAsync(user, Input.UserName, CancellationToken.None); + base.OnAfterRender(firstRender); - var result = await UserManager.CreateAsync(user, Input.Password); - if (!result.Succeeded) + if (firstRender) { - NotificationService.Notify(new NotificationMessage() + if (!string.IsNullOrEmpty(Errors)) { - Severity = NotificationSeverity.Error, - Summary = "Ошибка", - Detail = string.Join(", ", result.Errors.Select(error => error.Description)) - }); - return; + NotificationService.Notify(new NotificationMessage() + { + Severity = NotificationSeverity.Error, + Summary = "Ошибка", + Detail = Errors, + Duration = -1d + }); + } } - - await SignInManager.SignInAsync(user, isPersistent: false); - - RedirectManager.RedirectTo(ReturnUrl); } } diff --git a/Hcs.WebApp/Components/Pages/Test/Export.razor b/Hcs.WebApp/Components/Pages/Test/Export.razor index 33ed8b0..2f4019e 100644 --- a/Hcs.WebApp/Components/Pages/Test/Export.razor +++ b/Hcs.WebApp/Components/Pages/Test/Export.razor @@ -1,12 +1,12 @@ @page "/test/export" -@using Microsoft.AspNetCore.Authorization @using Hcs.Broker @using Hcs.Broker.Logger @using Hcs.Broker.MessageCapturer @using Hcs.Service.Async.Nsi @using Hcs.WebApp.Config @using Hcs.WebApp.Utils +@using Microsoft.AspNetCore.Authorization @implements IDisposable diff --git a/Hcs.WebApp/Components/_Imports.razor b/Hcs.WebApp/Components/_Imports.razor index a09ea1b..9a15bfd 100644 --- a/Hcs.WebApp/Components/_Imports.razor +++ b/Hcs.WebApp/Components/_Imports.razor @@ -1,5 +1,8 @@ @using System.Net.Http @using System.Net.Http.Json +@using Hcs.WebApp +@using Hcs.WebApp.Components +@using Hcs.WebApp.Components.Shared @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @@ -8,8 +11,5 @@ @using Microsoft.JSInterop @using Radzen @using Radzen.Blazor -@using Hcs.WebApp -@using Hcs.WebApp.Components -@using Hcs.WebApp.Components.Shared @using static Microsoft.AspNetCore.Components.Web.RenderMode diff --git a/Hcs.WebApp/Controllers/IdentityController.cs b/Hcs.WebApp/Controllers/IdentityController.cs new file mode 100644 index 0000000..25f1ca1 --- /dev/null +++ b/Hcs.WebApp/Controllers/IdentityController.cs @@ -0,0 +1,55 @@ +using Hcs.WebApp.Data; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Radzen; + +namespace Hcs.WebApp.Controllers +{ + [Route("identity/[action]")] + public class IdentityController( + IUserStore userStore, + UserManager userManager, + SignInManager signInManager) : Controller + { + private readonly IUserStore userStore = userStore; + private readonly UserManager userManager = userManager; + private readonly SignInManager signInManager = signInManager; + + [HttpPost] + public async Task Register(string userName, string password, string returnUrl) + { + var user = Activator.CreateInstance(); + await userStore.SetUserNameAsync(user, userName, CancellationToken.None); + + var result = await userManager.CreateAsync(user, password); + if (!result.Succeeded) + { + var errors = string.Join(", ", result.Errors.Select(error => error.Description)); + if (!string.IsNullOrEmpty(returnUrl)) + { + return Redirect($"/account/register?errors={errors}&returnUrl={Uri.EscapeDataString(returnUrl)}"); + } + else + { + return Redirect($"/account/register?errors={errors}"); + } + } + + await signInManager.SignInAsync(user, isPersistent: false); + + if (string.IsNullOrEmpty(returnUrl)) + { + Redirect("/"); + } + + return Redirect(returnUrl); + } + + public async Task Logout() + { + await signInManager.SignOutAsync(); + + return Redirect("/"); + } + } +} diff --git a/Hcs.WebApp/Data/AppIdentityDbContext.cs b/Hcs.WebApp/Data/AppIdentityDbContext.cs index 3e760cd..391264d 100644 --- a/Hcs.WebApp/Data/AppIdentityDbContext.cs +++ b/Hcs.WebApp/Data/AppIdentityDbContext.cs @@ -3,5 +3,5 @@ using Microsoft.EntityFrameworkCore; namespace Hcs.WebApp.Data { - internal class AppIdentityDbContext(DbContextOptions options) : IdentityDbContext(options) { } + public class AppIdentityDbContext(DbContextOptions options) : IdentityDbContext(options) { } } diff --git a/Hcs.WebApp/Data/AppRole.cs b/Hcs.WebApp/Data/AppRole.cs new file mode 100644 index 0000000..dab2600 --- /dev/null +++ b/Hcs.WebApp/Data/AppRole.cs @@ -0,0 +1,6 @@ +using Microsoft.AspNetCore.Identity; + +namespace Hcs.WebApp.Data +{ + public class AppRole : IdentityRole { } +} diff --git a/Hcs.WebApp/Data/AppUser.cs b/Hcs.WebApp/Data/AppUser.cs index 21b6d93..1abbf81 100644 --- a/Hcs.WebApp/Data/AppUser.cs +++ b/Hcs.WebApp/Data/AppUser.cs @@ -2,5 +2,5 @@ namespace Hcs.WebApp.Data { - internal class AppUser : IdentityUser { } + public class AppUser : IdentityUser { } } diff --git a/Hcs.WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/Hcs.WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.cs index 8cb9094..052ba4a 100644 --- a/Hcs.WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.cs +++ b/Hcs.WebApp/Data/Migrations/00000000000000_CreateIdentitySchema.cs @@ -4,7 +4,7 @@ namespace Hcs.WebApp.Data.Migrations { /// - internal partial class CreateIdentitySchema : Migration + public partial class CreateIdentitySchema : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) diff --git a/Hcs.WebApp/Data/Migrations/AppIdentityDbContextModelSnapshot.cs b/Hcs.WebApp/Data/Migrations/AppIdentityDbContextModelSnapshot.cs index 2fa491b..a63d47d 100644 --- a/Hcs.WebApp/Data/Migrations/AppIdentityDbContextModelSnapshot.cs +++ b/Hcs.WebApp/Data/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; namespace Hcs.WebApp.Data.Migrations { [DbContext(typeof(AppIdentityDbContext))] - internal class AppIdentityDbContextModelSnapshot : ModelSnapshot + public class AppIdentityDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { diff --git a/Hcs.WebApp/Hcs.WebApp.csproj b/Hcs.WebApp/Hcs.WebApp.csproj index 65cd85e..c063d08 100644 --- a/Hcs.WebApp/Hcs.WebApp.csproj +++ b/Hcs.WebApp/Hcs.WebApp.csproj @@ -8,6 +8,7 @@ + diff --git a/Hcs.WebApp/Identity/IdentityComponentsEndpointRouteBuilderExtensions.cs b/Hcs.WebApp/Identity/IdentityComponentsEndpointRouteBuilderExtensions.cs deleted file mode 100644 index 58cc656..0000000 --- a/Hcs.WebApp/Identity/IdentityComponentsEndpointRouteBuilderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Hcs.WebApp.Data; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; - -namespace Hcs.WebApp.Identity -{ - internal static class IdentityComponentsEndpointRouteBuilderExtensions - { - public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEndpointRouteBuilder endpoints) - { - ArgumentNullException.ThrowIfNull(endpoints); - - var accountGroup = endpoints.MapGroup("/account"); - accountGroup.MapPost("/logout", async ( - ClaimsPrincipal user, - SignInManager signInManager, - [FromForm] string returnUrl) => - { - await signInManager.SignOutAsync(); - return TypedResults.LocalRedirect($"~/{returnUrl}"); - }); - - return accountGroup; - } - } -} diff --git a/Hcs.WebApp/Identity/IdentityRedirectManager.cs b/Hcs.WebApp/Identity/IdentityRedirectManager.cs deleted file mode 100644 index ef2bb53..0000000 --- a/Hcs.WebApp/Identity/IdentityRedirectManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.AspNetCore.Components; -using System.Diagnostics.CodeAnalysis; - -namespace Hcs.WebApp.Identity -{ - internal sealed class IdentityRedirectManager(NavigationManager navigationManager) - { - public const string STATUS_COOKIE_NAME = "Identity.StatusMessage"; - - private static readonly CookieBuilder statusCookieBuilder = new() - { - SameSite = SameSiteMode.Strict, - HttpOnly = true, - IsEssential = true, - MaxAge = TimeSpan.FromSeconds(5), - }; - - private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); - - [DoesNotReturn] - public void RedirectTo(string? uri) - { - uri ??= ""; - - if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) - { - uri = navigationManager.ToBaseRelativePath(uri); - } - - navigationManager.NavigateTo(uri); - - throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} может быть использован только при статичном рендеринге"); - } - - [DoesNotReturn] - public void RedirectTo(string uri, Dictionary queryParameters) - { - var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); - var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); - RedirectTo(newUri); - } - - [DoesNotReturn] - public void RedirectToWithStatus(string uri, string message, HttpContext context) - { - context.Response.Cookies.Append(STATUS_COOKIE_NAME, message, statusCookieBuilder.Build(context)); - RedirectTo(uri); - } - - [DoesNotReturn] - public void RedirectToCurrentPage() - { - RedirectTo(CurrentPath); - } - - [DoesNotReturn] - public void RedirectToCurrentPageWithStatus(string message, HttpContext context) - { - RedirectToWithStatus(CurrentPath, message, context); - } - } -} diff --git a/Hcs.WebApp/Identity/IdentityRevalidatingAuthenticationStateProvider.cs b/Hcs.WebApp/Identity/IdentityRevalidatingAuthenticationStateProvider.cs deleted file mode 100644 index dd98fe2..0000000 --- a/Hcs.WebApp/Identity/IdentityRevalidatingAuthenticationStateProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Hcs.WebApp.Data; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Server; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using System.Security.Claims; - -namespace Hcs.WebApp.Identity -{ - internal sealed class IdentityRevalidatingAuthenticationStateProvider( - ILoggerFactory loggerFactory, - IServiceScopeFactory scopeFactory, - IOptions options) : RevalidatingServerAuthenticationStateProvider(loggerFactory) - { - protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); - - protected override async Task ValidateAuthenticationStateAsync( - AuthenticationState authenticationState, CancellationToken cancellationToken) - { - await using var scope = scopeFactory.CreateAsyncScope(); - - var userManager = scope.ServiceProvider.GetRequiredService>(); - return await ValidateSecurityStampAsync(userManager, authenticationState.User); - } - - private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) - { - var user = await userManager.GetUserAsync(principal); - if (user is null) - { - return false; - } - else if (!userManager.SupportsUserSecurityStamp) - { - return true; - } - else - { - var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType); - var userStamp = await userManager.GetSecurityStampAsync(user); - return principalStamp == userStamp; - } - } - } -} diff --git a/Hcs.WebApp/Identity/IdentityUserAccessor.cs b/Hcs.WebApp/Identity/IdentityUserAccessor.cs deleted file mode 100644 index 0446880..0000000 --- a/Hcs.WebApp/Identity/IdentityUserAccessor.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Hcs.WebApp.Data; -using Microsoft.AspNetCore.Identity; - -namespace Hcs.WebApp.Identity -{ - internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) - { - public async Task GetRequiredUserAsync(HttpContext context) - { - var user = await userManager.GetUserAsync(context.User); - if (user is null) - { - redirectManager.RedirectToWithStatus("account/invalid_user", $"Ошибка: Не удалось загрузить пользователя с идентификатором '{userManager.GetUserId(context.User)}'", context); - } - return user; - } - } -} diff --git a/Hcs.WebApp/Program.cs b/Hcs.WebApp/Program.cs index ee6889a..78de197 100644 --- a/Hcs.WebApp/Program.cs +++ b/Hcs.WebApp/Program.cs @@ -1,7 +1,5 @@ using Hcs.WebApp.Components; using Hcs.WebApp.Data; -using Hcs.WebApp.Identity; -using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Radzen; @@ -12,52 +10,49 @@ builder.Services .AddRazorComponents() .AddInteractiveServerComponents(); +builder.Services.AddControllers(); builder.Services.AddRadzenComponents(); -builder.Services.AddCascadingAuthenticationState(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -builder.Services - .AddAuthentication(options => - { - options.DefaultScheme = IdentityConstants.ApplicationScheme; - options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) - .AddIdentityCookies(); +builder.Services.AddHttpClient("Hcs.WebApp").AddHeaderPropagation(x => x.Headers.Add("Cookie")); +builder.Services.AddHeaderPropagation(x => x.Headers.Add("Cookie")); +builder.Services.AddAuthentication(); +builder.Services.AddAuthorization(); var connectionString = builder.Configuration.GetConnectionString("IdentityConnection") ?? throw new InvalidOperationException(" 'IdentityConnection'"); builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); -builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services - .AddIdentityCore() + .AddIdentity(options => + { + options.Password.RequiredLength = 6; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireUppercase = false; + options.Password.RequiredUniqueChars = 1; + }) .AddEntityFrameworkStores() - .AddSignInManager() .AddDefaultTokenProviders(); var app = builder.Build(); -if (app.Environment.IsDevelopment()) -{ - app.UseMigrationsEndPoint(); -} -else +if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); - app.UseStaticFiles(); +app.UseHeaderPropagation(); +app.UseRouting(); app.UseAntiforgery(); +app.UseAuthentication(); +app.UseAuthorization(); +app.MapControllers(); app .MapRazorComponents() .AddInteractiveServerRenderMode(); -app.MapAdditionalIdentityEndpoints(); - app.Run();