[CLAUDE] Auth: user tự đổi mật khẩu (self-service change-password) — BE + 2 FE + test
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m10s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m10s
Go-live request anh Kiệt FDC (Zalo "user chưa tự đổi pass được hả em"): trước đây chỉ admin
Reset hộ (POST users/{id}/reset-password), user KHÔNG tự đổi được. Thêm self-service:
- BE: ChangePasswordCommand + POST /api/auth/change-password ([Authorize]). Lấy user qua
ICurrentUser.UserId; UserManager.ChangePasswordAsync verify mật khẩu HIỆN TẠI; sai →
ValidationException field CurrentPassword "Mật khẩu hiện tại không đúng."; rule mật khẩu mới
≥ 12 + khác mật khẩu cũ (validator); sau đổi vô hiệu refresh token (mirror ResetPassword).
Qualify SolutionErp...ValidationException (tránh CS0104 clash với FluentValidation).
- FE 2 app (SHA-mirror): ChangePasswordDialog (3 ô current/new/confirm + validate client:
≥12, khớp, khác cũ) wire vào menu tài khoản TopBar — "Đổi mật khẩu" trên "Đăng xuất".
- Test: +ChangePasswordCommandTests (đúng/sai pass cũ keyed-field + msg, <12 boundary, ==cũ,
chưa-auth/UserId-null/user-not-found, RefreshToken cleared). Full suite 431 PASS (45D + 386I).
Build: BE 0 warn/0 err · fe-user + fe-admin build OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,58 @@
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using SolutionErp.Application.Common.Exceptions;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Domain.Identity;
|
||||
|
||||
namespace SolutionErp.Application.Auth.Commands.ChangePassword;
|
||||
|
||||
// Self-service đổi mật khẩu — user đang đăng nhập TỰ đổi, BẮT BUỘC nhập mật khẩu hiện tại
|
||||
// để xác thực. Khác ResetPasswordCommand (admin đặt pass hộ theo userId, KHÔNG cần pass cũ).
|
||||
public record ChangePasswordCommand(string CurrentPassword, string NewPassword) : IRequest;
|
||||
|
||||
public class ChangePasswordCommandValidator : AbstractValidator<ChangePasswordCommand>
|
||||
{
|
||||
public ChangePasswordCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CurrentPassword)
|
||||
.NotEmpty().WithMessage("Vui lòng nhập mật khẩu hiện tại.");
|
||||
RuleFor(x => x.NewPassword)
|
||||
.NotEmpty().WithMessage("Vui lòng nhập mật khẩu mới.")
|
||||
.MinimumLength(12).WithMessage("Mật khẩu mới phải có ít nhất 12 ký tự.");
|
||||
RuleFor(x => x.NewPassword)
|
||||
.NotEqual(x => x.CurrentPassword).WithMessage("Mật khẩu mới phải khác mật khẩu hiện tại.")
|
||||
.When(x => !string.IsNullOrEmpty(x.NewPassword) && !string.IsNullOrEmpty(x.CurrentPassword));
|
||||
}
|
||||
}
|
||||
|
||||
public class ChangePasswordCommandHandler(
|
||||
UserManager<User> userManager,
|
||||
ICurrentUser currentUser) : IRequestHandler<ChangePasswordCommand>
|
||||
{
|
||||
public async Task Handle(ChangePasswordCommand request, CancellationToken ct)
|
||||
{
|
||||
if (!currentUser.IsAuthenticated || currentUser.UserId is null)
|
||||
throw new UnauthorizedException();
|
||||
|
||||
var user = await userManager.FindByIdAsync(currentUser.UserId.Value.ToString())
|
||||
?? throw new UnauthorizedException();
|
||||
|
||||
// Identity tự verify CurrentPassword — sai mật khẩu cũ => result.Errors có code "PasswordMismatch".
|
||||
// Luật mật khẩu mới (độ dài, ...) cũng do Identity + validator ở trên kiểm.
|
||||
var result = await userManager.ChangePasswordAsync(user, request.CurrentPassword, request.NewPassword);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
var failures = result.Errors.Select(e => new FluentValidation.Results.ValidationFailure(
|
||||
e.Code == "PasswordMismatch" ? nameof(request.CurrentPassword) : nameof(request.NewPassword),
|
||||
e.Code == "PasswordMismatch" ? "Mật khẩu hiện tại không đúng." : e.Description));
|
||||
throw new SolutionErp.Application.Common.Exceptions.ValidationException(failures);
|
||||
}
|
||||
|
||||
// Vô hiệu refresh token (mirror ResetPasswordCommand) → các phiên hiện có sẽ phải
|
||||
// đăng nhập lại bằng mật khẩu mới khi access token hết hạn.
|
||||
user.RefreshToken = null;
|
||||
user.RefreshTokenExpiresAt = null;
|
||||
await userManager.UpdateAsync(user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user