From 766073954fb0892b82e7cd440b7313bd01b634b2 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 23 Jan 2024 16:14:52 +0530 Subject: [PATCH 1/4] fix: mark fake email as verified in emailpassword sign up --- .../emailpassword/EmailPassword.java | 40 ++++++++++++++++++- src/main/java/io/supertokens/utils/Utils.java | 4 ++ .../api/emailpassword/SignUpAPI.java | 4 +- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 0c7fb08a0..9d2b9000f 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -93,8 +93,16 @@ public static AuthRecipeUserInfo signUp(Main main, @Nonnull String email, @Nonnu } } + @TestOnly + public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + @Nonnull String email, @Nonnull String password) + throws DuplicateEmailException, StorageQueryException, TenantOrAppNotFoundException, + BadPermissionException { + return signUp(tenantIdentifierWithStorage, main, email, password, false); + } + public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, - @Nonnull String email, @Nonnull String password) + @Nonnull String email, @Nonnull String password, boolean setVerifiedForFakeEmails) throws DuplicateEmailException, StorageQueryException, TenantOrAppNotFoundException, BadPermissionException { @@ -115,9 +123,37 @@ public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdenti long timeJoined = System.currentTimeMillis(); try { - return tenantIdentifierWithStorage.getEmailPasswordStorage() + AuthRecipeUserInfo newUser = tenantIdentifierWithStorage.getEmailPasswordStorage() .signUp(tenantIdentifierWithStorage, userId, email, hashedPassword, timeJoined); + if (setVerifiedForFakeEmails && Utils.isFakeEmail(email)) { + try { + AuthRecipeUserInfo finalUser = newUser; + tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + try { + + tenantIdentifierWithStorage.getEmailVerificationStorage() + .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + finalUser.getSupertokensUserId(), email, true); + tenantIdentifierWithStorage.getEmailVerificationStorage() + .commitTransaction(con); + + return null; + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); + } + }); + newUser.loginMethods[0].setVerified(); // newly created user has only one loginMethod + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) e.actualException; + } + throw new StorageQueryException(e); + } + + } + + return newUser; } catch (DuplicateUserIdException ignored) { // we retry with a new userId (while loop) } diff --git a/src/main/java/io/supertokens/utils/Utils.java b/src/main/java/io/supertokens/utils/Utils.java index ca66eaa63..ecd3a0479 100644 --- a/src/main/java/io/supertokens/utils/Utils.java +++ b/src/main/java/io/supertokens/utils/Utils.java @@ -312,6 +312,10 @@ public static boolean verifyWithPublicKey(String content, String signature, Stri return sign.verify(decoder.decode(signature)); } + public static boolean isFakeEmail(String email) { + return email.endsWith("@stfakeemail.supertokens.com") || email.endsWith(".fakeemail.com"); + } + public static class PubPriKey { public String publicKey; public String privateKey; diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index da5725b77..dae173c05 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -79,7 +79,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); - AuthRecipeUserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); + AuthRecipeUserInfo user = EmailPassword.signUp( + tenant, super.main, normalisedEmail, password, + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v5_0)); ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.getSupertokensUserId()); From a0e7e52cbb64a628691829eb14f5177b4008eef6 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 24 Jan 2024 12:10:27 +0530 Subject: [PATCH 2/4] fix: add tests --- .../emailpassword/EmailPassword.java | 4 +- .../emailpassword/api/SignUpAPITest5_0.java | 167 ++++++++++++++++++ 2 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest5_0.java diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 9d2b9000f..cce06e3c2 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -128,13 +128,12 @@ public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdenti if (setVerifiedForFakeEmails && Utils.isFakeEmail(email)) { try { - AuthRecipeUserInfo finalUser = newUser; tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { try { tenantIdentifierWithStorage.getEmailVerificationStorage() .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - finalUser.getSupertokensUserId(), email, true); + newUser.getSupertokensUserId(), email, true); tenantIdentifierWithStorage.getEmailVerificationStorage() .commitTransaction(con); @@ -150,7 +149,6 @@ public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdenti } throw new StorageQueryException(e); } - } return newUser; diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest5_0.java b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest5_0.java new file mode 100644 index 000000000..d2d9c21f7 --- /dev/null +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest5_0.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.emailpassword.api; + +import com.google.gson.JsonObject; +import io.supertokens.ActiveUsers; +import io.supertokens.ProcessState; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.util.HashMap; + +import static org.junit.Assert.*; + + +public class SignUpAPITest5_0 { + + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + // Check good input works + @Test + public void testGoodInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "random@gmail.com"); + responseBody.addProperty("password", "validPass123"); + + Thread.sleep(1); // add a small delay to ensure a unique timestamp + long beforeSignIn = System.currentTimeMillis(); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signup", responseBody, 1000, 1000, null, SemVer.v5_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + + JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); + assertNotNull(jsonUser.get("id")); + assertNotNull(jsonUser.get("timeJoined")); + assert (!jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("random@gmail.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertNotNull(lM.get("timeJoined")); + assertNotNull(lM.get("recipeUserId")); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "random@gmail.com"); + assert (lM.entrySet().size() == 6); + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), beforeSignIn); + assert (activeUsers == 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testSignUpWithFakeEmailMarksTheEmailAsVerified() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "user1.google@@stfakeemail.supertokens.com"); + responseBody.addProperty("password", "validPass123"); + + Thread.sleep(1); // add a small delay to ensure a unique timestamp + long beforeSignIn = System.currentTimeMillis(); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signup", responseBody, 1000, 1000, null, SemVer.v5_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + + JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); + assertNotNull(jsonUser.get("id")); + assertNotNull(jsonUser.get("timeJoined")); + assert (!jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("user1.google@@stfakeemail.supertokens.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertTrue(lM.get("verified").getAsBoolean()); // Email must be verified + assertNotNull(lM.get("timeJoined")); + assertNotNull(lM.get("recipeUserId")); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "user1.google@@stfakeemail.supertokens.com"); + assert (lM.entrySet().size() == 6); + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), beforeSignIn); + assert (activeUsers == 1); + + // double ensure that the email is verified using email verification + + String userId = jsonUser.get("id").getAsString(); + + HashMap map = new HashMap<>(); + map.put("userId", userId); + map.put("email", "user1.google@@stfakeemail.supertokens.com"); + + JsonObject verifyResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/email/verify", map, 1000, 1000, null, + SemVer.v2_7.get(), "emailverification"); + assertEquals(verifyResponse.entrySet().size(), 2); + assertEquals(verifyResponse.get("status").getAsString(), "OK"); + assertTrue(verifyResponse.get("isVerified").getAsBoolean()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 7a7e8ddbb7d3aa0464431db5e3e3b7abe7eacfb1 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 30 Jan 2024 12:10:26 +0530 Subject: [PATCH 3/4] fix: pr comments --- .../io/supertokens/emailpassword/EmailPassword.java | 13 ++----------- .../webserver/api/emailpassword/SignUpAPI.java | 3 +-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index cce06e3c2..97772a433 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -93,16 +93,8 @@ public static AuthRecipeUserInfo signUp(Main main, @Nonnull String email, @Nonnu } } - @TestOnly public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, - @Nonnull String email, @Nonnull String password) - throws DuplicateEmailException, StorageQueryException, TenantOrAppNotFoundException, - BadPermissionException { - return signUp(tenantIdentifierWithStorage, main, email, password, false); - } - - public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, - @Nonnull String email, @Nonnull String password, boolean setVerifiedForFakeEmails) + @Nonnull String email, @Nonnull String password) throws DuplicateEmailException, StorageQueryException, TenantOrAppNotFoundException, BadPermissionException { @@ -118,7 +110,6 @@ public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdenti .createHashWithSalt(tenantIdentifierWithStorage.toAppIdentifier(), password); while (true) { - String userId = Utils.getUUID(); long timeJoined = System.currentTimeMillis(); @@ -126,7 +117,7 @@ public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdenti AuthRecipeUserInfo newUser = tenantIdentifierWithStorage.getEmailPasswordStorage() .signUp(tenantIdentifierWithStorage, userId, email, hashedPassword, timeJoined); - if (setVerifiedForFakeEmails && Utils.isFakeEmail(email)) { + if (Utils.isFakeEmail(email)) { try { tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { try { diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index dae173c05..95d34cb1c 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -80,8 +80,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); AuthRecipeUserInfo user = EmailPassword.signUp( - tenant, super.main, normalisedEmail, password, - getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v5_0)); + tenant, super.main, normalisedEmail, password); ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.getSupertokensUserId()); From cdbd373e88c5e150c89446fa879febcfeb9cf88a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 30 Jan 2024 12:13:19 +0530 Subject: [PATCH 4/4] fix: clean --- .../io/supertokens/webserver/api/emailpassword/SignUpAPI.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index 95d34cb1c..da5725b77 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -79,8 +79,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); - AuthRecipeUserInfo user = EmailPassword.signUp( - tenant, super.main, normalisedEmail, password); + AuthRecipeUserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.getSupertokensUserId());