diff --git a/src/main/java/me/braydon/license/common/IPUtils.java b/src/main/java/me/braydon/license/common/IPUtils.java new file mode 100644 index 0000000..e8990b6 --- /dev/null +++ b/src/main/java/me/braydon/license/common/IPUtils.java @@ -0,0 +1,86 @@ +package me.braydon.license.common; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +/** + * @author Braydon + */ +@UtilityClass +public final class IPUtils { + /** + * The regex expression for validating IPv4 addresses. + */ + public static final String IPV4_REGEX = "^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"; + + /** + * The regex expression for validating IPv6 addresses. + */ + public static final String IPV6_REGEX = "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?$"; + + private static final String[] IP_HEADERS = new String[] { + "CF-Connecting-IP", + "X-Forwarded-For" + }; + + /** + * Get the real IP from the given request. + * + * @param request the request + * @return the real IP + */ + @NonNull + public static String getRealIp(@NonNull HttpServletRequest request) { + String ip = request.getRemoteAddr(); + for (String headerName : IP_HEADERS) { + String header = request.getHeader(headerName); + if (header == null) { + continue; + } + if (!header.contains(",")) { // Handle single IP + ip = header; + break; + } + // Handle multiple IPs + String[] ips = header.split(","); + for (String ipHeader : ips) { + ip = ipHeader; + break; + } + } + return ip; + } + + /** + * Get the IP type of the given input. + * + * @param input the input + * @return the IP type, -1 if invalid + */ + public static int getIpType(@NonNull String input) { + return isIpV4(input) ? 4 : isIpV6(input) ? 6 : -1; + } + + /** + * Check if the given input is + * a valid IPv4 address. + * + * @param input the input + * @return true if IPv4, otherwise false + */ + public static boolean isIpV4(@NonNull String input) { + return input.matches(IPV4_REGEX); + } + + /** + * Check if the given input is + * a valid IPv6 address. + * + * @param input the input + * @return true if IPv6, otherwise false + */ + public static boolean isIpV6(@NonNull String input) { + return input.matches(IPV6_REGEX); + } +} diff --git a/src/main/java/me/braydon/license/controller/LicenseController.java b/src/main/java/me/braydon/license/controller/LicenseController.java index 225e023..b684cb3 100644 --- a/src/main/java/me/braydon/license/controller/LicenseController.java +++ b/src/main/java/me/braydon/license/controller/LicenseController.java @@ -5,6 +5,7 @@ import com.google.gson.JsonObject; import jakarta.servlet.http.HttpServletRequest; import lombok.NonNull; import me.braydon.license.LicenseServer; +import me.braydon.license.common.IPUtils; import me.braydon.license.dto.LicenseDTO; import me.braydon.license.exception.APIException; import me.braydon.license.model.License; @@ -45,7 +46,7 @@ public final class LicenseController { @ResponseBody public ResponseEntity check(@NonNull HttpServletRequest request, @RequestBody @NonNull String body) { try { // Attempt to check the license - String ip = request.getRemoteAddr(); // The IP of the requester + String ip = IPUtils.getRealIp(request); // The IP of the requester JsonObject jsonObject = LicenseServer.GSON.fromJson(body, JsonObject.class); JsonElement key = jsonObject.get("key"); // Get the key @@ -56,6 +57,11 @@ public final class LicenseController { if (key.isJsonNull() || product.isJsonNull() || hwid.isJsonNull()) { throw new APIException(HttpStatus.BAD_REQUEST, "Invalid request body"); } + // Ensure the IP is valid + if (IPUtils.getIpType(ip) == -1) { + throw new APIException(HttpStatus.BAD_REQUEST, "Invalid IP address"); + } + // Check the license License license = service.check( key.getAsString(),