From 97b54ebd3929cd74dd48f0b45a87ec4c3cacdaf7 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Mon, 23 Mar 2026 09:48:22 +0700 Subject: [PATCH] =?UTF-8?q?fix(security):=20fix=205=20P1=20backend=20issue?= =?UTF-8?q?s=20=E2=80=94=20BACK-C-01/03/04,=20BACK-W-02?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BACK-W-02: Replace string-interpolated SET LOCAL SQL with parameterized set_config() calls in TenantMiddleware across 5 services (order, wallet, inventory, catalog, fnb-engine). Eliminates SQL injection pattern; set_config(key, $1, true) is local-to-transaction, same semantics as SET LOCAL. BACK-C-01: Remove AllowAnyOrigin() from all 26 services. Switch to WithOrigins() reading AllowedOrigins config array, with dev-only fallback to localhost. In production, set AllowedOrigins=["https://goodgo.vn", "https://admin.goodgo.vn"] via environment config. BACK-C-03: Standardize OrdersController GET /orders/{id} 404 response from {Message:...} to {success:false, error:{code,message}} per API contract. BACK-C-04: Add complete ProblemDetails exception mappings to _template_dot_net: ValidationException -> 400, DomainException -> 422, with TODO comments for service-specific types (EntityNotFoundException -> 404, etc.). BACK-C-02: wallet-service and booking-service already have full IRequestManager idempotency implementation — no changes needed. Co-Authored-By: Paperclip --- .../src/MyService.API/Program.cs | 65 ++++++++++++++++++- .../src/AdsAnalyticsService.API/Program.cs | 4 +- .../src/AdsBillingService.API/Program.cs | 4 +- .../src/AdsManagerService.API/Program.cs | 4 +- .../src/AdsServingService.API/Program.cs | 4 +- .../src/AdsTrackingService.API/Program.cs | 4 +- .../src/BookingService.API/Program.cs | 4 +- .../Middleware/TenantMiddleware.cs | 15 +++-- .../src/CatalogService.API/Program.cs | 4 +- .../Middleware/TenantMiddleware.cs | 15 +++-- .../src/FnbEngine.API/Program.cs | 4 +- .../src/IamService.API/Program.cs | 4 +- .../Middleware/TenantMiddleware.cs | 15 +++-- .../src/InventoryService.API/Program.cs | 4 +- .../src/MembershipService.API/Program.cs | 4 +- .../src/MerchantService.API/Program.cs | 4 +- .../src/MissionService.API/Program.cs | 4 +- .../src/FacebookService.API/Program.cs | 4 +- .../src/WhatsAppService.API/Program.cs | 4 +- .../src/MktXService.API/Program.cs | 4 +- .../src/MktZaloService.API/Program.cs | 4 +- .../Controllers/OrdersController.cs | 2 +- .../Middleware/TenantMiddleware.cs | 15 +++-- .../src/PromotionService.API/Program.cs | 4 +- .../src/SocialService.API/Program.cs | 4 +- .../src/StorageService.API/Program.cs | 4 +- .../Middleware/TenantMiddleware.cs | 15 +++-- .../src/WalletService.API/Program.cs | 4 +- 28 files changed, 181 insertions(+), 45 deletions(-) diff --git a/services/_template_dot_net/src/MyService.API/Program.cs b/services/_template_dot_net/src/MyService.API/Program.cs index bd9b3df4..267f4f68 100644 --- a/services/_template_dot_net/src/MyService.API/Program.cs +++ b/services/_template_dot_net/src/MyService.API/Program.cs @@ -57,11 +57,63 @@ try // EN: Add controllers / VI: Thêm controllers builder.Services.AddControllers(); - // EN: Add ProblemDetails middleware (RFC 7807) / VI: Thêm ProblemDetails middleware + // EN: Add ProblemDetails middleware (RFC 7807) with domain exception mappings. + // All services MUST map their domain exceptions here so ProblemDetails middleware + // handles them before the generic 500 fallback. + // VI: Thêm ProblemDetails middleware (RFC 7807) với domain exception mappings. + // Mọi service PHẢI map domain exceptions ở đây để ProblemDetails middleware + // xử lý chúng trước fallback 500 chung. builder.Services.AddProblemDetails(options => { options.IncludeExceptionDetails = (ctx, ex) => builder.Environment.IsDevelopment(); + + // EN: Map FluentValidation.ValidationException to 400 BadRequest with field-level errors. + // VI: Map FluentValidation.ValidationException sang 400 BadRequest với lỗi theo field. + options.Map(ex => + { + var errors = ex.Errors + .GroupBy(e => e.PropertyName) + .ToDictionary( + g => g.Key, + g => g.Select(e => e.ErrorMessage).ToArray() + ); + + return new Microsoft.AspNetCore.Mvc.ValidationProblemDetails(errors) + { + Title = "Validation Error", + Status = StatusCodes.Status400BadRequest, + Detail = "One or more validation errors occurred.", + Type = "https://httpstatuses.io/400" + }; + }); + + // EN: Map DomainException (base) to 422 Unprocessable Entity. + // Replace with your specific domain exception types: + // e.g. DuplicateResourceException -> 409, EntityNotFoundException -> 404 + // VI: Map DomainException (base) sang 422 Unprocessable Entity. + // Thay bằng các domain exception cụ thể của service: + // ví dụ DuplicateResourceException -> 409, EntityNotFoundException -> 404 + options.Map(ex => + new Microsoft.AspNetCore.Mvc.ProblemDetails + { + Title = "Business Rule Violation", + Status = StatusCodes.Status422UnprocessableEntity, + Detail = ex.Message, + Type = "https://httpstatuses.io/422" + }); + + // EN: TODO — add service-specific mappings below following this pattern: + // + // options.Map(ex => + // new ProblemDetails { Title = "Not Found", Status = 404, Detail = ex.Message, + // Type = "https://httpstatuses.io/404" }); + // + // options.Map(ex => + // new ProblemDetails { Title = "Conflict", Status = 409, Detail = ex.Message, + // Type = "https://httpstatuses.io/409" }); + // + // VI: TODO — thêm mappings riêng cho từng service theo pattern này. }); // EN: Add Swagger / VI: Thêm Swagger @@ -85,12 +137,19 @@ try name: "postgresql", tags: ["db", "postgresql"]); - // EN: Add CORS / VI: Thêm CORS + // EN: Add CORS — restrict to allowed origins. In production, set AllowedOrigins in config + // to ["https://goodgo.vn", "https://admin.goodgo.vn"]. Dev fallback is localhost only. + // NOTE: Do NOT use AllowAnyOrigin() in any environment — Traefik handles external traffic. + // VI: Thêm CORS — giới hạn origins được phép. Trong production, đặt AllowedOrigins trong config + // thành ["https://goodgo.vn", "https://admin.goodgo.vn"]. Dev fallback chỉ localhost. + // LƯU Ý: KHÔNG dùng AllowAnyOrigin() trong bất kỳ môi trường nào — Traefik xử lý traffic ngoài. builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Program.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Program.cs index d0ce4823..b20d6258 100644 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Program.cs +++ b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Program.cs @@ -114,7 +114,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Program.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Program.cs index c52e37de..bcc6aa10 100644 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Program.cs +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Program.cs @@ -114,7 +114,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Program.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Program.cs index 55672b37..fb335d7e 100644 --- a/services/ads-manager-service-net/src/AdsManagerService.API/Program.cs +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Program.cs @@ -114,7 +114,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/ads-serving-service-net/src/AdsServingService.API/Program.cs b/services/ads-serving-service-net/src/AdsServingService.API/Program.cs index ef851ccf..21c26f3d 100644 --- a/services/ads-serving-service-net/src/AdsServingService.API/Program.cs +++ b/services/ads-serving-service-net/src/AdsServingService.API/Program.cs @@ -119,7 +119,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Program.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Program.cs index 64398b6d..cbe7bb5f 100644 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Program.cs +++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Program.cs @@ -114,7 +114,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/booking-service-net/src/BookingService.API/Program.cs b/services/booking-service-net/src/BookingService.API/Program.cs index 20cf2079..76dd1a26 100644 --- a/services/booking-service-net/src/BookingService.API/Program.cs +++ b/services/booking-service-net/src/BookingService.API/Program.cs @@ -91,7 +91,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/catalog-service-net/src/CatalogService.API/Middleware/TenantMiddleware.cs b/services/catalog-service-net/src/CatalogService.API/Middleware/TenantMiddleware.cs index 58cad899..0145058c 100644 --- a/services/catalog-service-net/src/CatalogService.API/Middleware/TenantMiddleware.cs +++ b/services/catalog-service-net/src/CatalogService.API/Middleware/TenantMiddleware.cs @@ -64,15 +64,22 @@ public class TenantMiddleware await npgsqlConnection.OpenAsync(); } - // EN: Set shop_id as the primary tenant identifier - // VI: Đặt shop_id làm tenant identifier chính + // EN: Set shop_id as the primary tenant identifier using parameterized set_config() + // to prevent SQL injection. set_config(key, value, is_local) is equivalent + // to SET LOCAL but supports proper parameter binding. + // VI: Đặt shop_id làm tenant identifier chính dùng set_config() có tham số + // để ngăn SQL injection. set_config(key, value, is_local) tương đương + // SET LOCAL nhưng hỗ trợ parameter binding đúng cách. await using var cmd = npgsqlConnection.CreateCommand(); - cmd.CommandText = $"SET LOCAL app.current_shop_id = '{shopId.ToString("D")}'"; + cmd.CommandText = "SELECT set_config('app.current_shop_id', $1, true)"; + cmd.Parameters.AddWithValue(shopId.ToString("D")); await cmd.ExecuteNonQueryAsync(); if (merchantId.HasValue) { - cmd.CommandText = $"SET LOCAL app.current_merchant_id = '{merchantId.Value.ToString("D")}'"; + cmd.Parameters.Clear(); + cmd.CommandText = "SELECT set_config('app.current_merchant_id', $1, true)"; + cmd.Parameters.AddWithValue(merchantId.Value.ToString("D")); await cmd.ExecuteNonQueryAsync(); } diff --git a/services/catalog-service-net/src/CatalogService.API/Program.cs b/services/catalog-service-net/src/CatalogService.API/Program.cs index bda56579..dcef9e46 100644 --- a/services/catalog-service-net/src/CatalogService.API/Program.cs +++ b/services/catalog-service-net/src/CatalogService.API/Program.cs @@ -127,7 +127,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/fnb-engine-net/src/FnbEngine.API/Middleware/TenantMiddleware.cs b/services/fnb-engine-net/src/FnbEngine.API/Middleware/TenantMiddleware.cs index 53d55ea1..6527b466 100644 --- a/services/fnb-engine-net/src/FnbEngine.API/Middleware/TenantMiddleware.cs +++ b/services/fnb-engine-net/src/FnbEngine.API/Middleware/TenantMiddleware.cs @@ -64,15 +64,22 @@ public class TenantMiddleware await npgsqlConnection.OpenAsync(); } - // EN: Set shop_id as the primary tenant identifier - // VI: Đặt shop_id làm tenant identifier chính + // EN: Set shop_id as the primary tenant identifier using parameterized set_config() + // to prevent SQL injection. set_config(key, value, is_local) is equivalent + // to SET LOCAL but supports proper parameter binding. + // VI: Đặt shop_id làm tenant identifier chính dùng set_config() có tham số + // để ngăn SQL injection. set_config(key, value, is_local) tương đương + // SET LOCAL nhưng hỗ trợ parameter binding đúng cách. await using var cmd = npgsqlConnection.CreateCommand(); - cmd.CommandText = $"SET LOCAL app.current_shop_id = '{shopId.ToString("D")}'"; + cmd.CommandText = "SELECT set_config('app.current_shop_id', $1, true)"; + cmd.Parameters.AddWithValue(shopId.ToString("D")); await cmd.ExecuteNonQueryAsync(); if (merchantId.HasValue) { - cmd.CommandText = $"SET LOCAL app.current_merchant_id = '{merchantId.Value.ToString("D")}'"; + cmd.Parameters.Clear(); + cmd.CommandText = "SELECT set_config('app.current_merchant_id', $1, true)"; + cmd.Parameters.AddWithValue(merchantId.Value.ToString("D")); await cmd.ExecuteNonQueryAsync(); } diff --git a/services/fnb-engine-net/src/FnbEngine.API/Program.cs b/services/fnb-engine-net/src/FnbEngine.API/Program.cs index f43faa1c..ea46799f 100644 --- a/services/fnb-engine-net/src/FnbEngine.API/Program.cs +++ b/services/fnb-engine-net/src/FnbEngine.API/Program.cs @@ -114,7 +114,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/iam-service-net/src/IamService.API/Program.cs b/services/iam-service-net/src/IamService.API/Program.cs index 5bb26e6c..93b2dbe5 100644 --- a/services/iam-service-net/src/IamService.API/Program.cs +++ b/services/iam-service-net/src/IamService.API/Program.cs @@ -243,7 +243,9 @@ builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/inventory-service-net/src/InventoryService.API/Middleware/TenantMiddleware.cs b/services/inventory-service-net/src/InventoryService.API/Middleware/TenantMiddleware.cs index c8c10931..9489da0f 100644 --- a/services/inventory-service-net/src/InventoryService.API/Middleware/TenantMiddleware.cs +++ b/services/inventory-service-net/src/InventoryService.API/Middleware/TenantMiddleware.cs @@ -64,15 +64,22 @@ public class TenantMiddleware await npgsqlConnection.OpenAsync(); } - // EN: Set shop_id as the primary tenant identifier - // VI: Đặt shop_id làm tenant identifier chính + // EN: Set shop_id as the primary tenant identifier using parameterized set_config() + // to prevent SQL injection. set_config(key, value, is_local) is equivalent + // to SET LOCAL but supports proper parameter binding. + // VI: Đặt shop_id làm tenant identifier chính dùng set_config() có tham số + // để ngăn SQL injection. set_config(key, value, is_local) tương đương + // SET LOCAL nhưng hỗ trợ parameter binding đúng cách. await using var cmd = npgsqlConnection.CreateCommand(); - cmd.CommandText = $"SET LOCAL app.current_shop_id = '{shopId.ToString("D")}'"; + cmd.CommandText = "SELECT set_config('app.current_shop_id', $1, true)"; + cmd.Parameters.AddWithValue(shopId.ToString("D")); await cmd.ExecuteNonQueryAsync(); if (merchantId.HasValue) { - cmd.CommandText = $"SET LOCAL app.current_merchant_id = '{merchantId.Value.ToString("D")}'"; + cmd.Parameters.Clear(); + cmd.CommandText = "SELECT set_config('app.current_merchant_id', $1, true)"; + cmd.Parameters.AddWithValue(merchantId.Value.ToString("D")); await cmd.ExecuteNonQueryAsync(); } diff --git a/services/inventory-service-net/src/InventoryService.API/Program.cs b/services/inventory-service-net/src/InventoryService.API/Program.cs index dea3326f..3162d72a 100644 --- a/services/inventory-service-net/src/InventoryService.API/Program.cs +++ b/services/inventory-service-net/src/InventoryService.API/Program.cs @@ -113,7 +113,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/membership-service-net/src/MembershipService.API/Program.cs b/services/membership-service-net/src/MembershipService.API/Program.cs index d9cba3dd..ce0b937d 100644 --- a/services/membership-service-net/src/MembershipService.API/Program.cs +++ b/services/membership-service-net/src/MembershipService.API/Program.cs @@ -162,7 +162,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/merchant-service-net/src/MerchantService.API/Program.cs b/services/merchant-service-net/src/MerchantService.API/Program.cs index f6963a9c..86ac075e 100644 --- a/services/merchant-service-net/src/MerchantService.API/Program.cs +++ b/services/merchant-service-net/src/MerchantService.API/Program.cs @@ -101,7 +101,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/mission-service-net/src/MissionService.API/Program.cs b/services/mission-service-net/src/MissionService.API/Program.cs index fe12614c..3769fa18 100644 --- a/services/mission-service-net/src/MissionService.API/Program.cs +++ b/services/mission-service-net/src/MissionService.API/Program.cs @@ -91,7 +91,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/mkt-facebook-service-net/src/FacebookService.API/Program.cs b/services/mkt-facebook-service-net/src/FacebookService.API/Program.cs index a69e738d..fd53b69d 100644 --- a/services/mkt-facebook-service-net/src/FacebookService.API/Program.cs +++ b/services/mkt-facebook-service-net/src/FacebookService.API/Program.cs @@ -92,7 +92,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/mkt-whatsapp-service-net/src/WhatsAppService.API/Program.cs b/services/mkt-whatsapp-service-net/src/WhatsAppService.API/Program.cs index dd1c39a9..f22c512e 100644 --- a/services/mkt-whatsapp-service-net/src/WhatsAppService.API/Program.cs +++ b/services/mkt-whatsapp-service-net/src/WhatsAppService.API/Program.cs @@ -92,7 +92,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/mkt-x-service-net/src/MktXService.API/Program.cs b/services/mkt-x-service-net/src/MktXService.API/Program.cs index fa5baff0..4d0f5759 100644 --- a/services/mkt-x-service-net/src/MktXService.API/Program.cs +++ b/services/mkt-x-service-net/src/MktXService.API/Program.cs @@ -92,7 +92,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/mkt-zalo-service-net/src/MktZaloService.API/Program.cs b/services/mkt-zalo-service-net/src/MktZaloService.API/Program.cs index 47f0e741..75625d36 100644 --- a/services/mkt-zalo-service-net/src/MktZaloService.API/Program.cs +++ b/services/mkt-zalo-service-net/src/MktZaloService.API/Program.cs @@ -96,7 +96,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/order-service-net/src/OrderService.API/Controllers/OrdersController.cs b/services/order-service-net/src/OrderService.API/Controllers/OrdersController.cs index e2bc8012..28f445b5 100644 --- a/services/order-service-net/src/OrderService.API/Controllers/OrdersController.cs +++ b/services/order-service-net/src/OrderService.API/Controllers/OrdersController.cs @@ -70,7 +70,7 @@ public class OrdersController : ControllerBase if (result == null) { - return NotFound(new { Message = $"Order with ID {id} not found" }); + return NotFound(new { success = false, error = new { code = "ORDER_NOT_FOUND", message = $"Order with ID {id} not found" } }); } return Ok(result); diff --git a/services/order-service-net/src/OrderService.API/Middleware/TenantMiddleware.cs b/services/order-service-net/src/OrderService.API/Middleware/TenantMiddleware.cs index 7a98b097..0cd9a611 100644 --- a/services/order-service-net/src/OrderService.API/Middleware/TenantMiddleware.cs +++ b/services/order-service-net/src/OrderService.API/Middleware/TenantMiddleware.cs @@ -68,15 +68,22 @@ public class TenantMiddleware await npgsqlConnection.OpenAsync(); } - // EN: Set shop_id as the primary tenant identifier - // VI: Đặt shop_id làm tenant identifier chính + // EN: Set shop_id as the primary tenant identifier using parameterized set_config() + // to prevent SQL injection. set_config(key, value, is_local) is equivalent + // to SET LOCAL but supports proper parameter binding. + // VI: Đặt shop_id làm tenant identifier chính dùng set_config() có tham số + // để ngăn SQL injection. set_config(key, value, is_local) tương đương + // SET LOCAL nhưng hỗ trợ parameter binding đúng cách. await using var cmd = npgsqlConnection.CreateCommand(); - cmd.CommandText = $"SET LOCAL app.current_shop_id = '{shopId.ToString("D")}'"; + cmd.CommandText = "SELECT set_config('app.current_shop_id', $1, true)"; + cmd.Parameters.AddWithValue(shopId.ToString("D")); await cmd.ExecuteNonQueryAsync(); if (merchantId.HasValue) { - cmd.CommandText = $"SET LOCAL app.current_merchant_id = '{merchantId.Value.ToString("D")}'"; + cmd.Parameters.Clear(); + cmd.CommandText = "SELECT set_config('app.current_merchant_id', $1, true)"; + cmd.Parameters.AddWithValue(merchantId.Value.ToString("D")); await cmd.ExecuteNonQueryAsync(); } diff --git a/services/promotion-service-net/src/PromotionService.API/Program.cs b/services/promotion-service-net/src/PromotionService.API/Program.cs index 135e36f7..12ae5599 100644 --- a/services/promotion-service-net/src/PromotionService.API/Program.cs +++ b/services/promotion-service-net/src/PromotionService.API/Program.cs @@ -136,7 +136,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/social-service-net/src/SocialService.API/Program.cs b/services/social-service-net/src/SocialService.API/Program.cs index 835fe5b5..76ee9d6d 100644 --- a/services/social-service-net/src/SocialService.API/Program.cs +++ b/services/social-service-net/src/SocialService.API/Program.cs @@ -135,7 +135,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/storage-service-net/src/StorageService.API/Program.cs b/services/storage-service-net/src/StorageService.API/Program.cs index b7bcba66..babca261 100644 --- a/services/storage-service-net/src/StorageService.API/Program.cs +++ b/services/storage-service-net/src/StorageService.API/Program.cs @@ -142,7 +142,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); }); diff --git a/services/wallet-service-net/src/WalletService.API/Middleware/TenantMiddleware.cs b/services/wallet-service-net/src/WalletService.API/Middleware/TenantMiddleware.cs index 3e9c88d1..e7b0a6dd 100644 --- a/services/wallet-service-net/src/WalletService.API/Middleware/TenantMiddleware.cs +++ b/services/wallet-service-net/src/WalletService.API/Middleware/TenantMiddleware.cs @@ -64,15 +64,22 @@ public class TenantMiddleware await npgsqlConnection.OpenAsync(); } - // EN: Set shop_id as the primary tenant identifier - // VI: Đặt shop_id làm tenant identifier chính + // EN: Set shop_id as the primary tenant identifier using parameterized set_config() + // to prevent SQL injection. set_config(key, value, is_local) is equivalent + // to SET LOCAL but supports proper parameter binding. + // VI: Đặt shop_id làm tenant identifier chính dùng set_config() có tham số + // để ngăn SQL injection. set_config(key, value, is_local) tương đương + // SET LOCAL nhưng hỗ trợ parameter binding đúng cách. await using var cmd = npgsqlConnection.CreateCommand(); - cmd.CommandText = $"SET LOCAL app.current_shop_id = '{shopId.ToString("D")}'"; + cmd.CommandText = "SELECT set_config('app.current_shop_id', $1, true)"; + cmd.Parameters.AddWithValue(shopId.ToString("D")); await cmd.ExecuteNonQueryAsync(); if (merchantId.HasValue) { - cmd.CommandText = $"SET LOCAL app.current_merchant_id = '{merchantId.Value.ToString("D")}'"; + cmd.Parameters.Clear(); + cmd.CommandText = "SELECT set_config('app.current_merchant_id', $1, true)"; + cmd.Parameters.AddWithValue(merchantId.Value.ToString("D")); await cmd.ExecuteNonQueryAsync(); } diff --git a/services/wallet-service-net/src/WalletService.API/Program.cs b/services/wallet-service-net/src/WalletService.API/Program.cs index 888be071..3163f1de 100644 --- a/services/wallet-service-net/src/WalletService.API/Program.cs +++ b/services/wallet-service-net/src/WalletService.API/Program.cs @@ -155,7 +155,9 @@ try { options.AddDefaultPolicy(policy => { - policy.AllowAnyOrigin() + policy.WithOrigins( + builder.Configuration.GetSection("AllowedOrigins").Get() + ?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"]) .AllowAnyMethod() .AllowAnyHeader(); });