Added loopback address whitelisting override #62

Merged
khwezi merged 1 commits from payments into master 2026-06-03 00:38:29 +02:00
3 changed files with 14 additions and 5 deletions
@@ -61,7 +61,7 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi
if (notification.PerformBackgroundChecks) if (notification.PerformBackgroundChecks)
{ {
var isHostValid = await payfastService.ValidateReferrerIpAsync(notification.RemoteIpAddress!, cancellationToken); var isHostValid = await payfastService.ValidateReferrerIpAsync(notification.RemoteIpAddress!, notification.AllowLoopback, cancellationToken);
if (isHostValid.IsFailed) if (isHostValid.IsFailed)
throw new Exception("Security validation exception: Webhook packet source address failed cluster validation checks."); throw new Exception("Security validation exception: Webhook packet source address failed cluster validation checks.");
@@ -13,15 +13,18 @@ public sealed class PayfastPaymentConfirmationReceivedEvent : EventBase, IEvent
public bool PerformBackgroundChecks { get; set; } public bool PerformBackgroundChecks { get; set; }
public bool AllowLoopback { get; set; }
public PayfastPaymentConfirmationReceivedEvent() { } public PayfastPaymentConfirmationReceivedEvent() { }
private PayfastPaymentConfirmationReceivedEvent(PayfastWebhookPayload? payload, string paymentId, bool performBackgroundChecks = true) private PayfastPaymentConfirmationReceivedEvent(PayfastWebhookPayload? payload, string paymentId, bool performBackgroundChecks = true, bool allowLoopback = false)
{ {
Payload = payload; Payload = payload;
CorrelationId = paymentId; CorrelationId = paymentId;
PerformBackgroundChecks = performBackgroundChecks; PerformBackgroundChecks = performBackgroundChecks;
AllowLoopback = allowLoopback;
} }
public static PayfastPaymentConfirmationReceivedEvent Create(PayfastWebhookPayload? payload, string paymentId, bool performBackgroundChecks = true) => public static PayfastPaymentConfirmationReceivedEvent Create(PayfastWebhookPayload? payload, string paymentId, bool performBackgroundChecks = true, bool allowLoopback = false) =>
new(payload, paymentId, performBackgroundChecks); new(payload, paymentId, performBackgroundChecks, allowLoopback);
} }
@@ -49,7 +49,7 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
} }
} }
public async ValueTask<Result<bool>> ValidateReferrerIpAsync(string remoteIpAddress, CancellationToken cancellationToken = default) public async ValueTask<Result<bool>> ValidateReferrerIpAsync(string remoteIpAddress, bool allowLoopback = false, CancellationToken cancellationToken = default)
{ {
if (string.IsNullOrWhiteSpace(remoteIpAddress)) if (string.IsNullOrWhiteSpace(remoteIpAddress))
return Result.Fail<bool>("Remote IP address is null or whitespace."); return Result.Fail<bool>("Remote IP address is null or whitespace.");
@@ -74,6 +74,12 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
if (IPAddress.TryParse(remoteIpAddress, out var incomingIp)) if (IPAddress.TryParse(remoteIpAddress, out var incomingIp))
{ {
if (allowLoopback && IPAddress.IsLoopback(incomingIp))
{
logger.LogInformation("Local development loopback IP '{RemoteIp}' allowed bypassing DNS verification.", remoteIpAddress);
return Result.Ok(true);
}
bool isValid = validIps.Contains(incomingIp); bool isValid = validIps.Contains(incomingIp);
if (!isValid) if (!isValid)