From 6b7e4435644c63ef9b0a94b942e97205cfe5e9ce Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 18 Mar 2024 17:37:51 +0530 Subject: [PATCH 1/5] fix: enforce public tenant in session APIs (#964) --- .../api/session/SessionRemoveAPI.java | 15 +-- .../webserver/api/session/SessionUserAPI.java | 14 +-- .../api/TestWithNonAuthRecipes.java | 93 +++++++++++++++++++ 3 files changed, 102 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java index 2686de03d..804800e2d 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java @@ -113,19 +113,14 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String[] sessionHandlesRevoked; if (revokeAcrossAllTenants) { - // when revokeAcrossAllTenants is true, and given that the backend SDK might pass tenant id - // we do not want to enforce public tenant here but behave as if this is an app specific API - // So instead of calling enforcePublicTenantAndGetAllStoragesForApp, we simply do all the logic - // here to fetch the storages and find the storage where `userId` exists. If user id does not - // exist, we use the storage for the tenantId passed in the request. AppIdentifier appIdentifier = getAppIdentifier(req); - Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier); try { - StorageAndUserIdMapping storageAndUserIdMapping = StorageLayer.findStorageAndUserIdMappingForUser( - appIdentifier, storages, userId, UserIdType.ANY); + StorageAndUserIdMapping storageAndUserIdMapping = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); storage = storageAndUserIdMapping.storage; } catch (UnknownUserIdException e) { - storage = getTenantStorage(req); + throw new IllegalStateException("should never happen"); } sessionHandlesRevoked = Session.revokeAllSessionsForUser( main, appIdentifier, storage, userId, revokeSessionsForLinkedAccounts); @@ -159,7 +154,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } result.add("sessionHandlesRevoked", sessionHandlesRevokedJSON); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } else { diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java index 8e7b53e2e..5f17f18f0 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java @@ -77,21 +77,15 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String[] sessionHandles; if (fetchAcrossAllTenants) { - // when fetchAcrossAllTenants is true, and given that the backend SDK might pass tenant id - // we do not want to enforce public tenant here but behave as if this is an app specific API - // So instead of calling enforcePublicTenantAndGetAllStoragesForApp, we simply do all the logic - // here to fetch the storages and find the storage where `userId` exists. If user id does not - // exist, we use the storage for the tenantId passed in the request. AppIdentifier appIdentifier = getAppIdentifier(req); - Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier); Storage storage; try { StorageAndUserIdMapping storageAndUserIdMapping = - StorageLayer.findStorageAndUserIdMappingForUser( - appIdentifier, storages, userId, UserIdType.ANY); + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); storage = storageAndUserIdMapping.storage; } catch (UnknownUserIdException e) { - storage = getTenantStorage(req); + throw new IllegalStateException("should never happen"); } sessionHandles = Session.getAllNonExpiredSessionHandlesForUser( main, appIdentifier, storage, userId, @@ -112,7 +106,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO result.add("sessionHandles", arr); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java b/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java index 2cb01382a..1819ded24 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java @@ -34,18 +34,24 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.usermetadata.sqlStorage.UserMetadataSQLStorage; +import io.supertokens.session.Session; +import io.supertokens.session.info.SessionInformationHolder; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.httpRequest.HttpResponseException; import io.supertokens.thirdparty.InvalidProviderConfigException; import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.utils.SemVer; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import static org.junit.Assert.*; @@ -375,4 +381,91 @@ public void testEmailVerificationWithUsersOnDifferentTenantStorages() throws Exc assertFalse(t1EvStorage.isEmailVerified(t0.toAppIdentifier(), user2.getSupertokensUserId(), "test@example.com")); // ensure t1 storage does not have user2's ev assertFalse(t0EvStorage.isEmailVerified(t0.toAppIdentifier(), user1.getSupertokensUserId(), "test@example.com")); // ensure t0 storage does not have user1's ev } + + @Test + public void testSessionCannotGetAcrossAllStorageOrRevokedAcrossAllTenantsFromNonPublicTenant() throws Exception { + if (StorageLayer.getBaseStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + + TenantIdentifier t0 = new TenantIdentifier(null, null, null); + Storage t0Storage = (StorageLayer.getStorage(t0, process.getProcess())); + + TenantIdentifier t1 = new TenantIdentifier(null, null, "t1"); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + + // Create users + AuthRecipeUserInfo user1 = EmailPassword.signUp(t0, t0Storage, process.getProcess(), "test@example.com", "password123"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", "password123"); + + UserIdMapping.populateExternalUserIdForUsers(t0.toAppIdentifier(), t0Storage, new AuthRecipeUserInfo[]{user1}); + UserIdMapping.populateExternalUserIdForUsers(t1.toAppIdentifier(), t1Storage, new AuthRecipeUserInfo[]{user2}); + + SessionInformationHolder sess1 = Session.createNewSession(t0, t0Storage, + process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject()); + SessionInformationHolder sess2 = Session.createNewSession(t1, t1Storage, + process.getProcess(), user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); + + { + Map params = new HashMap<>(); + params.put("fetchAcrossAllTenants", "true"); + params.put("userId", user1.getSupertokensUserId()); + + JsonObject sessionResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/user"), + params, 1000, 1000, null, SemVer.v4_0.get(), + "session"); + assertEquals("OK", sessionResponse.get("status").getAsString()); + assertEquals(1, sessionResponse.get("sessionHandles").getAsJsonArray().size()); + assertEquals(sess1.session.handle, sessionResponse.get("sessionHandles").getAsJsonArray().get(0).getAsString()); + } + + { + try { + Map params = new HashMap<>(); + params.put("fetchAcrossAllTenants", "true"); + params.put("userId", user1.getSupertokensUserId()); + + JsonObject sessionResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/user"), + params, 1000, 1000, null, SemVer.v5_0.get(), + "session"); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + } + + { + try { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", user1.getSupertokensUserId()); + requestBody.addProperty("revokeAcrossAllTenants", true); + + JsonObject sessionResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/remove"), requestBody, + 1000, 1000, null, SemVer.v5_0.get(), + "session"); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + } + + { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", user1.getSupertokensUserId()); + requestBody.addProperty("revokeAcrossAllTenants", true); + + JsonObject sessionResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/remove"), requestBody, + 1000, 1000, null, SemVer.v4_0.get(), + "session"); + assertEquals("OK", sessionResponse.get("status").getAsString()); + } + } } From 2f4776e1bdadf4f227f7cf701626fbaa2d62a5c8 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 18 Mar 2024 22:00:23 +0530 Subject: [PATCH 2/5] adding dev-v9.0.0 tag to this commit to ensure building --- cli/jar/cli.jar | Bin 47546 -> 47546 bytes downloader/jar/downloader.jar | Bin 15229 -> 15229 bytes ee/jar/ee.jar | Bin 14513 -> 14513 bytes jar/core-9.0.0.jar | Bin 765762 -> 765816 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/cli/jar/cli.jar b/cli/jar/cli.jar index afa32bc247f8ba155740bcedb423f6de4bc48d31..d5965db07d1d53f249680de77df5ddfecde258d3 100644 GIT binary patch delta 758 zcmY+C-%C?r9LCSi+1zoiMG%2Lss3GrLR$@*>7PEKQ*KHhw~h{J7lh4 zA@d|{j@%vUpYM7p@yWp$W!f0NfW${fuA^B!Fg_)M`;P;}Ta)jIrNJk(nFy=H@-F*9 z_Scs(uaI-JR5*VQhohIw{h{ua`E!u%^Z6G3J6P}1FW~Ci146hGcpAoWvrl0sa2T!y z&PP1JSmX-uG13dTqHRDhdK>s1Z3Wb2KOv#8>~a;Ra;y&s#Cm{i>=kfly22qCn@Mbl5%I0DoiES z1&pSsz3(Y%wmRJoGnA&dtu!tOb7m-fI@1KTt}D+B+(za!ZUWDZOTf0#iqkr~>H+Sr GHv9!1@&qmb delta 758 zcmZva-Ahwp9LLYj)E(!X2q7p(ut0)D#z?1WVjx<2SSa&l_>RiVw~3O9kr!o1gKqSx z2VEW81W#w(#Cvlei20r;bQmA2!tyaYBKylr z$#_xW)MZ?bdU^RTbuX{nfNWVwrJ6mfzqw>hYySw|V&It<*Ufgl7U0ZV4&3xr0yDn5 zz-M0%VD&cx9{(Wl$KMDj>n=h_X7TkBOr<~{;0|;F@xUT5wDA!5wc!M+Hr;^L&x1D0 zFii_m(y3rS)Q{i*poAR2Qiu{Nyu=liO0=ou?5MR^H-#x^B22mCVFjifX$KxesJ)*N yYPK}m0&_k}ao?hNAj}e@@K>=qs2x>a(C`|WMe7BgYqx>#8fCb?MHh2_tL8tSISN() diff --git a/downloader/jar/downloader.jar b/downloader/jar/downloader.jar index 90f10542438084c2ee17987bdcb246a469ca508e..03d589fbf9f07bd2bfbae9b875d44c97277aed7d 100644 GIT binary patch delta 335 zcmexc_P2~Dz?+$ci-CcIgMod+L>_e}AZhBY%D$l}f&mDaKm-sl194`)J~IP~@};^U z-l69NlK!K|4pz--=m6rn8`dy`WRIGdf~Y?xAkoQo zrurZ%-_#mJtub{2QD02sKvalX5Qth~mID_nU8T58&iJ7^G*sMi+RAi)UBI1q1#WdMlcvI0{vR#_n42`fJkC1ITk0J*7VdjJ3c diff --git a/ee/jar/ee.jar b/ee/jar/ee.jar index e8870b973e92b8ac9bf71bdac5376f85e5c74360..4b0f0b5d0d90d8e5aef7e3637cfd6b96ca4166e8 100644 GIT binary patch delta 199 zcmdm3xUrBYz?+$ci-CcIgMn+qL>_e}AZhBY%C(^=f&mDaKm-sl194`)J~IP~@};^U zf delta 199 zcmdm3xUrBYz?+$ci-CcIgP}NLB9A%~kTmsHEsiLPU;qLp5CH_tK%AMc&&+_Le5o!- z`4s~oIhl>o5=d=!WIW0OqB+>^F@fk89FMg@(q~P;s{Wfafq2cPrXXsUsRM}mW2z6L sbj_?mRF;_?h+1Xl4x&DrMT4jS^I#CQ)I1PGWtquL7O_wQF)b`A0sL_}yZ`_I diff --git a/jar/core-9.0.0.jar b/jar/core-9.0.0.jar index 5af5fdf2c5f5f4b5dfe99cbf67b13e901d55a51d..972f3e3e2c30f4b7ce81239b6b171c9982d8b27b 100644 GIT binary patch delta 16894 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 WP+=<1Up@tuYD8qH(tHR|@&5pxt8$tE delta 16687 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 Date: Wed, 20 Mar 2024 19:09:45 +0530 Subject: [PATCH 3/5] fix: totp (#967) * fix: totp * fix: changelog and test * fix: version --- CHANGELOG.md | 5 +++ build.gradle | 2 +- src/main/java/io/supertokens/totp/Totp.java | 22 ++++++++-- .../webserver/api/totp/VerifyTotpAPI.java | 3 -- .../api/totp/VerifyTotpDeviceAPI.java | 3 -- .../supertokens/test/totp/TOTPRecipeTest.java | 24 ++++++++++- .../test/totp/api/VerifyTotpAPITest.java | 38 ++++++++++++++--- .../totp/api/VerifyTotpDeviceAPITest.java | 41 +++++++++++++++---- 8 files changed, 111 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf903f10..6474eb568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [9.0.1] - 2024-03-20 + +- Fixes verify TOTP and verify device APIs to treat any code as invalid +- Fixes the computation of the number of failed attempts when return `INVALID_TOTP_ERROR` + ## [9.0.0] - 2024-03-13 ### Added diff --git a/build.gradle b/build.gradle index 6080bc20d..62bae0a23 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "9.0.0" +version = "9.0.1" repositories { diff --git a/src/main/java/io/supertokens/totp/Totp.java b/src/main/java/io/supertokens/totp/Totp.java index e76dd6182..957cd4f7e 100644 --- a/src/main/java/io/supertokens/totp/Totp.java +++ b/src/main/java/io/supertokens/totp/Totp.java @@ -203,17 +203,28 @@ private static void checkAndStoreCode(TenantIdentifier tenantIdentifier, Storage // N represents # of invalid attempts that will trigger rate limiting: int N = Config.getConfig(tenantIdentifier, main).getTotpMaxAttempts(); // (Default 5) - // Count # of contiguous invalids in latest N attempts (stop at first valid): - long invalidOutOfN = Arrays.stream(usedCodes).limit(N).takeWhile(usedCode -> !usedCode.isValid) - .count(); + + // filter only invalid codes, stop at first valid code + TOTPUsedCode[] invalidUsedCodes = Arrays.stream(usedCodes).limit(N).takeWhile( + usedCode -> !usedCode.isValid).toArray(TOTPUsedCode[]::new); + int rateLimitResetTimeInMs = Config.getConfig(tenantIdentifier, main) .getTotpRateLimitCooldownTimeSec() * 1000; // (Default 15 mins) + // Count how many of the latest invalid codes fall under the rate limiting compared + // to the latest invalid attempt + long invalidOutOfN = 0; + if (invalidUsedCodes.length > 0) { + invalidOutOfN = Arrays.stream(invalidUsedCodes).limit(N).takeWhile( + usedCode -> usedCode.createdTime > invalidUsedCodes[0].createdTime - rateLimitResetTimeInMs + ).count(); + } + // Check if the user has been rate limited: if (invalidOutOfN == N) { // All of the latest N attempts were invalid: - long latestInvalidCodeCreatedTime = usedCodes[0].createdTime; + long latestInvalidCodeCreatedTime = invalidUsedCodes[0].createdTime; long now = System.currentTimeMillis(); if (now - latestInvalidCodeCreatedTime < rateLimitResetTimeInMs) { @@ -225,6 +236,9 @@ private static void checkAndStoreCode(TenantIdentifier tenantIdentifier, Storage // If we insert the used code here, then it will further delay the user from // being able to login. So not inserting it here. } + + // since we are past the cool down period, the user can retry all the attempts + invalidOutOfN = 0; } // Check if the code is valid for any device: diff --git a/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpAPI.java b/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpAPI.java index 83b5f16b4..8e303ff1e 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpAPI.java @@ -46,9 +46,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (userId.isEmpty()) { throw new ServletException(new BadRequestException("userId cannot be empty")); } - if (totp.length() != 6) { - throw new ServletException(new BadRequestException("totp must be 6 characters long")); - } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpDeviceAPI.java b/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpDeviceAPI.java index 770ee7bd9..e50c22f2e 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpDeviceAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpDeviceAPI.java @@ -50,9 +50,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (deviceName.isEmpty()) { throw new ServletException(new BadRequestException("deviceName cannot be empty")); } - if (totp.length() != 6) { - throw new ServletException(new BadRequestException("totp must be 6 characters long")); - } JsonObject result = new JsonObject(); diff --git a/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java b/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java index 9e809d86e..8177878ef 100644 --- a/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java +++ b/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java @@ -348,13 +348,33 @@ public void rateLimitCooldownTest() throws Exception { // Wait for 1 second (Should cool down rate limiting): Thread.sleep(1000); // But again try with invalid code: - assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "invalid0")); + InvalidTotpException invalidTotpException = assertThrows(InvalidTotpException.class, + () -> Totp.verifyCode(main, "user", "invalid0")); + assertEquals(1, invalidTotpException.currentAttempts); + invalidTotpException = assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "invalid0")); + assertEquals(2, invalidTotpException.currentAttempts); + invalidTotpException = assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "invalid0")); + assertEquals(3, invalidTotpException.currentAttempts); + // This triggered rate limiting again. So even valid codes will fail for // another cooldown period: - assertThrows(LimitReachedException.class, + LimitReachedException limitReachedException = assertThrows(LimitReachedException.class, () -> Totp.verifyCode(main, "user", generateTotpCode(main, device))); + assertEquals(3, limitReachedException.currentAttempts); // Wait for 1 second (Should cool down rate limiting): Thread.sleep(1000); + + // test that after cool down, we can retry invalid codes N times again + invalidTotpException = assertThrows(InvalidTotpException.class, + () -> Totp.verifyCode(main, "user", "invalid0")); + assertEquals(1, invalidTotpException.currentAttempts); + invalidTotpException = assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "invalid0")); + assertEquals(2, invalidTotpException.currentAttempts); + invalidTotpException = assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "invalid0")); + assertEquals(3, invalidTotpException.currentAttempts); + + Thread.sleep(1100); + // Now try with valid code: Totp.verifyCode(main, "user", generateTotpCode(main, device)); // Now invalid code shouldn't trigger rate limiting. Unless you do it N times: diff --git a/src/test/java/io/supertokens/test/totp/api/VerifyTotpAPITest.java b/src/test/java/io/supertokens/test/totp/api/VerifyTotpAPITest.java index cbfa0a9e1..c030e298d 100644 --- a/src/test/java/io/supertokens/test/totp/api/VerifyTotpAPITest.java +++ b/src/test/java/io/supertokens/test/totp/api/VerifyTotpAPITest.java @@ -23,6 +23,8 @@ import org.junit.Test; import org.junit.rules.TestRule; +import java.io.IOException; + import static io.supertokens.test.totp.TOTPRecipeTest.generateTotpCode; import static org.junit.Assert.*; @@ -56,6 +58,21 @@ private Exception verifyTotpCodeRequest(TestingProcessManager.TestingProcess pro "totp")); } + private void verifyTotpRequestThatReturnsInvalidCode(TestingProcessManager.TestingProcess process, JsonObject body) + throws HttpResponseException, IOException { + JsonObject resp = HttpRequestForTesting.sendJsonPOSTRequest( + process.getProcess(), + "", + "http://localhost:3567/recipe/totp/verify", + body, + 1000, + 1000, + null, + Utils.getCdiVersionStringLatestForTests(), + "totp"); + assertEquals("INVALID_TOTP_ERROR", resp.get("status").getAsString()); + } + private void checkFieldMissingErrorResponse(Exception ex, String fieldName) { assert ex instanceof HttpResponseException; HttpResponseException e = (HttpResponseException) ex; @@ -149,18 +166,27 @@ public void testApi() throws Exception { checkResponseErrorContains(e, "userId cannot be empty"); // Note that this is not a field missing error body.addProperty("userId", device.userId); - e = verifyTotpCodeRequest(process, body); - checkResponseErrorContains(e, "totp must be 6 characters long"); + verifyTotpRequestThatReturnsInvalidCode(process, body); + + Thread.sleep(1100); // test totp of length 5: body.addProperty("totp", "12345"); - e = verifyTotpCodeRequest(process, body); - checkResponseErrorContains(e, "totp must be 6 characters long"); + verifyTotpRequestThatReturnsInvalidCode(process, body); + + Thread.sleep(1100); + + // test totp of alphabets: + body.addProperty("totp", "abcd"); + verifyTotpRequestThatReturnsInvalidCode(process, body); + + Thread.sleep(1100); // test totp of length 8: body.addProperty("totp", "12345678"); - e = verifyTotpCodeRequest(process, body); - checkResponseErrorContains(e, "totp must be 6 characters long"); + verifyTotpRequestThatReturnsInvalidCode(process, body); + + Thread.sleep(1100); // but let's pass invalid code first body.addProperty("totp", "123456"); diff --git a/src/test/java/io/supertokens/test/totp/api/VerifyTotpDeviceAPITest.java b/src/test/java/io/supertokens/test/totp/api/VerifyTotpDeviceAPITest.java index 8a55255c9..2cae79fc5 100644 --- a/src/test/java/io/supertokens/test/totp/api/VerifyTotpDeviceAPITest.java +++ b/src/test/java/io/supertokens/test/totp/api/VerifyTotpDeviceAPITest.java @@ -21,6 +21,8 @@ import org.junit.Test; import org.junit.rules.TestRule; +import java.io.IOException; + import static org.junit.Assert.*; public class VerifyTotpDeviceAPITest { @@ -53,6 +55,21 @@ private Exception updateDeviceRequest(TestingProcessManager.TestingProcess proce "totp")); } + private void requestWithInvalidCode(TestingProcessManager.TestingProcess process, JsonObject body) + throws HttpResponseException, IOException { + JsonObject resp = HttpRequestForTesting.sendJsonPOSTRequest( + process.getProcess(), + "", + "http://localhost:3567/recipe/totp/device/verify", + body, + 1000, + 1000, + null, + Utils.getCdiVersionStringLatestForTests(), + "totp"); + assertEquals("INVALID_TOTP_ERROR", resp.get("status").getAsString()); + } + private void checkFieldMissingErrorResponse(Exception ex, String fieldName) { assert ex instanceof HttpResponseException; HttpResponseException e = (HttpResponseException) ex; @@ -126,7 +143,7 @@ public void testApi() throws Exception { checkFieldMissingErrorResponse(e, "totp"); } - // Invalid userId/deviceName/skew/period + // Invalid userId/deviceName/totp { body.addProperty("totp", ""); Exception e = updateDeviceRequest(process, body); @@ -137,18 +154,27 @@ public void testApi() throws Exception { checkResponseErrorContains(e, "deviceName cannot be empty"); body.addProperty("deviceName", device.deviceName); - e = updateDeviceRequest(process, body); - checkResponseErrorContains(e, "totp must be 6 characters long"); + requestWithInvalidCode(process, body); + + Thread.sleep(1100); // test totp of length 5: body.addProperty("totp", "12345"); - e = updateDeviceRequest(process, body); - checkResponseErrorContains(e, "totp must be 6 characters long"); + requestWithInvalidCode(process, body); + + Thread.sleep(1100); // test totp of length 8: body.addProperty("totp", "12345678"); - e = updateDeviceRequest(process, body); - checkResponseErrorContains(e, "totp must be 6 characters long"); + requestWithInvalidCode(process, body); + + Thread.sleep(1100); + + // test totp of length alphabets: + body.addProperty("totp", "abcd"); + requestWithInvalidCode(process, body); + + Thread.sleep(2100); // but let's pass invalid code first body.addProperty("totp", "123456"); @@ -247,5 +273,4 @@ public void testApi() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } - } From 0e4e29cd99739db1ae055a6a2482969d5c70777b Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 20 Mar 2024 19:11:59 +0530 Subject: [PATCH 4/5] adding dev-v9.0.1 tag to this commit to ensure building --- cli/jar/cli.jar | Bin 47546 -> 47546 bytes downloader/jar/downloader.jar | Bin 15229 -> 15229 bytes ee/jar/ee.jar | Bin 14513 -> 14513 bytes jar/{core-9.0.0.jar => core-9.0.1.jar} | Bin 765816 -> 765897 bytes 4 files changed, 0 insertions(+), 0 deletions(-) rename jar/{core-9.0.0.jar => core-9.0.1.jar} (92%) diff --git a/cli/jar/cli.jar b/cli/jar/cli.jar index d5965db07d1d53f249680de77df5ddfecde258d3..e55737871a5ab2babe1bc958cbe10bbbe04a657f 100644 GIT binary patch delta 835 zcmdn>nQ7N&Cf)#VW)?061`ZB}vY90ldDWOfl$no;+Dw5G=S{ zdp8$YFx4yoEU?3DKO>k?W${1+EEDVN2^LuCy9{Q7@#OQt9Fu#4dB75Lf@|2o4CyFI z7BIs+ZadhJ$sglgzycNtvmtsxb_b-fPUcI4=#@uiSjbj@TZu#%6HpF?DB zPh;o8<_6A%-~imLwD1SSH?oT@z&axqb3p7*Uhx4WFga}HUkJU*1Wfm?0!PZ^^Q-hg z{7I{=z{0kx?Z9;1YF{vYZgnD>mkqgKddmig**qH|W>;)X1B*Z0m;t7JHU)v{g_|Je z`T*@x+6=KPW-~;*+*P w^xxeEPGge|wnNOBxgDIcCO_Q{kvG_p1Xe$FhZ~r_w*z91$xcYPmF{!}01f>}^Z)<= delta 835 zcmdn>nQ7N&Cf)#VW)?061`ZAemJLM{dDWOfl$n?Kk}P0`dE9oeA(KDGyMP5O5@tj6g6s}RW1Y;G2GJ{aic(3_+u zpI>VZW^=Af2GjZL;=uHUbu+3chZ!NF;=(+*6R H?sNqJH^C^Z diff --git a/downloader/jar/downloader.jar b/downloader/jar/downloader.jar index 03d589fbf9f07bd2bfbae9b875d44c97277aed7d..7d2880762d65875f682643facf80ebf5fa0aaa60 100644 GIT binary patch delta 375 zcmexc_P2~Tz?+$ci-CcIgQ0R}$wXc?W)NlOr3R!T7=VBYL;wLZ5NGD=Gc%xSSf&Hk zaMc_{ZRTfO$p&If-p%d|7GUHEW`zo9PxdwEncT}@k8Jui)nZS&f9FMfYO3s;pl}<8k1an(VO~CYSQ+qJ|*HjNo t>zP@B>1;DwFumH$4NQMAivrVu=0RY3nRx)1&Nh>tEMlPqW?NcR0081INV5O{ delta 219 zcmdm3xUrBoz?+$ci-CcIgMn*9(L`P~W)NlOr3R!T7=VBYL;wLZ5NGD=Gc%xSSf&Hk za8(~fO=f4b0MVPB7>}@k8Jui)nZS&f9FMfYO3s;pl}<8k1an(VO~CYSQ+qJ|*HjNo t>zP@B>1;DwFumH$4NQMAivrVu=0RY3nRx)1&Nh>tEMlPqW?NcR007L$K(zn> diff --git a/jar/core-9.0.0.jar b/jar/core-9.0.1.jar similarity index 92% rename from jar/core-9.0.0.jar rename to jar/core-9.0.1.jar index 972f3e3e2c30f4b7ce81239b6b171c9982d8b27b..ac53fa6fcf65ed407dd7a35101c7499235b0f67b 100644 GIT binary patch delta 24306 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 Sg9)l4`~=A%Ns*`#4gUjiGlT8` From 691d59d383e1a93bd33f668259bb21dfbe92ca46 Mon Sep 17 00:00:00 2001 From: Rishabh Poddar Date: Thu, 21 Mar 2024 12:44:58 +0530 Subject: [PATCH 5/5] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4a96a5bb2..e9d0fb8e7 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,10 @@ Melvyn Hills
Daniil Borovoy

Krzysztof Witkowski
+ +
Lehoczky Zoltán
+Melvyn Hills + ## 👩‍💻 Contributing