285 lines
14 KiB
Plaintext
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();
|
|
}
|
|
} |