diff --git a/src/main/java/org/juv25d/filter/CorsFilter.java b/src/main/java/org/juv25d/filter/CorsFilter.java index 141251b1..ccd8c144 100644 --- a/src/main/java/org/juv25d/filter/CorsFilter.java +++ b/src/main/java/org/juv25d/filter/CorsFilter.java @@ -2,20 +2,22 @@ import org.juv25d.http.HttpRequest; import org.juv25d.http.HttpResponse; - +import org.juv25d.util.ConfigLoader; import java.io.IOException; +import java.util.HashSet; import java.util.Map; import java.util.Set; public class CorsFilter implements Filter { - // Whitelist, allow known origins: - private static final Set ALLOWED_ORIGINS = Set.of( - "http://localhost:3000" - ); + private final Set allowedOrigins; + private final String allowedMethods; - // Supported methods - private static final String ALLOWED_METHODS = "GET, POST, PUT, PATCH, DELETE, OPTIONS"; + public CorsFilter() { + ConfigLoader config = ConfigLoader.getInstance(); + this.allowedOrigins = new HashSet<>(config.getAllowedOrigins()); + this.allowedMethods = String.join(",", config.getAllowedMethods()); + } @Override public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throws IOException { @@ -28,7 +30,7 @@ public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throw } // Origin exists but are not allowed, return no CORS headers - if (!ALLOWED_ORIGINS.contains(origin)) { + if (!allowedOrigins.contains(origin)) { chain.doFilter(req, res); return; } @@ -44,7 +46,7 @@ public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throw // Preflight, OPTIONS if ("OPTIONS".equalsIgnoreCase(req.method())) { - res.setHeader("Access-Control-Allow-Methods", ALLOWED_METHODS); + res.setHeader("Access-Control-Allow-Methods", allowedMethods); // If browser requests specific headers, mirror String requestedHeaders = header(req.headers(), "Access-Control-Request-Headers"); diff --git a/src/main/java/org/juv25d/util/ConfigLoader.java b/src/main/java/org/juv25d/util/ConfigLoader.java index a3e87c52..6ca73026 100644 --- a/src/main/java/org/juv25d/util/ConfigLoader.java +++ b/src/main/java/org/juv25d/util/ConfigLoader.java @@ -3,10 +3,10 @@ import org.jspecify.annotations.Nullable; import org.juv25d.proxy.ProxyRoute; import org.yaml.snakeyaml.Yaml; - import java.io.InputStream; import java.util.*; + public class ConfigLoader { @Nullable private static ConfigLoader instance; private int port; @@ -19,6 +19,8 @@ public class ConfigLoader { private boolean requestBodySizeEnabled; private List trustedProxies; private List proxyRoutes = new ArrayList<>(); + private List allowedOrigins = List.of(); + private List allowedMethods = List.of(); private ConfigLoader() { loadConfiguration(getClass().getClassLoader() @@ -54,11 +56,14 @@ private void loadConfiguration(InputStream input) { this.rootDirectory = "static"; this.logLevel = "INFO"; this.trustedProxies = List.of(); + this.allowedOrigins = List.of(); + this.allowedMethods = List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"); // server Object serverObj = config.get("server"); if (serverObj != null) { Map serverConfig = asStringObjectMap(serverObj); + Object portValue = serverConfig.get("port"); if (portValue instanceof Number n) this.port = n.intValue(); @@ -115,6 +120,38 @@ private void loadConfiguration(InputStream input) { Long.parseLong(String.valueOf(rateLimitingConfig.getOrDefault("burst-capacity", 100L))); } + //Cors + Object corsObj = config.get("cors"); + if (corsObj != null) { + Map corsConfig = asStringObjectMap(corsObj); + + //Allowed-origins + Object origins = corsConfig.get("allowed-origins"); + if (origins instanceof List list) { + this.allowedOrigins = list.stream() + .filter(Objects::nonNull) + .map(String::valueOf) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList(); + } + + //Allowed-methods + Object methods = corsConfig.get("allowed-methods"); + if (methods instanceof List methodList) { + List parsedMethods = methodList.stream() + .filter(Objects::nonNull) + .map(String::valueOf) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(s -> s.toUpperCase(Locale.ROOT)) + .toList(); + + this.allowedMethods = parsedMethods.isEmpty() + ? List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") + : parsedMethods; + } + } // request body size this.requestBodySizeEnabled = false; this.maxBodySizeMb = 10L; @@ -185,4 +222,12 @@ public long getMaxBodySizeMb() { public List getProxyRoutes() { return Collections.unmodifiableList(proxyRoutes); } + + public List getAllowedOrigins() { + return allowedOrigins; + } + public List getAllowedMethods() { + return allowedMethods; + } + } diff --git a/src/main/resources/application-properties.yml b/src/main/resources/application-properties.yml index 59048d84..0a80f49d 100644 --- a/src/main/resources/application-properties.yml +++ b/src/main/resources/application-properties.yml @@ -16,6 +16,16 @@ rate-limiting: requests-per-minute: 60 burst-capacity: 100 +cors: + allowed-origins: + - http://localhost:3000 + allowed-methods: + - GET + - POST + - PUT + - PATCH + - DELETE + - OPTIONS request-body-size: enabled: true max-size-mb: 10