security: remove callback URL from user input — read from FrontOfficeBaseUrl config
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m28s

- PackageService: build callback URL from FrontOfficeBaseUrl config instead of request.CallbackUrl
- TransactionsService: same fix — callback URL from config
- Prevents potential open redirect vulnerability (user could send arbitrary URL)

All callback URLs now come from appsettings FrontOfficeBaseUrl or CmsBaseUrl.
This commit is contained in:
masoodafar-web
2026-02-27 22:20:04 +03:30
parent ed2b20a98e
commit 01073084ab
2 changed files with 11 additions and 7 deletions
@@ -182,9 +182,9 @@ public class PackageService : PackageContract.PackageContractBase
.Select(u => new { u.Mobile }) .Select(u => new { u.Mobile })
.FirstOrDefaultAsync(context.CancellationToken); .FirstOrDefaultAsync(context.CancellationToken);
// Embed orderId in callback URL so FrontOffice can pass it back for verification // Callback URL از config — نه از ورودی کاربر (امنیت)
var separator = request.CallbackUrl.Contains('?') ? "&" : "?"; var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
var callbackWithOrder = $"{request.CallbackUrl}{separator}orderId={purchase.Id}"; var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?orderId={purchase.Id}";
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
{ {
@@ -192,7 +192,7 @@ public class PackageService : PackageContract.PackageContractBase
UserId = userId, UserId = userId,
Mobile = user?.Mobile ?? string.Empty, Mobile = user?.Mobile ?? string.Empty,
Description = $"خرید پکیج {package.Title}", Description = $"خرید پکیج {package.Title}",
CallbackUrl = callbackWithOrder CallbackUrl = callbackUrl
}, context.CancellationToken); }, context.CancellationToken);
if (!paymentResult.IsSuccess) if (!paymentResult.IsSuccess)
@@ -216,7 +216,7 @@ public class PackageService : PackageContract.PackageContractBase
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
Amount = package.Price, Amount = package.Price,
CallbackUrl = request.CallbackUrl, CallbackUrl = callbackUrl,
Description = $"خرید پکیج {package.Title}", Description = $"خرید پکیج {package.Title}",
Mobile = user?.Mobile, Mobile = user?.Mobile,
UserId = userId, UserId = userId,
@@ -162,6 +162,10 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
_context.Transactions.Add(transaction); _context.Transactions.Add(transaction);
await _context.SaveChangesAsync(context.CancellationToken); await _context.SaveChangesAsync(context.CancellationToken);
// Callback URL از config — نه از ورودی کاربر (امنیت)
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback";
// Initiate payment with gateway // Initiate payment with gateway
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
{ {
@@ -169,7 +173,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
UserId = userId, UserId = userId,
Mobile = request.Mobile ?? user?.Mobile ?? string.Empty, Mobile = request.Mobile ?? user?.Mobile ?? string.Empty,
Description = request.Description ?? "پرداخت آنلاین", Description = request.Description ?? "پرداخت آنلاین",
CallbackUrl = request.CallbackUrl CallbackUrl = callbackUrl
}, context.CancellationToken); }, context.CancellationToken);
if (!paymentResult.IsSuccess) if (!paymentResult.IsSuccess)
@@ -191,7 +195,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
Amount = request.Amount, Amount = request.Amount,
CallbackUrl = request.CallbackUrl, CallbackUrl = callbackUrl,
Description = request.Description ?? "پرداخت آنلاین", Description = request.Description ?? "پرداخت آنلاین",
Mobile = request.Mobile ?? user?.Mobile, Mobile = request.Mobile ?? user?.Mobile,
UserId = userId, UserId = userId,