Backend (7 fixes):
- wallet-service: remove conflicting EF Ignore() calls for mapped backing fields
- fnb-engine: remove KitchenTicket short constructor that set productId=orderItemId
- fnb-engine: replace fire-and-forget Task.Run with direct await for inventory deduction
- TenantMiddleware: implement PostgreSQL RLS SET LOCAL in 4 services (wallet, fnb, inventory, catalog)
- order-service: fix SQL injection pattern in TenantMiddleware with Guid.ToString("D")
- order-service: add ValidateShopAccess() authorization check in SignalR PosHub
- 4 services: register IDbConnection (NpgsqlConnection) in DI for RLS middleware
Frontend (3 fixes):
- PosDataService: return Success=false (not true) when PayOrder response parsing fails
- QrPayment: add _disposed guard to prevent timer race condition after component disposal
- BFF OrderController: add [Authorize] attribute to require JWT for all endpoints
Infrastructure (3 fixes):
- docker-compose: upgrade PostgreSQL 15-alpine to 16-alpine per project spec
- init-databases.sh: add 4 missing marketing service databases (mkt_*)
- Traefik routes: add wallet, catalog, booking routers and /api/v1/stock path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
113 lines
4.4 KiB
C#
113 lines
4.4 KiB
C#
// EN: Middleware to set PostgreSQL session variable for RLS policies.
|
|
// VI: Middleware để đặt biến session PostgreSQL cho RLS policies.
|
|
|
|
using System.Data;
|
|
using Npgsql;
|
|
using OrderService.API.Infrastructure.Tenant;
|
|
|
|
namespace OrderService.API.Middleware;
|
|
|
|
/// <summary>
|
|
/// EN: Sets PostgreSQL session variables (app.current_tenant_id) for row-level security.
|
|
/// This provides defense-in-depth: even if EF Core global query filters are bypassed
|
|
/// (e.g., via raw SQL/Dapper), PostgreSQL RLS policies will still enforce tenant isolation.
|
|
/// VI: Đặt biến session PostgreSQL (app.current_tenant_id) cho bảo mật row-level.
|
|
/// Điều này cung cấp phòng thủ theo chiều sâu: ngay cả khi global query filters của EF Core
|
|
/// bị bỏ qua (ví dụ: qua raw SQL/Dapper), RLS policies của PostgreSQL vẫn đảm bảo cách ly tenant.
|
|
/// </summary>
|
|
public class TenantMiddleware
|
|
{
|
|
private readonly RequestDelegate _next;
|
|
private readonly ILogger<TenantMiddleware> _logger;
|
|
|
|
public TenantMiddleware(RequestDelegate next, ILogger<TenantMiddleware> logger)
|
|
{
|
|
_next = next;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task InvokeAsync(HttpContext context, ITenantProvider tenantProvider, IDbConnection dbConnection)
|
|
{
|
|
var shopId = tenantProvider.GetCurrentShopId();
|
|
var merchantId = tenantProvider.GetCurrentMerchantId();
|
|
var isServiceCall = tenantProvider.IsServiceCall();
|
|
var isAdmin = tenantProvider.IsAdmin();
|
|
|
|
// EN: Skip tenant SET for service-to-service calls and admin users
|
|
// VI: Bỏ qua tenant SET cho cuộc gọi service-to-service và admin users
|
|
if (!isServiceCall && !isAdmin)
|
|
{
|
|
if (shopId.HasValue)
|
|
{
|
|
await SetTenantContextAsync(dbConnection, shopId.Value, merchantId);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug(
|
|
"EN: No tenant context available for request {Path} / " +
|
|
"VI: Không có tenant context cho request {Path}",
|
|
context.Request.Path);
|
|
}
|
|
}
|
|
|
|
await _next(context);
|
|
}
|
|
|
|
/// <summary>
|
|
/// EN: Set PostgreSQL session variables for RLS enforcement.
|
|
/// VI: Đặt biến session PostgreSQL cho RLS enforcement.
|
|
/// </summary>
|
|
private async Task SetTenantContextAsync(IDbConnection dbConnection, Guid shopId, Guid? merchantId)
|
|
{
|
|
try
|
|
{
|
|
if (dbConnection is NpgsqlConnection npgsqlConnection)
|
|
{
|
|
if (npgsqlConnection.State != ConnectionState.Open)
|
|
{
|
|
await npgsqlConnection.OpenAsync();
|
|
}
|
|
|
|
// EN: Set shop_id as the primary tenant identifier
|
|
// VI: Đặt shop_id làm tenant identifier chính
|
|
await using var cmd = npgsqlConnection.CreateCommand();
|
|
cmd.CommandText = $"SET LOCAL app.current_shop_id = '{shopId.ToString("D")}'";
|
|
await cmd.ExecuteNonQueryAsync();
|
|
|
|
if (merchantId.HasValue)
|
|
{
|
|
cmd.CommandText = $"SET LOCAL app.current_merchant_id = '{merchantId.Value.ToString("D")}'";
|
|
await cmd.ExecuteNonQueryAsync();
|
|
}
|
|
|
|
_logger.LogDebug(
|
|
"EN: Tenant context set - ShopId: {ShopId}, MerchantId: {MerchantId} / " +
|
|
"VI: Tenant context đã đặt - ShopId: {ShopId}, MerchantId: {MerchantId}",
|
|
shopId, merchantId);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex,
|
|
"EN: Failed to set PostgreSQL tenant context / " +
|
|
"VI: Không thể đặt PostgreSQL tenant context");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// EN: Extension method for registering TenantMiddleware.
|
|
/// VI: Extension method để đăng ký TenantMiddleware.
|
|
/// </summary>
|
|
public static class TenantMiddlewareExtensions
|
|
{
|
|
/// <summary>
|
|
/// EN: Use tenant middleware for PostgreSQL RLS. Must be placed after UseAuthentication().
|
|
/// VI: Sử dụng tenant middleware cho PostgreSQL RLS. Phải đặt sau UseAuthentication().
|
|
/// </summary>
|
|
public static IApplicationBuilder UseTenantMiddleware(this IApplicationBuilder builder)
|
|
{
|
|
return builder.UseMiddleware<TenantMiddleware>();
|
|
}
|
|
}
|