diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs
index e57695d9..af3bbeb4 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs
@@ -136,7 +136,7 @@ public class StaffController : ControllerBase
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(newPassword))
return BadRequest(new { success = false, message = "userId và newPassword là bắt buộc." });
- var payload = new { userId, newPassword };
+ var payload = new { newPassword };
return await _iam.PostAsJsonAsync($"/api/v1/users/{userId}/reset-password", payload).ProxyAsync();
}
diff --git a/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommand.cs b/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommand.cs
new file mode 100644
index 00000000..ad1ba8b6
--- /dev/null
+++ b/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommand.cs
@@ -0,0 +1,11 @@
+using MediatR;
+
+namespace IamService.API.Application.Commands.Auth;
+
+///
+/// EN: Admin command to force-reset a user's password (no current password required).
+/// VI: Command admin để reset mật khẩu user (không cần mật khẩu hiện tại).
+///
+public record AdminResetPasswordCommand(
+ Guid UserId,
+ string NewPassword) : IRequest;
diff --git a/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommandHandler.cs b/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommandHandler.cs
new file mode 100644
index 00000000..8972ce7c
--- /dev/null
+++ b/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommandHandler.cs
@@ -0,0 +1,48 @@
+using MediatR;
+using Microsoft.AspNetCore.Identity;
+using IamService.Domain.AggregatesModel.UserAggregate;
+using IamService.Domain.Exceptions;
+
+namespace IamService.API.Application.Commands.Auth;
+
+///
+/// EN: Handler for AdminResetPasswordCommand — uses RemovePassword + AddPassword (no current password needed).
+/// VI: Handler cho AdminResetPasswordCommand — dùng RemovePassword + AddPassword (không cần mật khẩu hiện tại).
+///
+public class AdminResetPasswordCommandHandler : IRequestHandler
+{
+ private readonly UserManager _userManager;
+ private readonly ILogger _logger;
+
+ public AdminResetPasswordCommandHandler(
+ UserManager userManager,
+ ILogger logger)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ public async Task Handle(AdminResetPasswordCommand request, CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("EN: Admin resetting password for user {UserId} / VI: Admin reset mật khẩu cho user {UserId}", request.UserId);
+
+ var user = await _userManager.FindByIdAsync(request.UserId.ToString());
+ if (user == null)
+ throw new DomainException($"User with ID {request.UserId} not found.");
+
+ // EN: Generate reset token and reset password (no current password needed)
+ // VI: Tạo reset token và reset mật khẩu (không cần mật khẩu hiện tại)
+ var token = await _userManager.GeneratePasswordResetTokenAsync(user);
+ var result = await _userManager.ResetPasswordAsync(user, token, request.NewPassword);
+
+ if (!result.Succeeded)
+ {
+ var errors = string.Join(", ", result.Errors.Select(e => e.Description));
+ _logger.LogWarning("EN: Failed to reset password for user {UserId}: {Errors}", request.UserId, errors);
+ return new ChangePasswordCommandResult(false, $"Đổi mật khẩu thất bại: {errors}");
+ }
+
+ _logger.LogInformation("EN: Password reset successfully for user {UserId}", request.UserId);
+ return new ChangePasswordCommandResult(true, "Đổi mật khẩu thành công.");
+ }
+}
diff --git a/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs b/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
index 2eebbbcd..52d3715c 100644
--- a/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
+++ b/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using IamService.API.Application.Common;
+using IamService.API.Application.Commands.Auth;
using IamService.API.Application.Commands.Users;
using IamService.API.Application.Queries.Users;
using IamService.Domain.AggregatesModel.UserAggregate;
@@ -184,6 +185,27 @@ public class UsersController : ControllerBase
}
}
+ ///
+ /// EN: Admin reset password for a user (no current password required).
+ /// VI: Admin reset mật khẩu cho user (không cần mật khẩu hiện tại).
+ ///
+ [HttpPost("{id:guid}/reset-password")]
+ [Authorize(Policy = "OwnerOrAdmin")]
+ [SwaggerOperation(Summary = "Admin reset password", Description = "Resets a user's password without requiring current password. Requires Owner or Admin role.")]
+ [ProducesResponseType(typeof(ChangePasswordCommandResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task AdminResetPassword(
+ [FromRoute] Guid id,
+ [FromBody] AdminResetPasswordRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var command = new AdminResetPasswordCommand(id, request.NewPassword);
+ var result = await _mediator.Send(command, cancellationToken);
+ if (!result.Success)
+ return BadRequest(new { success = false, message = result.Message });
+ return Ok(new { success = true, message = result.Message });
+ }
+
///
/// EN: Delete (deactivate) a user.
/// VI: Xóa (vô hiệu hóa) user.
@@ -423,3 +445,12 @@ public class UserPermissionsDto
///
public IEnumerable Permissions { get; set; } = Enumerable.Empty();
}
+
+///
+/// EN: Request body for admin password reset.
+/// VI: Request body cho admin reset mật khẩu.
+///
+public class AdminResetPasswordRequest
+{
+ public string NewPassword { get; set; } = string.Empty;
+}