Add password changing

This commit is contained in:
2025-10-17 11:02:50 +09:00
parent 5ae278eeca
commit 241171de17
7 changed files with 154 additions and 6 deletions

View File

@ -23,7 +23,7 @@
<RadzenPanelMenu>
<AuthorizeView>
<Authorized>
<RadzenPanelMenuItem Text="@context.User.Identity?.Name" />
<RadzenPanelMenuItem Path="/account/manage" Text="@context.User.Identity?.Name" />
<RadzenPanelMenuItem Path="/identity/logout" Text="Выйти" Icon="logout" />
</Authorized>
<NotAuthorized>

View File

@ -20,7 +20,7 @@
</RadzenFormField>
<RadzenFormField Text="Пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="Password" @bind-Value=@Input.Password AutoCompleteType="AutoCompleteType.NewPassword" />
<RadzenPassword Name="Password" @bind-Value=@Input.Password AutoCompleteType="AutoCompleteType.CurrentPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="Password" Text="Поле 'Пароль' обязательно к заполнению" />

View File

@ -0,0 +1,96 @@
@page "/account/manage"
@using Hcs.WebApp.Services
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject IdentityService IdentityService
<PageTitle>Профиль</PageTitle>
<RadzenTabs RenderMode="TabRenderMode.Server">
<Tabs>
<RadzenTabsItem Text="Пароль">
<div style="max-width: 420px">
<RadzenTemplateForm TItem="PasswordInputModel" Data=@PasswordInput Method="post" Submit="@ChangePassword">
<RadzenAlert AlertStyle="AlertStyle.Danger" Variant="Variant.Flat" Shade="Shade.Lighter" Visible="@hasError">
@errorMessage
</RadzenAlert>
<RadzenAlert AlertStyle="AlertStyle.Success" Variant="Variant.Flat" Shade="Shade.Lighter" Visible="@hasSuccess">
Пароль успешно изменен
</RadzenAlert>
<RadzenStack Gap="1rem" class="rz-p-sm-12">
<RadzenText TextStyle="TextStyle.H5">Смена пароля</RadzenText>
<RadzenFormField Text="Текущий пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="OldPassword" @bind-Value=@PasswordInput.OldPassword AutoCompleteType="AutoCompleteType.CurrentPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="OldPassword" Text="Поле 'Текущий пароль' обязательно к заполнению" />
<RadzenLengthValidator Component="OldPassword" Min="6" Text="Длина поля 'Текущий пароль' должна быть не меньше 6" />
<RadzenLengthValidator Component="OldPassword" Max="100" Text="Длина поля 'Текущий пароль' должна быть не больше 100" />
</Helper>
</RadzenFormField>
<RadzenFormField Text="Новый пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="NewPassword" @bind-Value=@PasswordInput.NewPassword AutoCompleteType="AutoCompleteType.NewPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="NewPassword" Text="Поле 'Новый пароль' обязательно к заполнению" />
<RadzenLengthValidator Component="NewPassword" Min="6" Text="Длина поля 'Новый пароль' должна быть не меньше 6" />
<RadzenLengthValidator Component="NewPassword" Max="100" Text="Длина поля 'Новый пароль' должна быть не больше 100" />
</Helper>
</RadzenFormField>
<RadzenFormField Text="Повторите новый пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="ConfirmNewPassword" @bind-Value=@PasswordInput.ConfirmNewPassword AutoCompleteType="AutoCompleteType.NewPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="ConfirmNewPassword" Text="Поле 'Повторите новый пароль' обязательно к заполнению" />
<RadzenCompareValidator Value=@PasswordInput.NewPassword Component="ConfirmNewPassword" Text="Пароли должны совпадать" />
</Helper>
</RadzenFormField>
<RadzenButton ButtonType="ButtonType.Submit" Text="Сменить пароль"></RadzenButton>
</RadzenStack>
</RadzenTemplateForm>
</div>
</RadzenTabsItem>
</Tabs>
</RadzenTabs>
@code {
sealed class PasswordInputModel
{
public string OldPassword { get; set; } = "";
public string NewPassword { get; set; } = "";
public string ConfirmNewPassword { get; set; } = "";
}
bool hasError;
string? errorMessage;
bool hasSuccess;
[SupplyParameterFromForm]
PasswordInputModel PasswordInput { get; set; } = new();
async Task ChangePassword()
{
hasError = false;
hasSuccess = false;
try
{
await IdentityService.ChangePassword(PasswordInput.OldPassword, PasswordInput.NewPassword);
hasSuccess = true;
}
catch (Exception e)
{
hasError = true;
errorMessage = e.Message;
}
}
}

View File

@ -33,7 +33,7 @@
<RadzenPassword Name="ConfirmPassword" @bind-Value=@Input.ConfirmPassword AutoCompleteType="AutoCompleteType.NewPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="ConfirmPassword" Text="Поле 'Пароль' обязательно к заполнению" />
<RadzenRequiredValidator Component="ConfirmPassword" Text="Поле 'Повторите пароль' обязательно к заполнению" />
<RadzenCompareValidator Value=@Input.Password Component="ConfirmPassword" Text="Пароли должны совпадать" />
</Helper>
</RadzenFormField>

View File

@ -1,11 +1,14 @@
using Hcs.WebApp.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Radzen;
using System.Security.Claims;
namespace Hcs.WebApp.Controllers
{
[Route("identity/[action]")]
[Route("identity/")]
[Authorize]
public class IdentityController(
IUserStore<AppUser> userStore,
UserManager<AppUser> userManager,
@ -16,6 +19,8 @@ namespace Hcs.WebApp.Controllers
private readonly SignInManager<AppUser> signInManager = signInManager;
[HttpPost]
[Route("register")]
[AllowAnonymous]
public async Task<IActionResult> Register(string userName, string password, string returnUrl)
{
var user = Activator.CreateInstance<AppUser>();
@ -24,7 +29,7 @@ namespace Hcs.WebApp.Controllers
var result = await userManager.CreateAsync(user, password);
if (!result.Succeeded)
{
var error = string.Join(", ", result.Errors.Select(error => error.Description));
var error = string.Join(", ", result.Errors.Select(x => x.Description));
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect($"/account/register?error={error}&returnUrl={Uri.EscapeDataString(returnUrl)}");
@ -46,6 +51,8 @@ namespace Hcs.WebApp.Controllers
}
[HttpPost]
[Route("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(string userName, string password, string returnUrl)
{
var result = await signInManager.PasswordSignInAsync(userName, password, false, false);
@ -69,11 +76,29 @@ namespace Hcs.WebApp.Controllers
return Redirect(returnUrl);
}
[HttpGet]
[Route("logout")]
public async Task<IActionResult> Logout()
{
await signInManager.SignOutAsync();
return Redirect("/");
}
[HttpPost]
[Route("change-password")]
public async Task<IActionResult> ChangePassword(string oldPassword, string newPassword)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await userManager.FindByIdAsync(userId);
var result = await userManager.ChangePasswordAsync(user, oldPassword, newPassword);
if (result.Succeeded)
{
return Ok();
}
var message = string.Join(", ", result.Errors.Select(x => x.Description));
return BadRequest(message);
}
}
}

View File

@ -14,11 +14,13 @@ builder.Services
builder.Services.AddControllers();
builder.Services.AddRadzenComponents();
builder.Services.AddHttpClient("Hcs.WebApp").AddHeaderPropagation(x => x.Headers.Add("Cookie"));
builder.Services.AddHttpClient("WithIdentity").AddHeaderPropagation(x => x.Headers.Add("Cookie"));
builder.Services.AddHeaderPropagation(x => x.Headers.Add("Cookie"));
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
builder.Services.AddScoped<IdentityService>();
var connectionString = builder.Configuration.GetConnectionString("IdentityConnection") ?? throw new InvalidOperationException("<22><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> 'IdentityConnection'");
builder.Services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(connectionString));

View File

@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Components;
namespace Hcs.WebApp.Services
{
public class IdentityService(NavigationManager navigationManager, IHttpClientFactory factory)
{
private readonly Uri baseUri = new($"{navigationManager.BaseUri}identity/");
private readonly HttpClient httpClient = factory.CreateClient("WithIdentity");
public async Task ChangePassword(string oldPassword, string newPassword)
{
var uri = new Uri($"{baseUri}change-password");
var content = new FormUrlEncodedContent(new Dictionary<string, string> {
{ "oldPassword", oldPassword },
{ "newPassword", newPassword }
});
var response = await httpClient.PostAsync(uri, content);
if (!response.IsSuccessStatusCode)
{
var message = await response.Content.ReadAsStringAsync();
throw new ApplicationException(message);
}
}
}
}