RobotNet/RobotNet.IdentityServer/Components/Account/Pages/Password.razor
2025-10-15 15:15:53 +07:00

285 lines
14 KiB
Plaintext

@rendermode InteractiveServer
@using Microsoft.AspNetCore.Identity
@using RobotNet.IdentityServer.Data
@using MudBlazor
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components
@using System.Threading
@using System.ComponentModel.DataAnnotations
@using RobotNet.IdentityServer.Services
@inherits LayoutComponentBase
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject PasswordStrengthService PasswordStrengthService
@inject UserManager<ApplicationUser> UserManager
@inject ISnackbar Snackbar
@inject NavigationManager NavigationManager
<MudSnackbarProvider />
<div class="password-container">
<div class="password-wrapper">
<MudCard Class="password-card" Elevation="0">
<div class="password-header">
<div class="header-content">
<MudIcon Icon="@Icons.Material.Filled.Lock" Class="header-icon" />
<div class="header-text">
<MudText Typo="Typo.h4">Đổi mật khẩu</MudText>
<MudText Typo="Typo.body1">Cập nhật mật khẩu để tăng cường bảo mật</MudText>
</div>
</div>
</div>
<MudCardContent Class="card-content">
<EditForm Model="@model" OnValidSubmit="ChangePassword" @ref="editForm">
<DataAnnotationsValidator />
<div class="form-fields">
<div class="form-group">
<MudTextField Label="Mật khẩu hiện tại"
@bind-Value="model.CurrentPassword"
InputType="@(showCurrentPassword? InputType.Text: InputType.Password)"
Variant="Variant.Outlined"
Class="password-field"
Adornment="Adornment.End"
AdornmentIcon="@(showCurrentPassword? Icons.Material.Filled.Visibility : Icons.Material.Filled.VisibilityOff)"
OnAdornmentClick="() => showCurrentPassword = !showCurrentPassword"
AdornmentAriaLabel="Toggle password visibility"
@onfocus="EnableButtons"
FullWidth="true" />
<ValidationMessage For="@(() => model.CurrentPassword)" class="validation-message" />
</div>
<div class="form-group">
<MudTextField Label="Mật khẩu mới"
@bind-Value="model.NewPassword"
InputType="@(showNewPassword? InputType.Text: InputType.Password)"
Variant="Variant.Outlined"
Class="password-field"
Adornment="Adornment.End"
AdornmentIcon="@(showNewPassword? Icons.Material.Filled.Visibility : Icons.Material.Filled.VisibilityOff)"
OnAdornmentClick="() => showNewPassword = !showNewPassword"
AdornmentAriaLabel="Toggle password visibility"
@onfocus="EnableButtons"
@oninput="OnNewPasswordChanged"
FullWidth="true" />
<ValidationMessage For="@(() => model.NewPassword)" class="validation-message" />
@if (!string.IsNullOrEmpty(model.NewPassword))
{
<div class="password-strength-container">
<MudText Typo="Typo.body2">Độ mạnh: @GetPasswordStrengthText()</MudText>
<MudProgressLinear Value="@GetPasswordStrength()" Color="@GetPasswordStrengthColor()" />
<div class="password-requirements">
<MudText Typo="Typo.caption">Yêu cầu:</MudText>
<div class="requirements-list">
<div class="requirement @(model.NewPassword.Length >= 6 ? "valid" : "invalid")">
<MudIcon Icon="@(model.NewPassword.Length >= 6 ? Icons.Material.Filled.Check : Icons.Material.Filled.Close)" />
<span>Tối thiểu 6 ký tự</span>
</div>
@* <div class="requirement @(model.NewPassword.Any(char.IsUpper) ? "valid" : "invalid")">
<MudIcon Icon="@(model.NewPassword.Any(char.IsUpper) ? Icons.Material.Filled.Check : Icons.Material.Filled.Close)" />
<span>Chữ hoa</span>
</div> *@
<div class="requirement @(model.NewPassword.Any(char.IsLower) ? "valid" : "invalid")">
<MudIcon Icon="@(model.NewPassword.Any(char.IsLower) ? Icons.Material.Filled.Check : Icons.Material.Filled.Close)" />
<span>Chữ thường</span>
</div>
@* <div class="requirement @(model.NewPassword.Any(char.IsDigit) ? "valid" : "invalid")">
<MudIcon Icon="@(model.NewPassword.Any(char.IsDigit) ? Icons.Material.Filled.Check : Icons.Material.Filled.Close)" />
<span>Số</span>
</div>
<div class="requirement @(model.NewPassword.Any(c => !char.IsLetterOrDigit(c)) ? "valid" : "invalid")">
<MudIcon Icon="@(model.NewPassword.Any(c => !char.IsLetterOrDigit(c)) ? Icons.Material.Filled.Check : Icons.Material.Filled.Close)" />
<span>Ký tự đặc biệt</span>
</div> *@
</div>
</div>
</div>
}
</div>
<div class="form-group">
<MudTextField Label="Xác nhận mật khẩu"
@bind-Value="model.ConfirmPassword"
InputType="@(showConfirmPassword? InputType.Text: InputType.Password)"
Variant="Variant.Outlined"
Class="password-field"
Adornment="Adornment.End"
AdornmentIcon="@(showConfirmPassword? Icons.Material.Filled.Visibility : Icons.Material.Filled.VisibilityOff)"
OnAdornmentClick="() => showConfirmPassword = !showConfirmPassword"
AdornmentAriaLabel="Toggle password visibility"
@onfocus="EnableButtons"
FullWidth="true" />
<ValidationMessage For="@(() => model.ConfirmPassword)" class="validation-message" />
@if (!string.IsNullOrEmpty(model.NewPassword) && !string.IsNullOrEmpty(model.ConfirmPassword))
{
<MudText Color="@(model.NewPassword == model.ConfirmPassword ? Color.Success : Color.Error)">
@(model.NewPassword == model.ConfirmPassword ? "Mật khẩu khớp" : "Mật khẩu không khớp")
</MudText>
}
</div>
</div>
@if (!string.IsNullOrEmpty(errorMessage))
{
<MudAlert Severity="Severity.Error" Class="mt-4" ShowCloseIcon="true" CloseIconClicked="() => errorMessage = string.Empty">
@errorMessage
</MudAlert>
}
</EditForm>
</MudCardContent>
<MudCardActions Class="d-flex justify-end gap-2 pb-4 px-4">
<MudButton Variant="Variant.Outlined"
Color="Color.Default"
OnClick="Cancel"
Disabled="@isButtonDisabled"
Class="action-button"
StartIcon="@Icons.Material.Filled.Cancel">
Hủy
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="SubmitForm"
Disabled="@(isButtonDisabled || isProcessing)"
Class="action-button"
StartIcon="@(isProcessing ? null : Icons.Material.Filled.Save)">
@if (isProcessing)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
<span class="ms-2">Đang xử lý...</span>
}
else
{
<span>Lưu</span>
}
</MudButton>
</MudCardActions>
</MudCard>
</div>
</div>
@code {
private bool isButtonDisabled = true;
private bool showCurrentPassword = false;
private bool showNewPassword = false;
private bool showConfirmPassword = false;
private ChangePasswordModel model = new();
private bool isProcessing = false;
private string errorMessage = string.Empty;
private EditForm? editForm;
private class ChangePasswordModel
{
[Required(ErrorMessage = "Vui lòng nhập mật khẩu hiện tại")]
public string CurrentPassword { get; set; } = string.Empty;
[Required(ErrorMessage = "Vui lòng nhập mật khẩu mới")]
[StringLength(100, ErrorMessage = "Mật khẩu phải từ {2} đến {1} ký tự", MinimumLength = 8)]
public string NewPassword { get; set; } = string.Empty;
[Required(ErrorMessage = "Vui lòng xác nhận mật khẩu mới")]
[Compare("NewPassword", ErrorMessage = "Mật khẩu xác nhận không khớp")]
public string ConfirmPassword { get; set; } = string.Empty;
}
private void EnableButtons()
{
isButtonDisabled = false;
}
private void OnNewPasswordChanged(ChangeEventArgs e)
{
model.NewPassword = e.Value?.ToString() ?? string.Empty;
StateHasChanged();
}
private async Task SubmitForm()
{
if (editForm?.EditContext?.Validate() == true)
{
await ChangePassword();
}
else
{
Snackbar.Add("Vui lòng kiểm tra lại thông tin nhập", Severity.Error);
}
}
private int GetPasswordStrength()
{
return PasswordStrengthService.EvaluatePasswordStrength(model.NewPassword);
}
private Color GetPasswordStrengthColor()
{
return PasswordStrengthService.GetStrengthColor(GetPasswordStrength());
}
private string GetPasswordStrengthText()
{
return PasswordStrengthService.GetStrengthDescription(GetPasswordStrength());
}
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState?.User?.Identity?.IsAuthenticated != true)
{
Snackbar.Add("Vui lòng đăng nhập", Severity.Error);
NavigationManager.NavigateTo("/Account/Login");
}
}
private async Task ChangePassword()
{
isProcessing = true;
errorMessage = string.Empty;
try
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = await UserManager.GetUserAsync(authState.User);
if (user == null)
{
Snackbar.Add("Không tìm thấy thông tin người dùng", Severity.Error);
return;
}
var result = await UserManager.ChangePasswordAsync(user, model.CurrentPassword, model.NewPassword);
if (result.Succeeded)
{
Snackbar.Add("Đổi mật khẩu thành công", Severity.Success);
model = new ChangePasswordModel();
isButtonDisabled = true;
}
else
{
errorMessage = string.Join(", ", result.Errors.Select(e => e.Description));
Snackbar.Add(errorMessage, Severity.Error);
}
}
catch (Exception ex)
{
errorMessage = $"Lỗi: {ex.Message}";
Snackbar.Add(errorMessage, Severity.Error);
}
finally
{
isProcessing = false;
StateHasChanged();
}
}
private void Cancel()
{
model = new ChangePasswordModel();
isButtonDisabled = true;
errorMessage = string.Empty;
StateHasChanged();
}
}