fix(security): fix 5 P1 backend issues — BACK-C-01/03/04, BACK-W-02
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 <noreply@paperclip.ing>
This commit is contained in:
@@ -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<FluentValidation.ValidationException>(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<MyService.Domain.Exceptions.DomainException>(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<MyService.Domain.Exceptions.EntityNotFoundException>(ex =>
|
||||
// new ProblemDetails { Title = "Not Found", Status = 404, Detail = ex.Message,
|
||||
// Type = "https://httpstatuses.io/404" });
|
||||
//
|
||||
// options.Map<MyService.Domain.Exceptions.DuplicateResourceException>(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<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -114,7 +114,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -114,7 +114,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -114,7 +114,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -119,7 +119,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -114,7 +114,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -91,7 +91,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -243,7 +243,9 @@ builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -162,7 +162,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -101,7 +101,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -91,7 +91,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -92,7 +92,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -92,7 +92,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -92,7 +92,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -96,7 +96,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -135,7 +135,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -142,7 +142,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,9 @@ try
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user