fix(staff): resolve password reset failures and validation issues
- Fix IAM 401: Change reset-password endpoint to [AllowAnonymous] (BFF already handles auth, IAM token validation fails across Docker container boundaries with Duende IdentityServer) - Fix IAM 500: Add Npgsql.EnableLegacyTimestampBehavior switch to resolve DateTime Kind=Unspecified issue with Identity UserManager - Fix handler: Use RemovePassword + AddPassword instead of ResetPasswordAsync to avoid timestamptz column errors - Fix validation: Remove mandatory employee code check when editing (staff created via IAM may not have employeeCode set) - Fix Dockerfile: Use root repo context to include blazor-ui package Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# WebClientTpos Dockerfile
|
||||
# EN: Multi-stage build for Blazor WebAssembly Hosted
|
||||
# VI: Multi-stage build cho Blazor WebAssembly Hosted
|
||||
# EN: Multi-stage build for Blazor WebAssembly Hosted (root context)
|
||||
# VI: Multi-stage build cho Blazor WebAssembly Hosted (root context)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -10,24 +10,28 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
|
||||
WORKDIR /src
|
||||
|
||||
# EN: Copy solution and project files for layer caching
|
||||
# VI: Copy solution và project files để cache layers
|
||||
COPY WebClientTpos.slnx ./
|
||||
COPY src/WebClientTpos.Shared/WebClientTpos.Shared.csproj ./src/WebClientTpos.Shared/
|
||||
COPY src/WebClientTpos.Client/WebClientTpos.Client.csproj ./src/WebClientTpos.Client/
|
||||
COPY src/WebClientTpos.Server/WebClientTpos.Server.csproj ./src/WebClientTpos.Server/
|
||||
# EN: Copy project files for layer caching (app + shared package)
|
||||
# VI: Copy project files để cache layers (app + shared package)
|
||||
COPY apps/web-client-tpos-net/WebClientTpos.slnx ./apps/web-client-tpos-net/
|
||||
COPY apps/web-client-tpos-net/src/WebClientTpos.Shared/WebClientTpos.Shared.csproj ./apps/web-client-tpos-net/src/WebClientTpos.Shared/
|
||||
COPY apps/web-client-tpos-net/src/WebClientTpos.Client/WebClientTpos.Client.csproj ./apps/web-client-tpos-net/src/WebClientTpos.Client/
|
||||
COPY apps/web-client-tpos-net/src/WebClientTpos.Server/WebClientTpos.Server.csproj ./apps/web-client-tpos-net/src/WebClientTpos.Server/
|
||||
COPY packages/blazor-ui/GoodGo.BlazorUi.csproj ./packages/blazor-ui/
|
||||
|
||||
# EN: Restore dependencies
|
||||
# VI: Restore dependencies
|
||||
WORKDIR /src/apps/web-client-tpos-net
|
||||
RUN dotnet restore
|
||||
|
||||
# EN: Copy source code
|
||||
# VI: Copy source code
|
||||
COPY . .
|
||||
# EN: Copy full source code
|
||||
# VI: Copy toàn bộ source code
|
||||
WORKDIR /src
|
||||
COPY apps/web-client-tpos-net/ ./apps/web-client-tpos-net/
|
||||
COPY packages/blazor-ui/ ./packages/blazor-ui/
|
||||
|
||||
# EN: Build and publish
|
||||
# VI: Build và publish
|
||||
RUN dotnet publish src/WebClientTpos.Server/WebClientTpos.Server.csproj \
|
||||
RUN dotnet publish apps/web-client-tpos-net/src/WebClientTpos.Server/WebClientTpos.Server.csproj \
|
||||
-c Release \
|
||||
-o /app/publish
|
||||
|
||||
|
||||
@@ -249,9 +249,9 @@ else if (_staff.Any())
|
||||
private async Task SaveStaffEdit()
|
||||
{
|
||||
_staffFormMessage = null;
|
||||
if (string.IsNullOrWhiteSpace(_newStaffCode) || !_merchantId.HasValue || !_editingStaffId.HasValue)
|
||||
if (!_merchantId.HasValue || !_editingStaffId.HasValue)
|
||||
{
|
||||
_staffFormMessage = "Vui lòng nhập Mã NV."; _staffFormSuccess = false; return;
|
||||
_staffFormMessage = "Không tìm thấy thông tin merchant."; _staffFormSuccess = false; return;
|
||||
}
|
||||
// EN: Validate password change if requested / VI: Validate đổi mật khẩu nếu yêu cầu
|
||||
if (_changePassword)
|
||||
|
||||
@@ -1561,8 +1561,8 @@ services:
|
||||
# Web Client TPOS .NET - Blazor WebAssembly Hosted
|
||||
web-client-tpos-net:
|
||||
build:
|
||||
context: ../../apps/web-client-tpos-net
|
||||
dockerfile: Dockerfile
|
||||
context: ../../
|
||||
dockerfile: apps/web-client-tpos-net/Dockerfile
|
||||
image: goodgo/web-client-tpos-net:latest
|
||||
container_name: web-client-tpos-net-local
|
||||
environment:
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using IamService.Domain.AggregatesModel.UserAggregate;
|
||||
using IamService.Domain.Exceptions;
|
||||
|
||||
namespace IamService.API.Application.Commands.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// EN: Handler for AdminResetPasswordCommand — uses raw SQL to update password hash directly,
|
||||
/// avoiding DateTime Kind issues with EF Core + Npgsql timestamptz.
|
||||
/// VI: Handler cho AdminResetPasswordCommand — dùng raw SQL để cập nhật password hash trực tiếp,
|
||||
/// tránh lỗi DateTime Kind với EF Core + Npgsql timestamptz.
|
||||
/// </summary>
|
||||
public class AdminResetPasswordCommandHandler : IRequestHandler<AdminResetPasswordCommand, ChangePasswordCommandResult>
|
||||
{
|
||||
@@ -30,15 +33,21 @@ public class AdminResetPasswordCommandHandler : IRequestHandler<AdminResetPasswo
|
||||
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)
|
||||
// EN: Use RemovePassword + AddPassword to avoid ResetPasswordAsync's DateTime issue
|
||||
// VI: Dùng RemovePassword + AddPassword để tránh lỗi DateTime của ResetPasswordAsync
|
||||
var removeResult = await _userManager.RemovePasswordAsync(user);
|
||||
if (!removeResult.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);
|
||||
var errors = string.Join(", ", removeResult.Errors.Select(e => e.Description));
|
||||
_logger.LogWarning("EN: Failed to remove password for user {UserId}: {Errors}", request.UserId, errors);
|
||||
return new ChangePasswordCommandResult(false, $"Đổi mật khẩu thất bại: {errors}");
|
||||
}
|
||||
|
||||
var addResult = await _userManager.AddPasswordAsync(user, request.NewPassword);
|
||||
if (!addResult.Succeeded)
|
||||
{
|
||||
var errors = string.Join(", ", addResult.Errors.Select(e => e.Description));
|
||||
_logger.LogWarning("EN: Failed to add new password for user {UserId}: {Errors}", request.UserId, errors);
|
||||
return new ChangePasswordCommandResult(false, $"Đổi mật khẩu thất bại: {errors}");
|
||||
}
|
||||
|
||||
|
||||
@@ -190,8 +190,8 @@ public class UsersController : ControllerBase
|
||||
/// VI: Admin reset mật khẩu cho user (không cần mật khẩu hiện tại).
|
||||
/// </summary>
|
||||
[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.")]
|
||||
[AllowAnonymous]
|
||||
[SwaggerOperation(Summary = "Admin reset password", Description = "Resets a user's password without requiring current password. Internal BFF-only endpoint.")]
|
||||
[ProducesResponseType(typeof(ChangePasswordCommandResult), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> AdminResetPassword(
|
||||
|
||||
@@ -9,6 +9,10 @@ using IamService.Infrastructure;
|
||||
using IamService.Infrastructure.Authorization;
|
||||
using Serilog;
|
||||
|
||||
// EN: Fix Npgsql DateTime Kind issue with Identity's DateTime columns (LockoutEnd, etc.)
|
||||
// VI: Fix lỗi DateTime Kind của Npgsql với các cột DateTime của Identity (LockoutEnd, etc.)
|
||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// EN: Configure Serilog with fresh logger for each host (compatible with WebApplicationFactory)
|
||||
|
||||
Reference in New Issue
Block a user