Compare commits

...

6 Commits

Author SHA1 Message Date
khwezi 24ba609e0c Merge pull request 'Excluded http environment from checkin' (#64) from payments into master
Reviewed-on: #64
2026-06-03 00:51:30 +02:00
Khwezi Mngoma 4bac14881d Excluded http environment from checkin
continuous-integration/drone/pr Build is passing
2026-06-03 00:50:20 +02:00
khwezi 29f6d66c44 Merge pull request 'Fixed tests' (#63) from payments into master
Reviewed-on: #63
2026-06-03 00:41:26 +02:00
Khwezi Mngoma fd6057d691 Fixed tests
continuous-integration/drone/pr Build is passing
2026-06-03 00:41:02 +02:00
khwezi bcfc9ef962 Merge pull request 'Added loopback address whitelisting override' (#62) from payments into master
Reviewed-on: #62
2026-06-03 00:38:29 +02:00
Khwezi Mngoma 7961d934ba Added loopback address whitelisting override
continuous-integration/drone/pr Build is failing
2026-06-03 00:37:59 +02:00
6 changed files with 18 additions and 24 deletions
+1
View File
@@ -362,3 +362,4 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/LiteCharms.Features.Tests/http/http-client.env.json
/LiteCharms.Features.Tests/http/midrandshop-api/http-client.env.json
@@ -37,7 +37,7 @@ public sealed class PayfastServiceFeatureTests(Fixture fixture) : IClassFixture<
string liveTargetIp = addresses.First().ToString();
var result = await payfastService.ValidateReferrerIpAsync(liveTargetIp, fixture.CancellationToken);
var result = await payfastService.ValidateReferrerIpAsync(liveTargetIp, true, fixture.CancellationToken);
Assert.True(result.IsSuccess);
Assert.True(result.Value);
@@ -48,7 +48,7 @@ public sealed class PayfastServiceFeatureTests(Fixture fixture) : IClassFixture<
{
string rogueIp = "8.8.8.8";
var result = await payfastService.ValidateReferrerIpAsync(rogueIp, fixture.CancellationToken);
var result = await payfastService.ValidateReferrerIpAsync(rogueIp, true, fixture.CancellationToken);
Assert.True(result.IsSuccess);
Assert.False(result.Value);
@@ -87,7 +87,7 @@ public sealed class PayfastServiceFeatureTests(Fixture fixture) : IClassFixture<
var result = await payfastService.ValidateServerConfirmationAsync(arbitraryParameters, isSandbox: true, fixture.CancellationToken);
Assert.True(result.IsSuccess);
Assert.True(result.IsSuccess);
Assert.False(result.Value); // Handshake data rejected as fraudulent/unrecognized
}
@@ -61,7 +61,7 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi
if (notification.PerformBackgroundChecks)
{
var isHostValid = await payfastService.ValidateReferrerIpAsync(notification.RemoteIpAddress!, cancellationToken);
var isHostValid = await payfastService.ValidateReferrerIpAsync(notification.RemoteIpAddress!, notification.AllowLoopback, cancellationToken);
if (isHostValid.IsFailed)
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 AllowLoopback { get; set; }
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;
CorrelationId = paymentId;
PerformBackgroundChecks = performBackgroundChecks;
AllowLoopback = allowLoopback;
}
public static PayfastPaymentConfirmationReceivedEvent Create(PayfastWebhookPayload? payload, string paymentId, bool performBackgroundChecks = true) =>
new(payload, paymentId, performBackgroundChecks);
public static PayfastPaymentConfirmationReceivedEvent Create(PayfastWebhookPayload? payload, string paymentId, bool performBackgroundChecks = true, bool allowLoopback = false) =>
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))
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 (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);
if (!isValid)
@@ -1,16 +0,0 @@
{
"local": {
"baseUrl": "https://localhost:7196",
"paymentId": "jdPB2zaKM3Z",
"signature": "6aeff59bb74f2448ff2c3d81b2ec95de",
"item_name": "System Architecture Book",
"amount": "350.00"
},
"uat": {
"baseUrl": "https://api.uat.midrandbooks.co.za",
"paymentId": "jdPB2zaKM3Z",
"signature": "6aeff59bb74f2448ff2c3d81b2ec95de",
"item_name": "System Architecture Book",
"amount": "350.00"
}
}