diff --git a/CHANGELOG.md b/CHANGELOG.md index 1caa72922..115819b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +## [9.1.2] -2024-07-24 + +- Fixes path routing which rejected tenantId stop words even if it was not an exact stop word match. For example, `/hellotenant` is a valid tenantId prefix, however, it was being rejected for the stop word `hello`. + ## [9.1.1] -2024-07-24 ### Fixes diff --git a/build.gradle b/build.gradle index c1aa06140..9902e4fb2 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "9.1.1" +version = "9.1.2" repositories { diff --git a/src/main/java/io/supertokens/webserver/PathRouter.java b/src/main/java/io/supertokens/webserver/PathRouter.java index 5e01d3d52..7bbc34099 100644 --- a/src/main/java/io/supertokens/webserver/PathRouter.java +++ b/src/main/java/io/supertokens/webserver/PathRouter.java @@ -35,7 +35,7 @@ public PathRouter(Main main) { } public void addAPI(WebserverAPI newApi) { - this.apis.add(newApi); + this.apis.add(0, newApi); // add to the front so that the most recent API is checked first for (WebserverAPI api : this.apis) { for (WebserverAPI api2 : this.apis) { if (api != api2 && api.getPath().equals(api2.getPath())) { @@ -82,10 +82,23 @@ private WebserverAPI getAPIThatMatchesPath(HttpServletRequest req) { apiPath = "/" + apiPath; } - String tenantIdStopWords = String.join("|", Utils.INVALID_WORDS_FOR_TENANTID); - if (requestPath.matches( - "^(/appid-[a-z0-9-]*)?(/(?!" + tenantIdStopWords + ")[a-z0-9-]+)?" + apiPath + "/?$")) { - return api; + if (apiPath.endsWith("/")) { + apiPath = apiPath.substring(0, apiPath.length() - 1); + } + + if (apiPath.isBlank()) { + String tenantIdStopWords = String.join("$|", Utils.INVALID_WORDS_FOR_TENANTID) + "$"; // Adds an end of string for each entry + tenantIdStopWords += "|" + String.join("/|", Utils.INVALID_WORDS_FOR_TENANTID) + "/"; // Adds a trailing slash for each entry + if (requestPath.matches( + "^(/appid-[a-z0-9-]*)?(/(?!" + tenantIdStopWords + ")[a-z0-9-]+)?" + "/?$")) { + return api; + } + } else { + String tenantIdStopWords = String.join("/|", Utils.INVALID_WORDS_FOR_TENANTID) + "/"; // Adds a trailing slash for each entry + if (requestPath.matches( + "^(/appid-[a-z0-9-]*)?(/(?!" + tenantIdStopWords + ")[a-z0-9-]+)?" + apiPath + "/?$")) { + return api; + } } } for (WebserverAPI api : this.apis) { diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index 1ba34f8a5..658f7fe2c 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -259,16 +259,23 @@ private String getTenantId(HttpServletRequest req) { if (!apiPath.startsWith("/")) { apiPath = "/" + apiPath; } - if (apiPath.equals("/")) { - if (path.equals("") || path.equals("/")) { - return null; - } + if (apiPath.endsWith("/")) { + apiPath = apiPath.substring(0, apiPath.length() - 1); + } + + if (apiPath.isBlank() && (path.equals("") || path.equals("/"))) { + return null; } else { if (path.matches("^/appid-[a-z0-9-]*/[a-z0-9-]+" + apiPath + "/?$")) { String tenantId = path.split("/")[2].toLowerCase(); if (tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { return null; } + + if (Utils.INVALID_WORDS_FOR_TENANTID.contains(tenantId)) { + return null; + } + return tenantId; } else if (path.matches("^/appid-[a-z0-9-]*" + apiPath + "/?$")) { return null; @@ -277,12 +284,16 @@ private String getTenantId(HttpServletRequest req) { if (tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { return null; } + + if (Utils.INVALID_WORDS_FOR_TENANTID.contains(tenantId)) { + return null; + } + return tenantId; } else { return null; } } - return null; } private String getAppId(HttpServletRequest req) { @@ -291,10 +302,12 @@ private String getAppId(HttpServletRequest req) { if (!apiPath.startsWith("/")) { apiPath = "/" + apiPath; } - if (apiPath.equals("/")) { - if (path.equals("") || path.equals("/")) { - return null; - } + if (apiPath.endsWith("/")) { + apiPath = apiPath.substring(0, apiPath.length() - 1); + } + + if (apiPath.isBlank() && (path.equals("") || path.equals("/"))) { + return null; } else { if (path.matches("^/appid-[a-z0-9-]*(/[a-z0-9-]+)?" + apiPath + "/?$")) { String appId = path.split("/")[1].toLowerCase(); @@ -306,7 +319,6 @@ private String getAppId(HttpServletRequest req) { return null; } } - return null; } private String getConnectionUriDomain(HttpServletRequest req) throws ServletException { diff --git a/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java b/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java index ce9acc90b..a99ba88dc 100644 --- a/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java @@ -21,6 +21,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.RateLimiter; @@ -79,11 +80,27 @@ protected void handleRequest(HttpServletRequest req, HttpServletResponse resp) t appIdentifier = getAppIdentifier(req); storages = StorageLayer.getStoragesForApp(main, appIdentifier); } catch (TenantOrAppNotFoundException e) { - // we send 500 status code throw new ServletException(e); } - if (req.getServletPath().equals("/")) { + String path = req.getServletPath(); + TenantIdentifier tenantIdentifier = null; + try { + tenantIdentifier = getTenantIdentifier(req); + } catch (TenantOrAppNotFoundException e) { + super.sendTextResponse(404, "Not found", resp); + return; + } + + if (path.startsWith("/appid-")) { + path = path.replace("/appid-"+tenantIdentifier.getAppId(), ""); + } + + if (!tenantIdentifier.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID)) { + path = path.replace("/" + tenantIdentifier.getTenantId(), ""); + } + + if (path.equals("/") || path.isBlank()) { // API is app specific try { RateLimiter rateLimiter = RateLimiter.getInstance(appIdentifier, super.main, 200); diff --git a/src/test/java/io/supertokens/test/HelloAPITest.java b/src/test/java/io/supertokens/test/HelloAPITest.java index 0944b4d3a..59aa52313 100644 --- a/src/test/java/io/supertokens/test/HelloAPITest.java +++ b/src/test/java/io/supertokens/test/HelloAPITest.java @@ -315,6 +315,23 @@ public void testThatHelloAPIDoesNotRequireAPIKeys() throws Exception { new JsonObject() ), false); + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + new TenantIdentifier(null, null, "hellotenant"), + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, + new JsonObject() + ), false); + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + new TenantIdentifier(null, "hello", "hellotenant"), + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, + new JsonObject() + ), false); + String[] HELLO_ROUTES = new String[]{ "http://localhost:3567", // / "http://localhost:3567/", // / @@ -324,6 +341,18 @@ public void testThatHelloAPIDoesNotRequireAPIKeys() throws Exception { "http://localhost:3567/appid-hello/hello", // app + /hello "http://localhost:3567/appid-hello/hello/", // app + /hello "http://localhost:3567/appid-hello/test/hello", // app + tenant + /hello + + "http://localhost:3567/hellotenant", + "http://localhost:3567/hellotenant/", + "http://localhost:3567/hellotenant/hello", + + "http://localhost:3567/appid-hello", // app + / + "http://localhost:3567/appid-hello/", // app + / + "http://localhost:3567/appid-hello/test", // app + tenant + / + "http://localhost:3567/appid-hello/test/", // app + tenant + / + "http://localhost:3567/appid-hello/hellotenant", + "http://localhost:3567/appid-hello/hellotenant/", + "http://localhost:3567/appid-hello/hellotenant/hello", }; for (String helloUrl : HELLO_ROUTES) { @@ -337,15 +366,12 @@ public void testThatHelloAPIDoesNotRequireAPIKeys() throws Exception { String[] NOT_FOUND_ROUTES = new String[]{ "http://localhost:3567/abcd", - "http://localhost:3567/appid-hello", // app + / - "http://localhost:3567/appid-hello/", // app + / - "http://localhost:3567/appid-hello/test", // app + tenant + / - "http://localhost:3567/appid-hello/test/", // app + tenant + / }; // Not found for (String notFoundUrl : NOT_FOUND_ROUTES) { try { + System.out.println(notFoundUrl); String res = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", notFoundUrl, null, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), "");