fix(security): fix 5 P0 security blockers — SEC-C-01 through SEC-C-05
SEC-C-01: Replace Neon PostgreSQL credentials (npg_Ssfy6HKO0cXI) with local dev connection strings in all 19 appsettings.json files. Production credentials must be injected via ConnectionStrings__DefaultConnection env var. Add appsettings.Production.json and appsettings.Staging.json to .gitignore. SEC-C-02: Add services/goodgo-mcp-server/.env to root .gitignore. Create .env.example with safe placeholder values documenting required variables. SEC-C-03: Wrap AddDeveloperSigningCredential() in env check — development only. Non-development environments must provide X.509 certificate via IdentityServer:SigningCertificatePath and IdentityServer:SigningCertificatePassword. SEC-C-04: Remove 4 unauthenticated debug endpoints from StaffController: GET debug/all, POST debug/seed, POST debug/update-userid, POST debug/update-merchant. These endpoints allowed privilege escalation and data exfiltration without auth. SEC-C-05: Removed endpoints containing SQL injection via string interpolation (lines 307, 367 in StaffController). Also removed [AllowAnonymous] from GET lookup endpoint — inherits class-level [Authorize]. BREAKING: debug/* endpoints are permanently removed. BFF lookup endpoint now requires authentication. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -61,6 +61,15 @@ infra/secrets/**/*
|
||||
!infra/secrets/**/.env.example
|
||||
!infra/secrets/**/.gitignore
|
||||
|
||||
# .NET appsettings with environment-specific secrets (use env vars instead)
|
||||
appsettings.Production.json
|
||||
appsettings.Staging.json
|
||||
appsettings.production.json
|
||||
appsettings.staging.json
|
||||
|
||||
# MCP server secrets
|
||||
services/goodgo-mcp-server/.env
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "postgresql://neondb_owner:npg_Ssfy6HKO0cXI@ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech/ads_analytics_service?sslmode=require&channel_binding=require"
|
||||
"DefaultConnection": "Host=localhost;Database=ads_analytics_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "postgresql://neondb_owner:npg_Ssfy6HKO0cXI@ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech/ads_billing_service?sslmode=require&channel_binding=require"
|
||||
"DefaultConnection": "Host=localhost;Database=ads_billing_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "postgresql://neondb_owner:npg_Ssfy6HKO0cXI@ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech/ads_manager_service?sslmode=require&channel_binding=require"
|
||||
"DefaultConnection": "Host=localhost;Database=ads_manager_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "postgresql://neondb_owner:npg_Ssfy6HKO0cXI@ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech/ads_serving_service?sslmode=require&channel_binding=require"
|
||||
"DefaultConnection": "Host=localhost;Database=ads_serving_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "postgresql://neondb_owner:npg_Ssfy6HKO0cXI@ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech/ads_tracking_service?sslmode=require&channel_binding=require"
|
||||
"DefaultConnection": "Host=localhost;Database=ads_tracking_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=booking_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=booking_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=catalog_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=catalog_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=chat_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require",
|
||||
"DefaultConnection": "Host=localhost;Database=chat_service;Username=goodgo;Password=goodgo-local-2024",
|
||||
"Redis": "localhost:6379"
|
||||
},
|
||||
"SignalR": {
|
||||
@@ -55,4 +55,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=fnb_engine;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=fnb_engine;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# GoodGo MCP Server Configuration
|
||||
# GoodGo MCP Server — Environment Variables
|
||||
# Copy this file to .env and fill in actual values.
|
||||
# NEVER commit .env to git.
|
||||
|
||||
# API Gateway URL (Traefik routes all services by path prefix)
|
||||
# Docker local: http://localhost/api/v1 (port 80 via Traefik)
|
||||
# Staging: https://api.staging.goodgo.vn/api/v1
|
||||
# API Gateway
|
||||
API_GATEWAY_URL=http://localhost/api/v1
|
||||
|
||||
# Default shop for convenience (Cobic Coffee)
|
||||
DEFAULT_SHOP_ID=e1f392af-fe95-4c7f-8656-5b74ad5fd0a9
|
||||
# Default shop for testing (replace with your merchant shop ID)
|
||||
DEFAULT_SHOP_ID=
|
||||
|
||||
# JWT token (get from IAM login)
|
||||
# JWT Bearer token for API authentication
|
||||
# Obtain from IAM service: POST /api/v1/auth/token
|
||||
API_TOKEN=
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=iam_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=iam_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"Host": "167.114.174.113",
|
||||
@@ -77,4 +77,4 @@
|
||||
"IssuerUri": "http://iam-service"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public static class DependencyInjection
|
||||
// VI: Cấu hình Duende IdentityServer
|
||||
var issuerUri = configuration["IdentityServer:IssuerUri"];
|
||||
|
||||
services.AddIdentityServer(options =>
|
||||
var identityBuilder = services.AddIdentityServer(options =>
|
||||
{
|
||||
// EN: Set fixed issuer URI to ensure consistency across hosts and containers
|
||||
// VI: Đặt issuer URI cố định để đảm bảo nhất quán giữa host và container
|
||||
@@ -138,8 +138,28 @@ public static class DependencyInjection
|
||||
.AddInMemoryApiResources(Config.ApiResources)
|
||||
.AddInMemoryClients(Config.Clients)
|
||||
.AddAspNetIdentity<ApplicationUser>()
|
||||
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
|
||||
.AddDeveloperSigningCredential(); // EN: Use certificate in production / VI: Dùng certificate trong production
|
||||
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();
|
||||
|
||||
// EN: Signing credential — dev uses in-memory key, production MUST use a real X.509 certificate.
|
||||
// VI: Signing credential — dev dùng key in-memory, production PHẢI dùng X.509 certificate thật.
|
||||
if (environmentName == "Development" || string.IsNullOrEmpty(environmentName))
|
||||
{
|
||||
identityBuilder.AddDeveloperSigningCredential();
|
||||
}
|
||||
else
|
||||
{
|
||||
// EN: Read signing certificate path and password from environment variables.
|
||||
// VI: Đọc đường dẫn và mật khẩu certificate từ biến môi trường.
|
||||
var certPath = configuration["IdentityServer:SigningCertificatePath"]
|
||||
?? throw new InvalidOperationException(
|
||||
"IdentityServer:SigningCertificatePath must be set in non-Development environments. " +
|
||||
"Provide a valid X.509 certificate via environment variable.");
|
||||
var certPassword = configuration["IdentityServer:SigningCertificatePassword"]
|
||||
?? throw new InvalidOperationException(
|
||||
"IdentityServer:SigningCertificatePassword must be set in non-Development environments.");
|
||||
var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath, certPassword);
|
||||
identityBuilder.AddSigningCredential(cert);
|
||||
}
|
||||
|
||||
// EN: Add JWT Bearer authentication for API endpoints using local IdentityServer
|
||||
// VI: Thêm JWT Bearer authentication cho API endpoints sử dụng IdentityServer cục bộ
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=inventory_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=inventory_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=membership_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=membership_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"Host": "167.114.174.113",
|
||||
@@ -48,4 +48,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,162 +219,11 @@ public class StaffPublicController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Debug endpoint to list all staff (dev only).
|
||||
/// VI: Endpoint debug để list tất cả staff (dev only).
|
||||
/// </summary>
|
||||
[HttpGet("debug/all")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> DebugAllStaff()
|
||||
{
|
||||
var ctx = HttpContext.RequestServices.GetRequiredService<MerchantService.Infrastructure.MerchantServiceContext>();
|
||||
var staffCount = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync(ctx.MerchantStaff);
|
||||
var merchantCount = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync(ctx.Merchants);
|
||||
var merchants = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(
|
||||
System.Linq.Queryable.Take(ctx.Merchants, 5));
|
||||
var staffItems = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(
|
||||
System.Linq.Queryable.Take(ctx.MerchantStaff, 20));
|
||||
return Ok(new
|
||||
{
|
||||
staffCount, merchantCount,
|
||||
merchants = merchants.Select(m => new { m.Id, m.BusinessName, m.StatusId }),
|
||||
staff = staffItems.Select(s => new { s.Id, s.Email, s.FirstName, s.LastName, s.UserId, s.MerchantId, statusId = s.StatusId })
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Debug seed endpoint — seed test staff data (dev only).
|
||||
/// VI: Debug seed — tạo dữ liệu test staff (dev only).
|
||||
/// </summary>
|
||||
[HttpPost("debug/seed")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> DebugSeedStaff()
|
||||
{
|
||||
var staffRepo = HttpContext.RequestServices.GetRequiredService<MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.IMerchantStaffRepository>();
|
||||
var ctx = HttpContext.RequestServices.GetRequiredService<MerchantService.Infrastructure.MerchantServiceContext>();
|
||||
|
||||
// Get first merchant
|
||||
var merchant = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync(ctx.Merchants);
|
||||
if (merchant == null) return BadRequest(new { message = "No merchant found" });
|
||||
|
||||
var staffData = new[]
|
||||
{
|
||||
new { Email = "tranvanb@goodgo.vn", FirstName = "Bình", LastName = "Trần Văn", Role = "Cashier", UserId = Guid.Parse("9160b2f3-619b-48de-aade-8098cef2a426") },
|
||||
new { Email = "tranvanc@goodgo.vn", FirstName = "Cường", LastName = "Trần Văn", Role = "Waiter", UserId = Guid.NewGuid() },
|
||||
new { Email = "tranvand@goodgo.vn", FirstName = "Dũng", LastName = "Trần Văn", Role = "Kitchen", UserId = Guid.NewGuid() },
|
||||
new { Email = "tranvane@goodgo.vn", FirstName = "Huy", LastName = "Trần Văn", Role = "Manager", UserId = Guid.NewGuid() },
|
||||
};
|
||||
|
||||
var created = new List<object>();
|
||||
foreach (var sd in staffData)
|
||||
{
|
||||
var existing = await staffRepo.GetByEmailAsync(sd.Email);
|
||||
if (existing != null) { created.Add(new { sd.Email, status = "already_exists", existing.Id }); continue; }
|
||||
|
||||
var role = sd.Role switch
|
||||
{
|
||||
"Cashier" => MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.StaffRole.Cashier,
|
||||
"Waiter" => MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.StaffRole.Waiter,
|
||||
"Kitchen" => MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.StaffRole.Kitchen,
|
||||
"Manager" => MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.StaffRole.Manager,
|
||||
_ => MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.StaffRole.Cashier,
|
||||
};
|
||||
var staff = MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.MerchantStaff.CreateActive(
|
||||
merchant.Id, sd.UserId, sd.Email, role, firstName: sd.FirstName, lastName: sd.LastName);
|
||||
|
||||
staffRepo.Add(staff);
|
||||
created.Add(new { sd.Email, status = "created", staff.Id });
|
||||
}
|
||||
|
||||
await staffRepo.UnitOfWork.SaveEntitiesAsync();
|
||||
|
||||
// Also seed a shop if none exists and assign staff to it
|
||||
Guid? shopId = null;
|
||||
try
|
||||
{
|
||||
var shopIdResult = await ctx.Database.ExecuteSqlRawAsync(@"
|
||||
INSERT INTO shops (id, merchant_id, name, slug, type_id, category_id, status_id, created_at)
|
||||
SELECT @p0, @p1, 'Cobic Coffee', 'cobic-coffee', 1, 1, 2, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM shops WHERE merchant_id = @p1)
|
||||
", Guid.Parse("e1f392af-fe95-4c7f-8656-5b74ad5fd0a9"), merchant.Id);
|
||||
|
||||
// Find shop for this merchant via raw connection
|
||||
Guid existingShopId = Guid.Empty;
|
||||
var conn = ctx.Database.GetDbConnection();
|
||||
if (conn.State != System.Data.ConnectionState.Open) await conn.OpenAsync();
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = $"SELECT id FROM shops WHERE merchant_id = '{merchant.Id}' LIMIT 1";
|
||||
var result = await cmd.ExecuteScalarAsync();
|
||||
if (result != null) existingShopId = Guid.Parse(result.ToString()!);
|
||||
}
|
||||
if (existingShopId != Guid.Empty)
|
||||
{
|
||||
shopId = existingShopId;
|
||||
// Assign all created staff to this shop
|
||||
foreach (var sd in staffData)
|
||||
{
|
||||
var staffEntity = await staffRepo.GetByEmailAsync(sd.Email);
|
||||
if (staffEntity != null && !staffEntity.ShopAssignments.Any())
|
||||
{
|
||||
staffEntity.AssignToShop(existingShopId, MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.ShopRole.Cashier);
|
||||
staffRepo.Update(staffEntity);
|
||||
}
|
||||
}
|
||||
await staffRepo.UnitOfWork.SaveEntitiesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Shop creation may fail if columns don't exist — that's OK, leave request can still work
|
||||
_logger.LogWarning("Shop seed failed: {Error}", ex.Message);
|
||||
}
|
||||
|
||||
return Ok(new { merchantId = merchant.Id, shopId, created });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Debug update staff userId (dev only).
|
||||
/// </summary>
|
||||
[HttpPost("debug/update-userid")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> DebugUpdateStaffUserId([FromQuery] string email, [FromQuery] Guid userId)
|
||||
{
|
||||
var staffRepo = HttpContext.RequestServices.GetRequiredService<MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.IMerchantStaffRepository>();
|
||||
var staff = await staffRepo.GetByEmailAsync(email);
|
||||
if (staff == null) return NotFound(new { message = "Staff not found" });
|
||||
|
||||
// Use reflection to set private _userId field
|
||||
var field = typeof(MerchantService.Domain.AggregatesModel.MerchantStaffAggregate.MerchantStaff)
|
||||
.GetField("_userId", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
field?.SetValue(staff, userId);
|
||||
staffRepo.Update(staff);
|
||||
await staffRepo.UnitOfWork.SaveEntitiesAsync();
|
||||
return Ok(new { success = true, staffId = staff.Id, userId, email });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Debug update merchant userId (dev only).
|
||||
/// </summary>
|
||||
[HttpPost("debug/update-merchant")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> DebugUpdateMerchantUserId([FromQuery] Guid merchantId, [FromQuery] Guid userId)
|
||||
{
|
||||
var ctx = HttpContext.RequestServices.GetRequiredService<MerchantService.Infrastructure.MerchantServiceContext>();
|
||||
var conn = ctx.Database.GetDbConnection();
|
||||
if (conn.State != System.Data.ConnectionState.Open) await conn.OpenAsync();
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"UPDATE merchants SET user_id = '{userId}' WHERE id = '{merchantId}'";
|
||||
var rows = await cmd.ExecuteNonQueryAsync();
|
||||
return Ok(new { success = rows > 0, merchantId, userId, rowsAffected = rows });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Lookup staff profile by email — used by BFF to resolve staff identity.
|
||||
/// VI: Tìm staff profile theo email — BFF dùng để resolve staff identity.
|
||||
/// </summary>
|
||||
[HttpGet("lookup")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> LookupByEmail([FromQuery] string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email))
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=merchant_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=merchant_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -41,4 +41,4 @@
|
||||
"RequireHttpsMetadata": false
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=mining_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=mining_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=order_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=order_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -57,4 +57,4 @@
|
||||
"WalletService": "http://wallet-service-net:8080"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=promotion_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=promotion_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"WalletService": {
|
||||
"BaseUrl": "http://wallet-service-net:8080",
|
||||
@@ -60,4 +60,4 @@
|
||||
"ConnectionString": "redis:6379"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=social_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=social_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=storage_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=storage_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Storage": {
|
||||
"Provider": "minio",
|
||||
@@ -75,4 +75,4 @@
|
||||
"DefaultCacheTtlSeconds": 86400
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
]
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=wallet_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
|
||||
"DefaultConnection": "Host=localhost;Database=wallet_service;Username=goodgo;Password=goodgo-local-2024"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379"
|
||||
@@ -43,4 +43,4 @@
|
||||
"RefreshTokenExpiryDays": 7
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user