[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

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:
pqhuy1987
2026-06-26 12:27:52 +07:00
parent 55494ad477
commit e81e87ff9f
7 changed files with 569 additions and 2 deletions

View File

@ -1,6 +1,7 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SolutionErp.Application.Auth.Commands.ChangePassword;
using SolutionErp.Application.Auth.Commands.Login;
using SolutionErp.Application.Auth.Commands.Refresh;
using SolutionErp.Application.Auth.Dtos;
@ -38,4 +39,13 @@ public class AuthController : ControllerBase
{
return NoContent();
}
// Self-service: user đang đăng nhập tự đổi mật khẩu (xác thực mật khẩu hiện tại).
[HttpPost("change-password")]
[Authorize]
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordCommand command, CancellationToken ct)
{
await _mediator.Send(command, ct);
return NoContent();
}
}