feat: implement IP extraction and validation functions in Proxmox firewall view for enhanced IP handling and geolocation fetching

This commit is contained in:
2025-12-04 13:20:37 -05:00
parent 6b7a5dbdd7
commit 3b291a8f26

View File

@@ -2534,10 +2534,40 @@
@script
<script>
// Extract IP from string, handling ports for both IPv4 and IPv6
window.extractIP = function(ip) {
if (!ip || typeof ip !== 'string') return ip;
ip = ip.trim();
// Handle IPv4 with port (e.g., "192.168.1.1:8080")
const ipv4WithPort = /^(\d+\.\d+\.\d+\.\d+):(\d+)$/.exec(ip);
if (ipv4WithPort) {
return ipv4WithPort[1];
}
// Handle IPv6 with port in brackets (e.g., "[2001:db8::1]:8080")
const ipv6WithPort = /^\[([^\]]+)\]:(\d+)$/.exec(ip);
if (ipv6WithPort) {
return ipv6WithPort[1];
}
// Check if it's a valid IPv6 address (contains multiple colons)
if (ip.includes(':') && (ip.match(/:/g) || []).length >= 2) {
// Might be IPv6, return as-is
return ip;
}
return ip;
};
// Check if an IP address is public (not private/localhost)
window.isPublicIP = function(ip) {
if (!ip || ip === '—' || ip === '-' || typeof ip !== 'string') return false;
// Extract IP address (handles ports properly for both IPv4 and IPv6)
ip = window.extractIP(ip);
ip = ip.trim();
// Check for localhost
@@ -2561,34 +2591,61 @@
const ipv4MappedMatch = ip.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
if (ipv4MappedMatch) {
// Recursively check if the embedded IPv4 address is public
return isPublicIP(ipv4MappedMatch[1]);
}
// Check for IPv6 private ranges (very simplified)
const ipv6PrivatePatterns = [
/^fe80:/i, // Link-local
/^fc00:/i, // Unique local
/^fd00:/i, // Unique local
/^::1$/, // Loopback
/^::/ // Unspecified
];
for (const pattern of ipv6PrivatePatterns) {
if (pattern.test(ip)) return false;
return window.isPublicIP(ipv4MappedMatch[1]);
}
// Check if it's IPv4 (basic validation)
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(ip)) {
// Already checked private ranges above, so it's public
return true;
}
// Check if it's IPv6
if (ip.includes(':')) {
// Check for IPv6 private ranges
// Link-local (fe80::/10)
if (/^fe[89ab][0-9a-fA-F]:/i.test(ip)) {
return false;
}
// Unique Local Address - ULA (fc00::/7)
if (/^f[cd][0-9a-fA-F]/i.test(ip)) {
return false;
}
// IPv4-mapped IPv6 (::ffff:x.x.x.x)
if (ip.startsWith('::ffff:')) {
const ipv4Part = ip.substring(7);
return window.isPublicIP(ipv4Part);
}
// If it's a valid-looking IPv6 and not in private ranges, consider it public
// Basic IPv6 validation
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
return ipv6Regex.test(ip);
}
return false;
};
// Fetch IP location data
// Fetch IP geolocation data via server-side proxy (uses HTTP protocol server-side)
window.fetchIPLocation = async function(ip) {
if (!window.isPublicIP(ip)) {
return { status: 'fail', message: 'Not a public IP address' };
}
// Extract IP address (handles ports properly for both IPv4 and IPv6)
ip = window.extractIP(ip);
try {
const response = await fetch(`https://ipapi.co/${ip}/json/`);
const data = await response.json();
return data;
// Use Livewire to call server-side method which fetches from http://ip-api.com/json/{ip}
// This avoids mixed content issues when the site is served over HTTPS
const data = await @this.call('fetchIPLocation', ip);
return data || { status: 'fail', message: 'No data returned' };
} catch (error) {
console.error('Failed to fetch IP location:', error);
return null;
console.error('Error fetching IP location:', error);
return { status: 'fail', message: 'Failed to fetch location data: ' + (error.message || 'Unknown error') };
}
};