diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 73ed544d0..ba76d7f45 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -252,7 +252,7 @@ public static ConsumeCodeResponse consumeCode(Main main, Storage storage = StorageLayer.getStorage(main); return consumeCode( new TenantIdentifier(null, null, null), storage, - main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false, true); + main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } @@ -269,7 +269,7 @@ public static ConsumeCodeResponse consumeCode(Main main, Storage storage = StorageLayer.getStorage(main); return consumeCode( new TenantIdentifier(null, null, null), storage, - main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, setEmailVerified, true); + main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, setEmailVerified); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } @@ -284,12 +284,12 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, TenantOrAppNotFoundException, BadPermissionException { return consumeCode(tenantIdentifier, storage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, - false, true); + false); } - public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, Storage storage, Main main, - String deviceId, String deviceIdHashFromUser, - String userInputCode, String linkCode, boolean setEmailVerified, boolean createRecipeUserIfNotExists) + public static PasswordlessDevice checkCodeAndReturnDevice(TenantIdentifier tenantIdentifier, Storage storage, Main main, + String deviceId, String deviceIdHashFromUser, + String userInputCode, String linkCode, boolean deleteCodeOnSuccess) throws RestartFlowException, ExpiredUserInputCodeException, IncorrectUserInputCodeException, DeviceIdHashMismatchException, StorageTransactionLogicException, StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, @@ -339,9 +339,8 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, throw new DeviceIdHashMismatchException(); } - PasswordlessDevice consumedDevice; try { - consumedDevice = passwordlessStorage.startTransaction(con -> { + return passwordlessStorage.startTransaction(con -> { PasswordlessDevice device = passwordlessStorage.getDevice_Transaction(tenantIdentifier, con, deviceIdHash.encode()); if (device == null) { @@ -386,12 +385,14 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, throw new StorageTransactionLogicException(new RestartFlowException()); } - if (device.email != null) { - passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifier, con, - device.email); - } else if (device.phoneNumber != null) { - passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifier, con, - device.phoneNumber); + if (deleteCodeOnSuccess) { + if (device.email != null) { + passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifier, con, + device.email); + } else if (device.phoneNumber != null) { + passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifier, con, + device.phoneNumber); + } } passwordlessStorage.commitTransaction(con); @@ -409,6 +410,20 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, } throw e; } + } + + public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, Storage storage, Main main, + String deviceId, String deviceIdHashFromUser, + String userInputCode, String linkCode, boolean setEmailVerified) + throws RestartFlowException, ExpiredUserInputCodeException, + IncorrectUserInputCodeException, DeviceIdHashMismatchException, StorageTransactionLogicException, + StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, + TenantOrAppNotFoundException, BadPermissionException { + + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); + + PasswordlessDevice consumedDevice = checkCodeAndReturnDevice(tenantIdentifier, storage, main, deviceId, deviceIdHashFromUser, + userInputCode, linkCode, true); // Getting here means that we successfully consumed the code AuthRecipeUserInfo user = null; @@ -441,57 +456,53 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, } if (user == null) { - if (createRecipeUserIfNotExists) { - while (true) { - try { - String userId = Utils.getUUID(); - long timeJoined = System.currentTimeMillis(); - user = passwordlessStorage.createUser(tenantIdentifier, userId, consumedDevice.email, - consumedDevice.phoneNumber, timeJoined); - - // Set email as verified, if using email - if (setEmailVerified && consumedDevice.email != null) { - try { - AuthRecipeUserInfo finalUser = user; - EmailVerificationSQLStorage evStorage = - StorageUtils.getEmailVerificationStorage(storage); - evStorage.startTransaction(con -> { - try { - evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, - finalUser.getSupertokensUserId(), consumedDevice.email, true); - evStorage.commitTransaction(con); - - return null; - } catch (TenantOrAppNotFoundException e) { - throw new StorageTransactionLogicException(e); - } - }); - user.loginMethods[0].setVerified(); // newly created user has only one loginMethod - } catch (StorageTransactionLogicException e) { - if (e.actualException instanceof TenantOrAppNotFoundException) { - throw (TenantOrAppNotFoundException) e.actualException; + while (true) { + try { + String userId = Utils.getUUID(); + long timeJoined = System.currentTimeMillis(); + user = passwordlessStorage.createUser(tenantIdentifier, userId, consumedDevice.email, + consumedDevice.phoneNumber, timeJoined); + + // Set email as verified, if using email + if (setEmailVerified && consumedDevice.email != null) { + try { + AuthRecipeUserInfo finalUser = user; + EmailVerificationSQLStorage evStorage = + StorageUtils.getEmailVerificationStorage(storage); + evStorage.startTransaction(con -> { + try { + evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, + finalUser.getSupertokensUserId(), consumedDevice.email, true); + evStorage.commitTransaction(con); + + return null; + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); } - throw new StorageQueryException(e); + }); + user.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 new ConsumeCodeResponse(true, user, consumedDevice.email, consumedDevice.phoneNumber, consumedDevice); - } catch (DuplicateEmailException | DuplicatePhoneNumberException e) { - // Getting these would mean that between getting the user and trying creating it: - // 1. the user managed to do a full create+consume flow - // 2. the users email or phoneNumber was updated to the new one (including device cleanup) - // These should be almost impossibly rare, so it's safe to just ask the user to restart. - // Also, both would make the current login fail if done before the transaction - // by cleaning up the device/code this consume would've used. - throw new RestartFlowException(); - } catch (DuplicateUserIdException e) { - // We can retry.. } + + return new ConsumeCodeResponse(true, user, consumedDevice.email, consumedDevice.phoneNumber, consumedDevice); + } catch (DuplicateEmailException | DuplicatePhoneNumberException e) { + // Getting these would mean that between getting the user and trying creating it: + // 1. the user managed to do a full create+consume flow + // 2. the users email or phoneNumber was updated to the new one (including device cleanup) + // These should be almost impossibly rare, so it's safe to just ask the user to restart. + // Also, both would make the current login fail if done before the transaction + // by cleaning up the device/code this consume would've used. + throw new RestartFlowException(); + } catch (DuplicateUserIdException e) { + // We can retry.. } } } else { - // We do not need this cleanup if we are creating the user, since it uses the email/phoneNumber of the - // device, which has already been cleaned up if (setEmailVerified && consumedDevice.email != null) { // Set email verification try { @@ -518,6 +529,8 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, } } + // We do need the cleanup here, however, we do not need this cleanup in the `if` block above + // since it uses the email/phoneNumber of the device, which has already been cleaned up if (loginMethod.email != null && !loginMethod.email.equals(consumedDevice.email)) { removeCodesByEmail(tenantIdentifier, storage, loginMethod.email); } @@ -570,6 +583,18 @@ public static void removeCode(TenantIdentifier tenantIdentifier, Storage storage }); } + public static void removeDevice(TenantIdentifier tenantIdentifier, Storage storage, + String deviceIdHash) + throws StorageQueryException, StorageTransactionLogicException { + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); + + passwordlessStorage.startTransaction(con -> { + passwordlessStorage.deleteDevice_Transaction(tenantIdentifier, con, deviceIdHash); + passwordlessStorage.commitTransaction(con); + return null; + }); + } + @TestOnly public static void removeCodesByEmail(Main main, String email) throws StorageQueryException, StorageTransactionLogicException { diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index f6c564b9e..700fb4ba1 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -187,6 +187,7 @@ private void setupRoutes() { addAPI(new DeleteCodesAPI(main)); addAPI(new DeleteCodeAPI(main)); addAPI(new CreateCodeAPI(main)); + addAPI(new CheckCodeAPI(main)); addAPI(new ConsumeCodeAPI(main)); addAPI(new TelemetryAPI(main)); addAPI(new UsersCountAPI(main)); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/CheckCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/CheckCodeAPI.java new file mode 100644 index 000000000..f04c15f40 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/passwordless/CheckCodeAPI.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020, 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.webserver.api.passwordless; + +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class CheckCodeAPI extends WebserverAPI { + + private static final long serialVersionUID = -4641988458637882374L; + + public CheckCodeAPI(Main main) { + super(main, RECIPE_ID.PASSWORDLESS.toString()); + } + + @Override + public String getPath() { + return "/recipe/signinup/code/check"; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is tenant specific + // Logic based on: https://app.code2flow.com/OFxcbh1FNLXd + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + + String linkCode = null; + String deviceId = null; + String userInputCode = null; + + String deviceIdHash = InputParser.parseStringOrThrowError(input, "preAuthSessionId", false); + + if (input.has("linkCode")) { + if (input.has("userInputCode") || input.has("deviceId")) { + throw new ServletException( + new BadRequestException("Please provide exactly one of linkCode or deviceId+userInputCode")); + } + linkCode = InputParser.parseStringOrThrowError(input, "linkCode", false); + } else if (input.has("userInputCode") && input.has("deviceId")) { + deviceId = InputParser.parseStringOrThrowError(input, "deviceId", false); + userInputCode = InputParser.parseStringOrThrowError(input, "userInputCode", false); + } else { + throw new ServletException( + new BadRequestException("Please provide exactly one of linkCode or deviceId+userInputCode")); + } + + try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = this.getTenantStorage(req); + Passwordless.checkCodeAndReturnDevice( + tenantIdentifier, + storage, main, + deviceId, deviceIdHash, + userInputCode, linkCode, false); + + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + + super.sendJsonResponse(200, result, resp); + } catch (RestartFlowException ex) { + JsonObject result = new JsonObject(); + result.addProperty("status", "RESTART_FLOW_ERROR"); + super.sendJsonResponse(200, result, resp); + } catch (ExpiredUserInputCodeException ex) { + JsonObject result = new JsonObject(); + result.addProperty("status", "EXPIRED_USER_INPUT_CODE_ERROR"); + result.addProperty("failedCodeInputAttemptCount", ex.failedCodeInputs); + result.addProperty("maximumCodeInputAttempts", ex.maximumCodeInputAttempts); + super.sendJsonResponse(200, result, resp); + } catch (IncorrectUserInputCodeException ex) { + JsonObject result = new JsonObject(); + result.addProperty("status", "INCORRECT_USER_INPUT_CODE_ERROR"); + result.addProperty("failedCodeInputAttemptCount", ex.failedCodeInputs); + result.addProperty("maximumCodeInputAttempts", ex.maximumCodeInputAttempts); + + super.sendJsonResponse(200, result, resp); + } catch (DeviceIdHashMismatchException ex) { + throw new ServletException(new BadRequestException("preAuthSessionId and deviceId doesn't match")); + } catch (StorageTransactionLogicException | StorageQueryException | NoSuchAlgorithmException | + InvalidKeyException | TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } catch (Base64EncodingException ex) { + throw new ServletException(new BadRequestException("Input encoding error in " + ex.source)); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index cb9245d5a..a1aa41ee6 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -65,7 +65,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String linkCode = null; String deviceId = null; String userInputCode = null; - Boolean createRecipeUserIfNotExists = true; String deviceIdHash = InputParser.parseStringOrThrowError(input, "preAuthSessionId", false); @@ -83,12 +82,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I new BadRequestException("Please provide exactly one of linkCode or deviceId+userInputCode")); } - if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v5_0)) { - if (input.has("createRecipeUserIfNotExists")) { - createRecipeUserIfNotExists = InputParser.parseBooleanOrThrowError(input, "createRecipeUserIfNotExists", false); - } - } - try { TenantIdentifier tenantIdentifier = getTenantIdentifier(req); Storage storage = this.getTenantStorage(req); @@ -98,52 +91,33 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I deviceId, deviceIdHash, userInputCode, linkCode, // From CDI version 4.0 onwards, the email verification will be set - getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0), - createRecipeUserIfNotExists); + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{consumeCodeResponse.user}); + + ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, + consumeCodeResponse.user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? consumeCodeResponse.user.toJson() : + consumeCodeResponse.user.toJsonWithoutAccountLinking(); - if (consumeCodeResponse.user != null) { - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{consumeCodeResponse.user}); - - ActiveUsers.updateLastActive(this.getAppIdentifier(req), main, consumeCodeResponse.user.getSupertokensUserId()); - - JsonObject userJson = getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? consumeCodeResponse.user.toJson() : - consumeCodeResponse.user.toJsonWithoutAccountLinking(); - - if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { - userJson.remove("tenantIds"); - } - - result.addProperty("createdNewUser", consumeCodeResponse.createdNewUser); - result.add("user", userJson); - if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { - for (LoginMethod loginMethod : consumeCodeResponse.user.loginMethods) { - if (loginMethod.recipeId.equals(RECIPE_ID.PASSWORDLESS) - && (consumeCodeResponse.email == null || Objects.equals(loginMethod.email, consumeCodeResponse.email)) - && (consumeCodeResponse.phoneNumber == null || Objects.equals(loginMethod.phoneNumber, consumeCodeResponse.phoneNumber))) { - result.addProperty("recipeUserId", loginMethod.getSupertokensOrExternalUserId()); - break; - } - } - } + if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { + userJson.remove("tenantIds"); } - if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v5_0)) { - JsonObject jsonDevice = new JsonObject(); - jsonDevice.addProperty("preAuthSessionId", consumeCodeResponse.consumedDevice.deviceIdHash); - jsonDevice.addProperty("failedCodeInputAttemptCount", consumeCodeResponse.consumedDevice.failedAttempts); - - if (consumeCodeResponse.consumedDevice.email != null) { - jsonDevice.addProperty("email", consumeCodeResponse.consumedDevice.email); - } - - if (consumeCodeResponse.consumedDevice.phoneNumber != null) { - jsonDevice.addProperty("phoneNumber", consumeCodeResponse.consumedDevice.phoneNumber); + result.addProperty("createdNewUser", consumeCodeResponse.createdNewUser); + result.add("user", userJson); + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + for (LoginMethod loginMethod : consumeCodeResponse.user.loginMethods) { + if (loginMethod.recipeId.equals(RECIPE_ID.PASSWORDLESS) + && (consumeCodeResponse.email == null || Objects.equals(loginMethod.email, consumeCodeResponse.email)) + && (consumeCodeResponse.phoneNumber == null || Objects.equals(loginMethod.phoneNumber, consumeCodeResponse.phoneNumber))) { + result.addProperty("recipeUserId", loginMethod.getSupertokensOrExternalUserId()); + break; + } } - - result.add("consumedDevice", jsonDevice); } super.sendJsonResponse(200, result, resp); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodeAPI.java index ea3b76fce..817b68faf 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodeAPI.java @@ -23,6 +23,7 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.utils.SemVer; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -50,10 +51,29 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I // Logic based on: https://app.code2flow.com/DDhe9U1rsFsQ JsonObject input = InputParser.parseJsonObjectOrThrowError(req); - String codeId = InputParser.parseStringOrThrowError(input, "codeId", false); + String codeId = InputParser.parseStringOrThrowError( + input, "codeId", getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v5_0)); + String deviceIdHash = null; + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v5_0)) { + deviceIdHash = InputParser.parseStringOrThrowError(input, "preAuthSessionId", true); + } + + if (codeId == null && deviceIdHash == null) { + throw new ServletException(new BadRequestException("Please provide either 'codeId' or 'preAuthSessionId'")); + } + + if (codeId != null && deviceIdHash != null) { + throw new ServletException(new BadRequestException("Please provide only one of 'codeId' or " + + "'preAuthSessionId'")); + } try { - Passwordless.removeCode(getTenantIdentifier(req), getTenantStorage(req), codeId); + if (codeId != null) { + Passwordless.removeCode(getTenantIdentifier(req), getTenantStorage(req), codeId); + } else { + Passwordless.removeDevice(getTenantIdentifier(req), getTenantStorage(req), + deviceIdHash); + } JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessConsumeCodeAPITest5_0.java b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessCheckCodeAPITest5_0.java similarity index 89% rename from src/test/java/io/supertokens/test/passwordless/api/PasswordlessConsumeCodeAPITest5_0.java rename to src/test/java/io/supertokens/test/passwordless/api/PasswordlessCheckCodeAPITest5_0.java index 6488e8255..4e7363f34 100644 --- a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessConsumeCodeAPITest5_0.java +++ b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessCheckCodeAPITest5_0.java @@ -38,7 +38,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -public class PasswordlessConsumeCodeAPITest5_0 { +public class PasswordlessCheckCodeAPITest5_0 { @Rule public TestRule watchman = Utils.getOnFailure(); @@ -73,7 +73,7 @@ public void testBadInput() throws Exception { JsonObject consumeCodeRequestBody = new JsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -93,7 +93,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -116,7 +116,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("userInputCode", createResp.userInputCode); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -138,7 +138,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("userInputCode", createResp.userInputCode); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -158,7 +158,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("userInputCode", createResp.userInputCode); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -178,7 +178,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -200,7 +200,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("userInputCode", createResp.userInputCode); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -220,7 +220,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -243,7 +243,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode + "==#"); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -266,7 +266,7 @@ public void testBadInput() throws Exception { consumeCodeRequestBody.addProperty("deviceId", createResp.deviceId + "==#"); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -307,13 +307,13 @@ public void testLinkCode() throws Exception { consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); - checkResponse(response, true, email, null); + checkResponse(response); int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); - assert (activeUsers == 1); + assert (activeUsers == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -342,7 +342,7 @@ public void testExpiredLinkCode() throws Exception { consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("RESTART_FLOW_ERROR", response.get("status").getAsString()); @@ -376,13 +376,54 @@ public void testUserInputCode() throws Exception { consumeCodeRequestBody.addProperty("userInputCode", createResp.userInputCode); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); - checkResponse(response, true, email, null); + checkResponse(response); int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); - assert (activeUsers == 1); + assert (activeUsers == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUserInputCodeDoesNotDeleteTheCode() 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; + } + + long startTs = System.currentTimeMillis(); + + String email = "test@example.com"; + CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), email, null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("deviceId", createResp.deviceId); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("userInputCode", createResp.userInputCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v5_0.get(), "passwordless"); + + checkResponse(response); + + // should be able to call again, if the code is not deleted + response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v5_0.get(), "passwordless"); + + checkResponse(response); + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); + assert (activeUsers == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -412,7 +453,7 @@ public void testExpiredUserInputCode() throws Exception { consumeCodeRequestBody.addProperty("userInputCode", createResp.userInputCode); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("EXPIRED_USER_INPUT_CODE_ERROR", response.get("status").getAsString()); @@ -447,7 +488,7 @@ public void testIncorrectUserInputCode() throws Exception { { JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("INCORRECT_USER_INPUT_CODE_ERROR", response.get("status").getAsString()); @@ -455,7 +496,7 @@ public void testIncorrectUserInputCode() throws Exception { { JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("RESTART_FLOW_ERROR", response.get("status").getAsString()); @@ -487,15 +528,12 @@ public void testConsumeCodeWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); - assertEquals(2, response.entrySet().size()); + assertEquals(1, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); - JsonObject consumedDevice = response.get("consumedDevice").getAsJsonObject(); - assertEquals("test@example.com", consumedDevice.get("email").getAsString()); - int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); assert (activeUsers == 0); @@ -506,7 +544,7 @@ public void testConsumeCodeWithoutCreatingUser() throws Exception { } @Test - public void testConsumeCodeWithoutCreatingUsersReturnsUserIfItAlreadyExists() throws Exception { + public void testVerifyCodeReturnsUserIfItAlreadyExists() throws Exception { String[] args = { "../" }; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); @@ -532,13 +570,13 @@ public void testConsumeCodeWithoutCreatingUsersReturnsUserIfItAlreadyExists() th consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); - checkResponse(response, false, email, null); + checkResponse(response); int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); - assert (activeUsers == 1); + assert (activeUsers == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -565,7 +603,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { JsonObject consumeCodeRequestBody = new JsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -586,7 +624,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -610,7 +648,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -633,7 +671,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -654,7 +692,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -675,7 +713,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -698,7 +736,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -719,7 +757,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -743,7 +781,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -767,7 +805,7 @@ public void testBadInputWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); } catch (HttpResponseException ex) { error = ex; @@ -808,13 +846,13 @@ public void testLinkCodeWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); - checkResponse(response, true, email, null); + checkResponse(response); int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); - assert (activeUsers == 1); + assert (activeUsers == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -844,7 +882,7 @@ public void testExpiredLinkCodeWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("RESTART_FLOW_ERROR", response.get("status").getAsString()); @@ -881,7 +919,7 @@ public void testExpiredUserInputCodeWithoutCreatingUser() throws Exception { consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", false); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("EXPIRED_USER_INPUT_CODE_ERROR", response.get("status").getAsString()); @@ -917,7 +955,7 @@ public void testIncorrectUserInputCodeWithoutCreatingUser() throws Exception { { JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("INCORRECT_USER_INPUT_CODE_ERROR", response.get("status").getAsString()); @@ -925,7 +963,7 @@ public void testIncorrectUserInputCodeWithoutCreatingUser() throws Exception { { JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + "http://localhost:3567/recipe/signinup/code/check", consumeCodeRequestBody, 1000, 1000, null, SemVer.v5_0.get(), "passwordless"); assertEquals("RESTART_FLOW_ERROR", response.get("status").getAsString()); @@ -934,70 +972,9 @@ public void testIncorrectUserInputCodeWithoutCreatingUser() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } - @Test - public void testLinkCodeWithCreateUserSetToTrue() 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; - } - - long startTs = System.currentTimeMillis(); - - String email = "test@example.com"; - CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), email, null, null, null); - - JsonObject consumeCodeRequestBody = new JsonObject(); - consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); - consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); - consumeCodeRequestBody.addProperty("createRecipeUserIfNotExists", true); - - JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, - SemVer.v5_0.get(), "passwordless"); - - checkResponse(response, true, email, null); - - int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); - assert (activeUsers == 1); - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); - } - - private void checkResponse(JsonObject response, Boolean isNewUser, String email, String phoneNumber) { + private void checkResponse(JsonObject response) { assertEquals("OK", response.get("status").getAsString()); - assertEquals(isNewUser, response.get("createdNewUser").getAsBoolean()); - assert (response.has("user")); - assertEquals(5, response.entrySet().size()); - - JsonObject userJson = response.getAsJsonObject("user"); - if (email == null) { - assert (!userJson.has("email")); - } else { - assertEquals(email, userJson.get("emails").getAsJsonArray().get(0).getAsString()); - } - - if (phoneNumber == null) { - assert (!userJson.has("phoneNumber")); - } else if (phoneNumber != null) { - assertEquals(phoneNumber, userJson.get("phoneNumbers").getAsJsonArray().get(0).getAsString()); - } - - assertEquals(8, userJson.entrySet().size()); - assertEquals(response.get("recipeUserId").getAsString(), userJson.get("id").getAsString()); - - JsonObject consumedDevice = response.getAsJsonObject("consumedDevice"); - if (email != null) { - assertEquals(email, consumedDevice.get("email").getAsString()); - } - - if (phoneNumber != null) { - assertEquals(phoneNumber, consumedDevice.get("phoneNumber").getAsString()); - } + assertEquals(1, response.entrySet().size()); } } diff --git a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessDeleteCodeAPITest5_0.java b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessDeleteCodeAPITest5_0.java new file mode 100644 index 000000000..9270d05b5 --- /dev/null +++ b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessDeleteCodeAPITest5_0.java @@ -0,0 +1,215 @@ +/* + * 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.passwordless.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.passwordless.PasswordlessCode; +import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage; +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.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + +public class PasswordlessDeleteCodeAPITest5_0 { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void testDeleteCode() 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; + } + + PasswordlessSQLStorage storage = (PasswordlessSQLStorage) StorageLayer.getStorage(process.getProcess()); + + String phoneNumber = "+442071838750"; + String codeId = "codeId"; + + String deviceIdHash = "pZ9SP0USbXbejGFO6qx7x3JBjupJZVtw4RkFiNtJGqc"; + String linkCodeHash = "wo5UcFFVSblZEd1KOUOl-dpJ5zpSr_Qsor1Eg4TzDRE"; + + storage.createDeviceWithCode(new TenantIdentifier(null, null, null), null, phoneNumber, "linkCodeSalt", + new PasswordlessCode(codeId, deviceIdHash, linkCodeHash, System.currentTimeMillis())); + + JsonObject createCodeRequestBody = new JsonObject(); + createCodeRequestBody.addProperty("codeId", codeId); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/remove", createCodeRequestBody, 1000, 1000, null, + SemVer.v5_0.get(), "passwordless"); + + assertEquals("OK", response.get("status").getAsString()); + + assertNull(storage.getCode(new TenantIdentifier(null, null, null), codeId)); + assertNull(storage.getDevice(new TenantIdentifier(null, null, null), deviceIdHash)); + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testDeleteNonExistentCode() 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; + } + + String codeId = "codeId"; + + JsonObject createCodeRequestBody = new JsonObject(); + createCodeRequestBody.addProperty("codeId", codeId); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/remove", createCodeRequestBody, 1000, 1000, null, + SemVer.v5_0.get(), "passwordless"); + + assertEquals("OK", response.get("status").getAsString()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + /** + * empty request body -> BadRequest + * + * @throws Exception + */ + @Test + public void testEmptyRequestBody() 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 createCodeRequestBody = new JsonObject(); + + HttpResponseException error = null; + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/remove", createCodeRequestBody, 1000, 1000, null, + SemVer.v5_0.get(), "passwordless"); + + } catch (HttpResponseException ex) { + error = ex; + } + + assertNotNull(error); + assertEquals(400, error.statusCode); + assertEquals("Http error. Status Code: 400. Message: Please provide either 'codeId' or 'preAuthSessionId'", + error.getMessage()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testDeleteNonExistantDeviceIdHash() 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; + } + + String preAuthSessionId = "preAuthSessionId"; + + JsonObject createCodeRequestBody = new JsonObject(); + createCodeRequestBody.addProperty("preAuthSessionId", preAuthSessionId); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/remove", createCodeRequestBody, 1000, 1000, null, + SemVer.v5_0.get(), "passwordless"); + + assertEquals("OK", response.get("status").getAsString()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testDeleteDeviceIdHash() 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; + } + + PasswordlessSQLStorage storage = (PasswordlessSQLStorage) StorageLayer.getStorage(process.getProcess()); + + String phoneNumber = "+442071838750"; + String codeId = "codeId"; + + String deviceIdHash = "pZ9SP0USbXbejGFO6qx7x3JBjupJZVtw4RkFiNtJGqc"; + String linkCodeHash = "wo5UcFFVSblZEd1KOUOl-dpJ5zpSr_Qsor1Eg4TzDRE"; + + storage.createDeviceWithCode(new TenantIdentifier(null, null, null), null, phoneNumber, "linkCodeSalt", + new PasswordlessCode(codeId, deviceIdHash, linkCodeHash, System.currentTimeMillis())); + + JsonObject createCodeRequestBody = new JsonObject(); + createCodeRequestBody.addProperty("preAuthSessionId", deviceIdHash); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/remove", createCodeRequestBody, 1000, 1000, null, + SemVer.v5_0.get(), "passwordless"); + + assertEquals("OK", response.get("status").getAsString()); + + assertNull(storage.getCode(new TenantIdentifier(null, null, null), codeId)); + assertNull(storage.getDevice(new TenantIdentifier(null, null, null), deviceIdHash)); + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +}