From 3858fa25bf98f18036406a74190ede313bbab136 Mon Sep 17 00:00:00 2001
From: jano3 <jano@bob.co.za>
Date: Wed, 29 Jan 2025 10:42:57 +0200
Subject: [PATCH] Add IP utils

---
 ip_utils/ip_utils.go | 166 +++++++++++++++++++++++++++++++++++++++++++
 utils/utils.go       |   1 +
 2 files changed, 167 insertions(+)
 create mode 100644 ip_utils/ip_utils.go

diff --git a/ip_utils/ip_utils.go b/ip_utils/ip_utils.go
new file mode 100644
index 0000000..a72f734
--- /dev/null
+++ b/ip_utils/ip_utils.go
@@ -0,0 +1,166 @@
+package ip_utils
+
+import (
+	"fmt"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
+	"net"
+	"os"
+	"strings"
+)
+
+// ====================================================================================================
+// region Payfast
+// ====================================================================================================
+
+// GetPayFastIPs returns a slice of string containing the current IP addresses
+// associated with host names published by PayFast.
+func getPayFastIPs() []string {
+	hostnames := []string{
+		"www.payfast.co.za",
+		"w1w.payfast.co.za",
+		"w2w.payfast.co.za",
+		"sandbox.payfast.co.za",
+		"api.payfast.co.za",
+		"ips.payfast.co.za",
+	}
+
+	ips := []string{}
+
+	for _, v := range hostnames {
+		addresses, err := net.LookupIP(v)
+		if err != nil {
+			// Swallow and continue.
+			continue
+		}
+
+		for _, a := range addresses {
+			ips = append(ips, a.String())
+		}
+	}
+
+	return ips
+}
+
+func VerifyPayfastSourceIP(sourceIP string) bool {
+	ips := getPayFastIPs()
+	for _, v := range ips {
+		if v == sourceIP {
+			return true
+		}
+	}
+
+	// If we got here, it means the lookup failed, so let's check the IP ranges as defined by Payfast
+	// Define the list of IP ranges as CIDR blocks - this is obtained from Payfast's documentation
+	ipRanges := []string{
+		"197.97.145.144/28",
+		"41.74.179.192/27",
+		"102.216.36.0/28",
+		"102.216.36.128/28",
+		"144.126.193.139/32",
+	}
+
+	// Now verify
+	if isValidIP(sourceIP, ipRanges) {
+		return true
+	}
+
+	return false
+}
+
+// endregion Payfast
+
+// ====================================================================================================
+// region Bob Pay
+// ====================================================================================================
+
+const (
+	bobPayDevIP     = "13.245.84.126"
+	bobPaySandboxIP = "13.246.115.225"
+	bobPayProdIP    = "13.246.100.25"
+)
+
+func VerifyBobPaySourceIP(sourceIP string) bool {
+	env := os.Getenv("ENVIRONMENT")
+
+	switch env {
+	case "dev":
+		return sourceIP == bobPayDevIP
+	case "stage":
+		return sourceIP == bobPaySandboxIP
+	case "prod":
+		return sourceIP == bobPayProdIP
+	}
+
+	return false
+}
+
+// endregion Bob Pay
+
+// ====================================================================================================
+// region Cloudflare
+// ====================================================================================================
+
+func VerifyCloudflareSourceIP(sourceIP string) bool {
+	ipRanges := []string{
+		"173.245.48.0/20",
+		"103.21.244.0/22",
+		"103.22.200.0/22",
+		"103.31.4.0/22",
+		"141.101.64.0/18",
+		"108.162.192.0/18",
+		"190.93.240.0/20",
+		"188.114.96.0/20",
+		"197.234.240.0/22",
+		"198.41.128.0/17",
+		"162.158.0.0/15",
+		"104.16.0.0/13",
+		"104.24.0.0/14",
+		"172.64.0.0/13",
+		"131.0.72.0/22",
+	}
+
+	// Now verify
+	if isValidIP(sourceIP, ipRanges) {
+		return true
+	}
+
+	return false
+}
+
+// endregion Cloudflare
+
+// ====================================================================================================
+// region Helpers
+// ====================================================================================================
+
+func isValidIP(ipStr string, ipRanges []string) bool {
+	ip := net.ParseIP(ipStr)
+	if ip == nil {
+		return false
+	}
+
+	for _, cidr := range ipRanges {
+		_, subnet, err := net.ParseCIDR(cidr)
+		if err != nil {
+			fmt.Println("Error parsing CIDR:", err)
+			continue
+		}
+
+		if subnet.Contains(ip) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func ValidateIPAddress(ipAddress string) (cleanedIPAddress string, err error) {
+	ipAddress = strings.ToLower(strings.TrimSpace(ipAddress))
+	ip := net.ParseIP(ipAddress)
+	if ip == nil {
+		return "", errors.Error("invalid IP address")
+	}
+	return ipAddress, nil
+}
+
+// endregion Helpers
diff --git a/utils/utils.go b/utils/utils.go
index 40bf5be..2c8d90b 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -308,6 +308,7 @@ func DetermineDaysLeft(fromDateLocal time.Time, toDateLocal time.Time) int {
 	return int(math.Floor(toDate.Sub(fromDate).Hours() / 24))
 }
 
+// ValidateIPAddress in this package is deprecated. Use ip_utils.ValidateIPAddress instead.
 func ValidateIPAddress(ipAddress string) (cleanedIPAddress string, err error) {
 	ipAddress = strings.ToLower(strings.TrimSpace(ipAddress))
 	ip := net.ParseIP(ipAddress)
-- 
GitLab